Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LVI][CVP] Treat undef like a full range #68190

Merged
merged 2 commits into from
Oct 10, 2023
Merged

Conversation

DianQK
Copy link
Member

@DianQK DianQK commented Oct 4, 2023

Fixes #68381.

@DianQK DianQK changed the title [LVI][CVP] getRangeFor propagates MayIncludeUndef [LVI][CVP] propagates undef to range and abs Oct 6, 2023
@DianQK DianQK requested a review from RKSimon October 6, 2023 07:48
@DianQK DianQK marked this pull request as ready for review October 6, 2023 08:42
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 6, 2023

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-analysis

Changes

Fixes #68381.


Full diff: https://github.com/llvm/llvm-project/pull/68190.diff

4 Files Affected:

  • (modified) llvm/include/llvm/Analysis/LazyValueInfo.h (+4-2)
  • (modified) llvm/lib/Analysis/LazyValueInfo.cpp (+47-23)
  • (modified) llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp (+3-3)
  • (added) llvm/test/Transforms/CorrelatedValuePropagation/merge-range-and-undef-2.ll (+109)
diff --git a/llvm/include/llvm/Analysis/LazyValueInfo.h b/llvm/include/llvm/Analysis/LazyValueInfo.h
index f013a4a75d3d6ad..55f9b3c14ca673d 100644
--- a/llvm/include/llvm/Analysis/LazyValueInfo.h
+++ b/llvm/include/llvm/Analysis/LazyValueInfo.h
@@ -70,14 +70,16 @@ namespace llvm {
     /// predicate.
     Tristate getPredicateOnEdge(unsigned Pred, Value *V, Constant *C,
                                 BasicBlock *FromBB, BasicBlock *ToBB,
-                                Instruction *CxtI = nullptr);
+                                Instruction *CxtI = nullptr,
+                                bool UndefinedAllowed = true);
 
     /// Determine whether the specified value comparison with a constant is
     /// known to be true or false at the specified instruction. \p Pred is a
     /// CmpInst predicate. If \p UseBlockValue is true, the block value is also
     /// taken into account.
     Tristate getPredicateAt(unsigned Pred, Value *V, Constant *C,
-                            Instruction *CxtI, bool UseBlockValue);
+                            Instruction *CxtI, bool UseBlockValue,
+                            bool UndefinedAllowed = true);
 
     /// Determine whether the specified value comparison is known to be true
     /// or false at the specified instruction. While this takes two Value's,
diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index 0892aa9d75fb417..d4da9c05ccfed90 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -411,7 +411,8 @@ class LazyValueInfoImpl {
   std::optional<ValueLatticeElement> solveBlockValueSelect(SelectInst *S,
                                                            BasicBlock *BB);
   std::optional<ConstantRange> getRangeFor(Value *V, Instruction *CxtI,
-                                           BasicBlock *BB);
+                                           BasicBlock *BB,
+                                           bool &MayIncludeUndef);
   std::optional<ValueLatticeElement> solveBlockValueBinaryOpImpl(
       Instruction *I, BasicBlock *BB,
       std::function<ConstantRange(const ConstantRange &, const ConstantRange &)>
@@ -892,11 +893,14 @@ LazyValueInfoImpl::solveBlockValueSelect(SelectInst *SI, BasicBlock *BB) {
 }
 
 std::optional<ConstantRange>
-LazyValueInfoImpl::getRangeFor(Value *V, Instruction *CxtI, BasicBlock *BB) {
+LazyValueInfoImpl::getRangeFor(Value *V, Instruction *CxtI, BasicBlock *BB,
+                               bool &MayIncludeUndef) {
   std::optional<ValueLatticeElement> OptVal = getBlockValue(V, BB, CxtI);
   if (!OptVal)
     return std::nullopt;
-  return getConstantRangeOrFull(*OptVal, V->getType(), DL);
+  const auto &Val = *OptVal;
+  MayIncludeUndef = Val.isConstantRangeIncludingUndef();
+  return getConstantRangeOrFull(Val, V->getType(), DL);
 }
 
 std::optional<ValueLatticeElement>
@@ -922,10 +926,12 @@ LazyValueInfoImpl::solveBlockValueCast(CastInst *CI, BasicBlock *BB) {
     return ValueLatticeElement::getOverdefined();
   }
 
+  bool MayIncludeUndef = false;
   // Figure out the range of the LHS.  If that fails, we still apply the
   // transfer rule on the full set since we may be able to locally infer
   // interesting facts.
-  std::optional<ConstantRange> LHSRes = getRangeFor(CI->getOperand(0), CI, BB);
+  std::optional<ConstantRange> LHSRes =
+      getRangeFor(CI->getOperand(0), CI, BB, MayIncludeUndef);
   if (!LHSRes)
     // More work to do before applying this transfer rule.
     return std::nullopt;
@@ -936,8 +942,8 @@ LazyValueInfoImpl::solveBlockValueCast(CastInst *CI, BasicBlock *BB) {
   // NOTE: We're currently limited by the set of operations that ConstantRange
   // can evaluate symbolically.  Enhancing that set will allows us to analyze
   // more definitions.
-  return ValueLatticeElement::getRange(LHSRange.castOp(CI->getOpcode(),
-                                                       ResultBitWidth));
+  return ValueLatticeElement::getRange(
+      LHSRange.castOp(CI->getOpcode(), ResultBitWidth), MayIncludeUndef);
 }
 
 std::optional<ValueLatticeElement>
@@ -949,15 +955,20 @@ LazyValueInfoImpl::solveBlockValueBinaryOpImpl(
   // conservative range, but apply the transfer rule anyways.  This
   // lets us pick up facts from expressions like "and i32 (call i32
   // @foo()), 32"
-  std::optional<ConstantRange> LHSRes = getRangeFor(I->getOperand(0), I, BB);
-  std::optional<ConstantRange> RHSRes = getRangeFor(I->getOperand(1), I, BB);
+  bool LMayIncludeUndef = false;
+  bool RMayIncludeUndef = false;
+  std::optional<ConstantRange> LHSRes =
+      getRangeFor(I->getOperand(0), I, BB, LMayIncludeUndef);
+  std::optional<ConstantRange> RHSRes =
+      getRangeFor(I->getOperand(1), I, BB, RMayIncludeUndef);
   if (!LHSRes || !RHSRes)
     // More work to do before applying this transfer rule.
     return std::nullopt;
 
   const ConstantRange &LHSRange = *LHSRes;
   const ConstantRange &RHSRange = *RHSRes;
-  return ValueLatticeElement::getRange(OpFn(LHSRange, RHSRange));
+  return ValueLatticeElement::getRange(OpFn(LHSRange, RHSRange),
+                                       LMayIncludeUndef || RMayIncludeUndef);
 }
 
 std::optional<ValueLatticeElement>
@@ -1002,16 +1013,21 @@ LazyValueInfoImpl::solveBlockValueIntrinsic(IntrinsicInst *II, BasicBlock *BB) {
     return MetadataVal;
   }
 
+  bool ArgsMayIncludeUndef = false;
   SmallVector<ConstantRange, 2> OpRanges;
   for (Value *Op : II->args()) {
-    std::optional<ConstantRange> Range = getRangeFor(Op, II, BB);
+    bool MayIncludeUndef = false;
+    std::optional<ConstantRange> Range =
+        getRangeFor(Op, II, BB, MayIncludeUndef);
     if (!Range)
       return std::nullopt;
+    ArgsMayIncludeUndef |= MayIncludeUndef;
     OpRanges.push_back(*Range);
   }
 
-  return intersect(ValueLatticeElement::getRange(ConstantRange::intrinsic(
-                       II->getIntrinsicID(), OpRanges)),
+  return intersect(ValueLatticeElement::getRange(
+                       ConstantRange::intrinsic(II->getIntrinsicID(), OpRanges),
+                       ArgsMayIncludeUndef),
                    MetadataVal);
 }
 
@@ -1750,7 +1766,8 @@ ConstantRange LazyValueInfo::getConstantRangeOnEdge(Value *V,
 
 static LazyValueInfo::Tristate
 getPredicateResult(unsigned Pred, Constant *C, const ValueLatticeElement &Val,
-                   const DataLayout &DL, TargetLibraryInfo *TLI) {
+                   const DataLayout &DL, TargetLibraryInfo *TLI,
+                   bool UndefinedAllowed = true) {
   // If we know the value is a constant, evaluate the conditional.
   Constant *Res = nullptr;
   if (Val.isConstant()) {
@@ -1761,6 +1778,8 @@ getPredicateResult(unsigned Pred, Constant *C, const ValueLatticeElement &Val,
   }
 
   if (Val.isConstantRange()) {
+    if (!UndefinedAllowed && Val.isConstantRangeIncludingUndef())
+      return LazyValueInfo::Unknown;
     ConstantInt *CI = dyn_cast<ConstantInt>(C);
     if (!CI) return LazyValueInfo::Unknown;
 
@@ -1818,17 +1837,20 @@ getPredicateResult(unsigned Pred, Constant *C, const ValueLatticeElement &Val,
 LazyValueInfo::Tristate
 LazyValueInfo::getPredicateOnEdge(unsigned Pred, Value *V, Constant *C,
                                   BasicBlock *FromBB, BasicBlock *ToBB,
-                                  Instruction *CxtI) {
+                                  Instruction *CxtI, bool UndefinedAllowed) {
   Module *M = FromBB->getModule();
   ValueLatticeElement Result =
       getOrCreateImpl(M).getValueOnEdge(V, FromBB, ToBB, CxtI);
 
-  return getPredicateResult(Pred, C, Result, M->getDataLayout(), TLI);
+  return getPredicateResult(Pred, C, Result, M->getDataLayout(), TLI,
+                            UndefinedAllowed);
 }
 
-LazyValueInfo::Tristate
-LazyValueInfo::getPredicateAt(unsigned Pred, Value *V, Constant *C,
-                              Instruction *CxtI, bool UseBlockValue) {
+LazyValueInfo::Tristate LazyValueInfo::getPredicateAt(unsigned Pred, Value *V,
+                                                      Constant *C,
+                                                      Instruction *CxtI,
+                                                      bool UseBlockValue,
+                                                      bool UndefinedAllowed) {
   // Is or is not NonNull are common predicates being queried. If
   // isKnownNonZero can tell us the result of the predicate, we can
   // return it quickly. But this is only a fastpath, and falling
@@ -1847,7 +1869,7 @@ LazyValueInfo::getPredicateAt(unsigned Pred, Value *V, Constant *C,
   ValueLatticeElement Result =
       UseBlockValue ? Impl.getValueInBlock(V, CxtI->getParent(), CxtI)
                     : Impl.getValueAt(V, CxtI);
-  Tristate Ret = getPredicateResult(Pred, C, Result, DL, TLI);
+  Tristate Ret = getPredicateResult(Pred, C, Result, DL, TLI, UndefinedAllowed);
   if (Ret != Unknown)
     return Ret;
 
@@ -1892,8 +1914,8 @@ LazyValueInfo::getPredicateAt(unsigned Pred, Value *V, Constant *C,
         Value *Incoming = PHI->getIncomingValue(i);
         BasicBlock *PredBB = PHI->getIncomingBlock(i);
         // Note that PredBB may be BB itself.
-        Tristate Result =
-            getPredicateOnEdge(Pred, Incoming, C, PredBB, BB, CxtI);
+        Tristate Result = getPredicateOnEdge(Pred, Incoming, C, PredBB, BB,
+                                             CxtI, UndefinedAllowed);
 
         // Keep going as long as we've seen a consistent known result for
         // all inputs.
@@ -1914,11 +1936,13 @@ LazyValueInfo::getPredicateAt(unsigned Pred, Value *V, Constant *C,
     // For predecessor edge, determine if the comparison is true or false
     // on that edge. If they're all true or all false, we can conclude
     // the value of the comparison in this block.
-    Tristate Baseline = getPredicateOnEdge(Pred, V, C, *PI, BB, CxtI);
+    Tristate Baseline =
+        getPredicateOnEdge(Pred, V, C, *PI, BB, CxtI, UndefinedAllowed);
     if (Baseline != Unknown) {
       // Check that all remaining incoming values match the first one.
       while (++PI != PE) {
-        Tristate Ret = getPredicateOnEdge(Pred, V, C, *PI, BB, CxtI);
+        Tristate Ret =
+            getPredicateOnEdge(Pred, V, C, *PI, BB, CxtI, UndefinedAllowed);
         if (Ret != Baseline)
           break;
       }
diff --git a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
index 48b27a1ea0a2984..b5d34638456af41 100644
--- a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
+++ b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
@@ -479,7 +479,7 @@ static bool processAbsIntrinsic(IntrinsicInst *II, LazyValueInfo *LVI) {
 
   // Is X in [0, IntMin]?  NOTE: INT_MIN is fine!
   Result = LVI->getPredicateAt(CmpInst::Predicate::ICMP_ULE, X, IntMin, II,
-                               /*UseBlockValue=*/true);
+                               /*UseBlockValue=*/true, IsIntMinPoison);
   if (Result == LazyValueInfo::True) {
     ++NumAbs;
     II->replaceAllUsesWith(X);
@@ -490,7 +490,7 @@ static bool processAbsIntrinsic(IntrinsicInst *II, LazyValueInfo *LVI) {
   // Is X in [IntMin, 0]?  NOTE: INT_MIN is fine!
   Constant *Zero = ConstantInt::getNullValue(Ty);
   Result = LVI->getPredicateAt(CmpInst::Predicate::ICMP_SLE, X, Zero, II,
-                               /*UseBlockValue=*/true);
+                               /*UseBlockValue=*/true, IsIntMinPoison);
   assert(Result != LazyValueInfo::False && "Should have been handled already.");
 
   if (Result == LazyValueInfo::Unknown) {
@@ -499,7 +499,7 @@ static bool processAbsIntrinsic(IntrinsicInst *II, LazyValueInfo *LVI) {
     if (!IsIntMinPoison) {
       // Can we at least tell that the argument is never INT_MIN?
       Result = LVI->getPredicateAt(CmpInst::Predicate::ICMP_NE, X, IntMin, II,
-                                   /*UseBlockValue=*/true);
+                                   /*UseBlockValue=*/true, IsIntMinPoison);
       if (Result == LazyValueInfo::True) {
         ++NumNSW;
         ++NumSubNSW;
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/merge-range-and-undef-2.ll b/llvm/test/Transforms/CorrelatedValuePropagation/merge-range-and-undef-2.ll
new file mode 100644
index 000000000000000..4188f5e732d640d
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/merge-range-and-undef-2.ll
@@ -0,0 +1,109 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 3
+; RUN: opt -S -passes=correlated-propagation %s | FileCheck %s
+
+; Test case for PR68381.
+
+; Don't delete the `and` instruction.
+
+define i32 @constant_range_and_undef_and(i1 %c0, i1 %c1, i8 %v1, i8 %v2) {
+; CHECK-LABEL: define i32 @constant_range_and_undef_and(
+; CHECK-SAME: i1 [[C0:%.*]], i1 [[C1:%.*]], i8 [[V1:%.*]], i8 [[V2:%.*]]) {
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    br i1 [[C0]], label [[BB0:%.*]], label [[BB1:%.*]]
+; CHECK:       bb0:
+; CHECK-NEXT:    [[V1_I32:%.*]] = zext i8 [[V1]] to i32
+; CHECK-NEXT:    br label [[BB1]]
+; CHECK:       bb1:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ [[V1_I32]], [[BB0]] ], [ undef, [[START:%.*]] ]
+; CHECK-NEXT:    br i1 [[C1]], label [[BB0]], label [[BB2:%.*]]
+; CHECK:       bb2:
+; CHECK-NEXT:    [[V2_I32:%.*]] = zext i8 [[V2]] to i32
+; CHECK-NEXT:    [[Y:%.*]] = or i32 [[X]], [[V2_I32]]
+; CHECK-NEXT:    [[Z:%.*]] = and i32 [[Y]], 255
+; CHECK-NEXT:    ret i32 [[Z]]
+;
+start:
+  br i1 %c0, label %bb0, label %bb1
+
+bb0:
+  %v1_i32 = zext i8 %v1 to i32
+  br label %bb1
+
+bb1:
+  %x = phi i32 [ %v1_i32, %bb0 ], [ undef, %start ]
+  br i1 %c1, label %bb0, label %bb2
+
+bb2:
+  %v2_i32 = zext i8 %v2 to i32
+  %y = or i32 %x, %v2_i32
+  %z = and i32 %y, 255
+  ret i32 %z
+}
+
+; Don't delete the `abs` intrinsic function.
+
+define i32 @constant_range_and_undef_abs_false(i1 %c0, i1 %c1, i8 %v1) {
+; CHECK-LABEL: define i32 @constant_range_and_undef_abs_false(
+; CHECK-SAME: i1 [[C0:%.*]], i1 [[C1:%.*]], i8 [[V1:%.*]]) {
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    br i1 [[C0]], label [[BB0:%.*]], label [[BB1:%.*]]
+; CHECK:       bb0:
+; CHECK-NEXT:    [[V1_I32:%.*]] = zext i8 [[V1]] to i32
+; CHECK-NEXT:    br label [[BB1]]
+; CHECK:       bb1:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ [[V1_I32]], [[BB0]] ], [ undef, [[START:%.*]] ]
+; CHECK-NEXT:    br i1 [[C1]], label [[BB0]], label [[BB2:%.*]]
+; CHECK:       bb2:
+; CHECK-NEXT:    [[Z:%.*]] = call i32 @llvm.abs.i32(i32 [[X]], i1 false)
+; CHECK-NEXT:    ret i32 [[Z]]
+;
+start:
+  br i1 %c0, label %bb0, label %bb1
+
+bb0:
+  %v1_i32 = zext i8 %v1 to i32
+  br label %bb1
+
+bb1:
+  %x = phi i32 [ %v1_i32, %bb0 ], [ undef, %start ]
+  br i1 %c1, label %bb0, label %bb2
+
+bb2:
+  %z = call i32 @llvm.abs.i32(i32 %x, i1 false)
+  ret i32 %z
+}
+
+; We can delete the `abs` intrinsic function.
+
+define i32 @constant_range_and_undef_true(i1 %c0, i1 %c1, i8 %v1) {
+; CHECK-LABEL: define i32 @constant_range_and_undef_true(
+; CHECK-SAME: i1 [[C0:%.*]], i1 [[C1:%.*]], i8 [[V1:%.*]]) {
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    br i1 [[C0]], label [[BB0:%.*]], label [[BB1:%.*]]
+; CHECK:       bb0:
+; CHECK-NEXT:    [[V1_I32:%.*]] = zext i8 [[V1]] to i32
+; CHECK-NEXT:    br label [[BB1]]
+; CHECK:       bb1:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ [[V1_I32]], [[BB0]] ], [ undef, [[START:%.*]] ]
+; CHECK-NEXT:    br i1 [[C1]], label [[BB0]], label [[BB2:%.*]]
+; CHECK:       bb2:
+; CHECK-NEXT:    ret i32 [[X]]
+;
+start:
+  br i1 %c0, label %bb0, label %bb1
+
+bb0:
+  %v1_i32 = zext i8 %v1 to i32
+  br label %bb1
+
+bb1:
+  %x = phi i32 [ %v1_i32, %bb0 ], [ undef, %start ]
+  br i1 %c1, label %bb0, label %bb2
+
+bb2:
+  %z = call i32 @llvm.abs.i32(i32 %x, i1 true)
+  ret i32 %z
+}
+
+declare i32 @llvm.abs.i32(i32, i1)
+

@DianQK
Copy link
Member Author

DianQK commented Oct 9, 2023

Gently ping. @nikic

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right approach to fixing this. The core fix here should be along these lines:

diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index 0892aa9d75fb..789a02ed329c 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -801,7 +801,7 @@ void LazyValueInfoImpl::intersectAssumeOrGuardBlockValueConstantRange(
 
 static ConstantRange getConstantRangeOrFull(const ValueLatticeElement &Val,
                                             Type *Ty, const DataLayout &DL) {
-  if (Val.isConstantRange())
+  if (Val.isConstantRange(/*UndefAllowed*/ false))
     return Val.getConstantRange();
   return ConstantRange::getFull(DL.getTypeSizeInBits(Ty));
 }

That is, when converting to ConstantRange, we should treat undef like a full range.

The way you are propagating undef here is basically as if it were poison, but this doesn't match its semantics. zext CR|undef does not result in CR.zext()|undef, because the top bits are guaranteed to be zero even if the input is undef.

I think this approach will result in a simpler and more robust fix.

@DianQK
Copy link
Member Author

DianQK commented Oct 10, 2023

The way you are propagating undef here is basically as if it were poison, but this doesn't match its semantics. zext CR|undef does not result in CR.zext()|undef, because the top bits are guaranteed to be zero even if the input is undef.

Thanks for pointing that out. Yes! This is the better approach.

For a better review and commit history, I will submit a separate PR for abs. And test cases for the new abs may depend on the PR.

@DianQK DianQK changed the title [LVI][CVP] propagates undef to range and abs [LVI][CVP] Treat undef like a full range Oct 10, 2023
@tru
Copy link
Collaborator

tru commented Oct 10, 2023

Can this PR be closed then?

@DianQK
Copy link
Member Author

DianQK commented Oct 10, 2023

Can this PR be closed then?

This PR should be merged. I made the changes in this PR as suggested by @nikic.
This PR is used to fix the origin issue with the and instruction.
I'll submit a new PR to fix the abs issue later.

@tru
Copy link
Collaborator

tru commented Oct 10, 2023

Ah never mind. I thought it was targeted for a backport to the release, but I just mixed up my notifications.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@DianQK DianQK merged commit 8185794 into llvm:main Oct 10, 2023
3 checks passed
@DianQK DianQK deleted the lvi-propagate-undef branch October 10, 2023 14:04
tru pushed a commit that referenced this pull request Oct 16, 2023
When converting to ConstantRange, we should treat undef like a full range.
Fixes #68381.

(cherry picked from commit 8185794)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[LVI][CVP] CVP error deleted the and instruction.
4 participants