-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[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
Conversation
@llvm/pr-subscribers-llvm-ir @llvm/pr-subscribers-llvm-transforms Author: Yingwei Zheng (dtcxzyw) ChangesCloses #137582. In the original case, LVI uses the edge information in Currently, This patch takes UB-implying attributes into account if Full diff: https://github.com/llvm/llvm-project/pull/137604.diff 4 Files Affected:
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
+}
|
There was a problem hiding this 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.)
llvm/lib/Analysis/ValueTracking.cpp
Outdated
if (!AllowRefinement) { | ||
if (CI->hasRetAttr(Attribute::NoUndef) || | ||
CI->getRetDereferenceableBytes() > 0 || | ||
CI->getRetDereferenceableOrNullBytes() > 0 || |
There was a problem hiding this comment.
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. |
There was a problem hiding this 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 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.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…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.
…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.
…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.
…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.
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 tosmin
has announdef
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.