Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[LVI][ValueTracking] Take UB-implying attributes into account in isSafeToSpeculativelyExecute #137604

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

Merged
merged 5 commits into from
Apr 30, 2025

Conversation

dtcxzyw
Copy link
Member

@dtcxzyw dtcxzyw commented Apr 28, 2025

Closes #137582.

In the original case, LVI uses the edge information in %entry -> %if.end to get a more precise result. However, since the call to smin has an noundef return attribute, an immediate UB will be triggered after optimization.

Currently, isSafeToSpeculativelyExecuteWithOpcode(%min) returns true because 6a288c1 only checks whether the function is speculatable. However, it is not enough in this case.

This patch takes UB-implying attributes into account if IgnoreUBImplyingAttrs is set to false. If it is set to true, the caller is responsible for correctly propagating UB-implying attributes.

@llvmbot
Copy link
Member

llvmbot commented Apr 28, 2025

@llvm/pr-subscribers-llvm-ir
@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-llvm-transforms

Author: Yingwei Zheng (dtcxzyw)

Changes

Closes #137582.

In the original case, LVI uses the edge information in %entry -> %if.end to get a more precise result. However, since the call to smin has an noundef return attribute, an immediate UB will be triggered after optimization.

Currently, isSafeToSpeculativelyExecuteWithOpcode(%min) returns true because 6a288c1 only checks whether the function is speculatable. However, it is not enough in this case.

This patch takes UB-implying attributes into account if AllowRefinement is set to false. If it is set to true, the caller is responsible for correctly propagating UB-implying attributes and metadata.


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

4 Files Affected:

  • (modified) llvm/include/llvm/Analysis/ValueTracking.h (+19-6)
  • (modified) llvm/lib/Analysis/LazyValueInfo.cpp (+2-1)
  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+22-10)
  • (added) llvm/test/Transforms/CorrelatedValuePropagation/pr137582.ll (+34)
diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index f927838c843ac..bf43a6297da4a 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -539,6 +539,13 @@ bool isNotCrossLaneOperation(const Instruction *I);
 /// move the instruction as long as the correct dominance relationships for
 /// the operands and users hold.
 ///
+/// If \p UseVariableInfo is true, the information from non-constant operands
+/// will be taken into account.
+///
+/// If \p AllowRefinement is true, UB-implying attributes and metadata will be
+/// ignored. The caller is responsible for correctly propagating them after
+/// hoisting.
+///
 /// This method can return true for instructions that read memory;
 /// for such instructions, moving them may change the resulting value.
 bool isSafeToSpeculativelyExecute(const Instruction *I,
@@ -546,24 +553,29 @@ bool isSafeToSpeculativelyExecute(const Instruction *I,
                                   AssumptionCache *AC = nullptr,
                                   const DominatorTree *DT = nullptr,
                                   const TargetLibraryInfo *TLI = nullptr,
-                                  bool UseVariableInfo = true);
+                                  bool UseVariableInfo = true,
+                                  bool AllowRefinement = true);
 
 inline bool isSafeToSpeculativelyExecute(const Instruction *I,
                                          BasicBlock::iterator CtxI,
                                          AssumptionCache *AC = nullptr,
                                          const DominatorTree *DT = nullptr,
                                          const TargetLibraryInfo *TLI = nullptr,
-                                         bool UseVariableInfo = true) {
+                                         bool UseVariableInfo = true,
+                                         bool AllowRefinement = true) {
   // Take an iterator, and unwrap it into an Instruction *.
-  return isSafeToSpeculativelyExecute(I, &*CtxI, AC, DT, TLI, UseVariableInfo);
+  return isSafeToSpeculativelyExecute(I, &*CtxI, AC, DT, TLI, UseVariableInfo,
+                                      AllowRefinement);
 }
 
 /// Don't use information from its non-constant operands. This helper is used
 /// when its operands are going to be replaced.
 inline bool
-isSafeToSpeculativelyExecuteWithVariableReplaced(const Instruction *I) {
+isSafeToSpeculativelyExecuteWithVariableReplaced(const Instruction *I,
+                                                 bool AllowRefinement = true) {
   return isSafeToSpeculativelyExecute(I, nullptr, nullptr, nullptr, nullptr,
-                                      /*UseVariableInfo=*/false);
+                                      /*UseVariableInfo=*/false,
+                                      AllowRefinement);
 }
 
 /// This returns the same result as isSafeToSpeculativelyExecute if Opcode is
@@ -586,7 +598,8 @@ isSafeToSpeculativelyExecuteWithVariableReplaced(const Instruction *I) {
 bool isSafeToSpeculativelyExecuteWithOpcode(
     unsigned Opcode, const Instruction *Inst, const Instruction *CtxI = nullptr,
     AssumptionCache *AC = nullptr, const DominatorTree *DT = nullptr,
-    const TargetLibraryInfo *TLI = nullptr, bool UseVariableInfo = true);
+    const TargetLibraryInfo *TLI = nullptr, bool UseVariableInfo = true,
+    bool AllowRefinement = true);
 
 /// Returns true if the result or effects of the given instructions \p I
 /// depend values not reachable through the def use graph.
diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index e49e004a48a51..4418869dc4a78 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -1701,7 +1701,8 @@ ValueLatticeElement LazyValueInfoImpl::getValueAtUse(const Use &U) {
     // of a cycle, we might end up reasoning about values from different cycle
     // iterations (PR60629).
     if (!CurrI->hasOneUse() ||
-        !isSafeToSpeculativelyExecuteWithVariableReplaced(CurrI))
+        !isSafeToSpeculativelyExecuteWithVariableReplaced(
+            CurrI, /*AllowRefinement=*/false))
       break;
     CurrU = &*CurrI->use_begin();
   }
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 1d3f8b7207a63..a7eb6c8feae0c 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -7201,20 +7201,19 @@ bool llvm::isNotCrossLaneOperation(const Instruction *I) {
          !isa<CallBase, BitCastInst, ExtractElementInst>(I);
 }
 
-bool llvm::isSafeToSpeculativelyExecute(const Instruction *Inst,
-                                        const Instruction *CtxI,
-                                        AssumptionCache *AC,
-                                        const DominatorTree *DT,
-                                        const TargetLibraryInfo *TLI,
-                                        bool UseVariableInfo) {
+bool llvm::isSafeToSpeculativelyExecute(
+    const Instruction *Inst, const Instruction *CtxI, AssumptionCache *AC,
+    const DominatorTree *DT, const TargetLibraryInfo *TLI, bool UseVariableInfo,
+    bool AllowRefinement) {
   return isSafeToSpeculativelyExecuteWithOpcode(Inst->getOpcode(), Inst, CtxI,
-                                                AC, DT, TLI, UseVariableInfo);
+                                                AC, DT, TLI, UseVariableInfo,
+                                                AllowRefinement);
 }
 
 bool llvm::isSafeToSpeculativelyExecuteWithOpcode(
     unsigned Opcode, const Instruction *Inst, const Instruction *CtxI,
     AssumptionCache *AC, const DominatorTree *DT, const TargetLibraryInfo *TLI,
-    bool UseVariableInfo) {
+    bool UseVariableInfo, bool AllowRefinement) {
 #ifndef NDEBUG
   if (Inst->getOpcode() != Opcode) {
     // Check that the operands are actually compatible with the Opcode override.
@@ -7266,7 +7265,7 @@ bool llvm::isSafeToSpeculativelyExecuteWithOpcode(
     return false;
   }
   case Instruction::Load: {
-    if (!UseVariableInfo)
+    if (!UseVariableInfo || !AllowRefinement)
       return false;
 
     const LoadInst *LI = dyn_cast<LoadInst>(Inst);
@@ -7287,7 +7286,20 @@ bool llvm::isSafeToSpeculativelyExecuteWithOpcode(
 
     // The called function could have undefined behavior or side-effects, even
     // if marked readnone nounwind.
-    return Callee && Callee->isSpeculatable();
+    if (!Callee || !Callee->isSpeculatable())
+      return false;
+    // Since the operands may be changed after hoisting, undefined behavior may
+    // be triggered by some UB-implying attributes.
+    if (!AllowRefinement) {
+      if (CI->hasRetAttr(Attribute::NoUndef) ||
+          CI->getRetDereferenceableBytes() > 0 ||
+          CI->getRetDereferenceableOrNullBytes() > 0 ||
+          any_of(CI->args(), [&](const Use &U) {
+            return CI->isPassingUndefUB(CI->getArgOperandNo(&U));
+          }))
+        return false;
+    }
+    return true;
   }
   case Instruction::VAArg:
   case Instruction::Alloca:
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/pr137582.ll b/llvm/test/Transforms/CorrelatedValuePropagation/pr137582.ll
new file mode 100644
index 0000000000000..7433606988285
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/pr137582.ll
@@ -0,0 +1,34 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=correlated-propagation -S | FileCheck %s
+
+; Make sure that the optimization does not introduce immediate UB.
+
+define i8 @test(i16 %x) {
+; CHECK-LABEL: define range(i8 -128, 1) i8 @test(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[OR:%.*]] = or i16 [[X]], 1
+; CHECK-NEXT:    [[CONV:%.*]] = trunc i16 [[OR]] to i8
+; CHECK-NEXT:    [[MIN:%.*]] = call noundef i8 @llvm.smin.i8(i8 [[CONV]], i8 0)
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i16 [[X]], 0
+; CHECK-NEXT:    br i1 [[COND]], label %[[IF_END:.*]], label %[[IF_THEN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[IF_END]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[RES:%.*]] = phi i8 [ [[MIN]], %[[ENTRY]] ], [ 0, %[[IF_THEN]] ]
+; CHECK-NEXT:    ret i8 [[RES]]
+;
+entry:
+  %or = or i16 %x, 1
+  %conv = trunc i16 %or to i8
+  %min = call noundef i8 @llvm.smin.i8(i8 %conv, i8 0)
+  %cond = icmp eq i16 %x, 0
+  br i1 %cond, label %if.end, label %if.then
+
+if.then:
+  br label %if.end
+
+if.end:
+  %res = phi i8 [ %min, %entry ], [ 0, %if.then ]
+  ret i8 %res
+}

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.

Do we also have to worry about this in other places that use isSafeToSpeculativelyExecuteWithVariableReplaced?

E.g. in replaceInInstruction -- though that one should drop the flags instead of bailing out.

It looks like the call in InstCombineCalls is ok because FoldOpIntoSelect already uses dropUBImplyingAttrsAndMetadata. (Which only handles return attributes, but that's a separate issue.)

if (!AllowRefinement) {
if (CI->hasRetAttr(Attribute::NoUndef) ||
CI->getRetDereferenceableBytes() > 0 ||
CI->getRetDereferenceableOrNullBytes() > 0 ||
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should have a has() query for dropUBImplyingAttrsAndMetadata similar to how we have hasPoisonGeneratingAnnotations and dropPoisonGeneratingAnnotations, so we can keep these things in sync.

///
/// If \p AllowRefinement is true, UB-implying attributes and metadata will be
/// ignored. The caller is responsible for correctly propagating them after
/// hoisting.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think AllowRefinement is really the right flag name for this. E.g. refinement to poison is fine.

I'd give this a more explicit name like IgnoreUBImplifyAttrs.

@nikic
Copy link
Contributor

nikic commented Apr 28, 2025

It's a shame that this introduces a case where more attributes results in less optimization. The high level alternative here would be to instead to do something like we do with DropFlags on simplifyWithOpReplaced, just for dropping UB-implying flags in this case. Let's see what llvm-opt-benchmark says, but probably the extra complexity of that approach is not worth it.

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

@dtcxzyw dtcxzyw merged commit 830cf36 into llvm:main Apr 30, 2025
11 checks passed
@dtcxzyw dtcxzyw deleted the fix-137582 branch April 30, 2025 03:53
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…afeToSpeculativelyExecute` (llvm#137604)

Closes llvm#137582.

In the original case, LVI uses the edge information in `%entry ->
%if.end` to get a more precise result. However, since the call to `smin`
has an `noundef` return attribute, an immediate UB will be triggered
after optimization.

Currently, `isSafeToSpeculativelyExecuteWithOpcode(%min)` returns true
because
llvm@6a288c1
only checks whether the function is speculatable. However, it is not
enough in this case.

This patch takes UB-implying attributes into account if
`IgnoreUBImplyingAttrs` is set to false. If it is set to true, the
caller is responsible for correctly propagating UB-implying attributes.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…afeToSpeculativelyExecute` (llvm#137604)

Closes llvm#137582.

In the original case, LVI uses the edge information in `%entry ->
%if.end` to get a more precise result. However, since the call to `smin`
has an `noundef` return attribute, an immediate UB will be triggered
after optimization.

Currently, `isSafeToSpeculativelyExecuteWithOpcode(%min)` returns true
because
llvm@6a288c1
only checks whether the function is speculatable. However, it is not
enough in this case.

This patch takes UB-implying attributes into account if
`IgnoreUBImplyingAttrs` is set to false. If it is set to true, the
caller is responsible for correctly propagating UB-implying attributes.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…afeToSpeculativelyExecute` (llvm#137604)

Closes llvm#137582.

In the original case, LVI uses the edge information in `%entry ->
%if.end` to get a more precise result. However, since the call to `smin`
has an `noundef` return attribute, an immediate UB will be triggered
after optimization.

Currently, `isSafeToSpeculativelyExecuteWithOpcode(%min)` returns true
because
llvm@6a288c1
only checks whether the function is speculatable. However, it is not
enough in this case.

This patch takes UB-implying attributes into account if
`IgnoreUBImplyingAttrs` is set to false. If it is set to true, the
caller is responsible for correctly propagating UB-implying attributes.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
…afeToSpeculativelyExecute` (llvm#137604)

Closes llvm#137582.

In the original case, LVI uses the edge information in `%entry ->
%if.end` to get a more precise result. However, since the call to `smin`
has an `noundef` return attribute, an immediate UB will be triggered
after optimization.

Currently, `isSafeToSpeculativelyExecuteWithOpcode(%min)` returns true
because
llvm@6a288c1
only checks whether the function is speculatable. However, it is not
enough in this case.

This patch takes UB-implying attributes into account if
`IgnoreUBImplyingAttrs` is set to false. If it is set to true, the
caller is responsible for correctly propagating UB-implying attributes.
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.

[CVP] Miscompilation at -O3
3 participants