-
Notifications
You must be signed in to change notification settings - Fork 12k
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
[Inliner] Fix bug where attributes are propagated incorrectly #109347
[Inliner] Fix bug where attributes are propagated incorrectly #109347
Conversation
The bug stems from the fact that we assume the new (inlined) callsite is calling the same function as the original (callee) callsite. While this is typically the case, since `VMap` simplifies the new instructions, callee intrinsics callsites can end up not corresponding with the same function. This can lead to buggy propagation.
@llvm/pr-subscribers-llvm-transforms Author: None (goldsteinn) Changes
The bug stems from the fact that we assume the new (inlined) callsite This can lead to buggy propagation. Full diff: https://github.com/llvm/llvm-project/pull/109347.diff 3 Files Affected:
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 52da65ce01b82b..53e25e53c2a86f 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -1383,6 +1383,12 @@ static void AddParamAndFnBasicAttributes(const CallBase &CB,
auto *NewInnerCB = dyn_cast_or_null<CallBase>(VMap.lookup(InnerCB));
if (!NewInnerCB)
continue;
+ // The InnerCB might have be simplified during the inlining
+ // process. Only propagate return attributes if we are in fact calling the
+ // same function.
+ if (InnerCB->getCalledFunction() != NewInnerCB->getCalledFunction())
+ continue;
+
AttributeList AL = NewInnerCB->getAttributes();
for (unsigned I = 0, E = InnerCB->arg_size(); I < E; ++I) {
// Check if the underlying value for the parameter is an argument.
@@ -1477,6 +1483,12 @@ static void AddReturnAttributes(CallBase &CB, ValueToValueMapTy &VMap) {
auto *NewRetVal = dyn_cast_or_null<CallBase>(VMap.lookup(RetVal));
if (!NewRetVal)
continue;
+
+ // The RetVal might have be simplified during the inlining
+ // process. Only propagate return attributes if we are in fact calling the
+ // same function.
+ if (RetVal->getCalledFunction() != NewRetVal->getCalledFunction())
+ continue;
// Backward propagation of attributes to the returned value may be incorrect
// if it is control flow dependent.
// Consider:
diff --git a/llvm/test/Transforms/Inline/access-attributes-prop.ll b/llvm/test/Transforms/Inline/access-attributes-prop.ll
index 965f0335b63eab..2c55f5f3b1f6ca 100644
--- a/llvm/test/Transforms/Inline/access-attributes-prop.ll
+++ b/llvm/test/Transforms/Inline/access-attributes-prop.ll
@@ -559,3 +559,24 @@ define void @prop_byval_readonly(ptr %p) {
call void @foo_byval_readonly(ptr %p)
ret void
}
+
+define ptr @caller_bad_param_prop(ptr %p1, ptr %p2, i64 %x) {
+; CHECK-LABEL: define {{[^@]+}}@caller_bad_param_prop
+; CHECK-SAME: (ptr [[P1:%.*]], ptr [[P2:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr [[P1]](i64 [[X]], ptr [[P2]])
+; CHECK-NEXT: ret ptr [[TMP1]]
+;
+ %1 = call ptr %p1(i64 %x, ptr %p2)
+ %2 = call ptr @callee_bad_param_prop(ptr %1)
+ ret ptr %2
+}
+
+define ptr @callee_bad_param_prop(ptr readonly %x) {
+; CHECK-LABEL: define {{[^@]+}}@callee_bad_param_prop
+; CHECK-SAME: (ptr readonly [[X:%.*]]) {
+; CHECK-NEXT: [[R:%.*]] = tail call ptr @llvm.ptrmask.p0.i64(ptr [[X]], i64 -1)
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %r = tail call ptr @llvm.ptrmask(ptr %x, i64 -1)
+ ret ptr %r
+}
diff --git a/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll b/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll
index dc685d2c4d1368..930bef43df1dba 100644
--- a/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll
+++ b/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll
@@ -421,3 +421,43 @@ define i8 @caller16_not_intersecting_ranges() {
call void @use.val(i8 %r)
ret i8 %r
}
+
+
+define ptr @caller_bad_ret_prop(ptr %p1, ptr %p2, i64 %x, ptr %other) {
+; CHECK-LABEL: define ptr @caller_bad_ret_prop
+; CHECK-SAME: (ptr [[P1:%.*]], ptr [[P2:%.*]], i64 [[X:%.*]], ptr [[OTHER:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call noundef ptr [[P1]](i64 [[X]], ptr [[P2]])
+; CHECK-NEXT: [[CMP_I:%.*]] = icmp eq ptr [[TMP1]], null
+; CHECK-NEXT: br i1 [[CMP_I]], label [[T_I:%.*]], label [[F_I:%.*]]
+; CHECK: T.i:
+; CHECK-NEXT: br label [[CALLEE_BAD_RET_PROP_EXIT:%.*]]
+; CHECK: F.i:
+; CHECK-NEXT: br label [[CALLEE_BAD_RET_PROP_EXIT]]
+; CHECK: callee_bad_ret_prop.exit:
+; CHECK-NEXT: [[TMP2:%.*]] = phi ptr [ [[OTHER]], [[T_I]] ], [ [[TMP1]], [[F_I]] ]
+; CHECK-NEXT: ret ptr [[TMP2]]
+;
+ %1 = call noundef ptr %p1(i64 %x, ptr %p2)
+ %2 = call nonnull ptr @callee_bad_ret_prop(ptr %1, ptr %other)
+ ret ptr %2
+}
+
+define ptr @callee_bad_ret_prop(ptr %x, ptr %other) {
+; CHECK-LABEL: define ptr @callee_bad_ret_prop
+; CHECK-SAME: (ptr [[X:%.*]], ptr [[OTHER:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[X]], null
+; CHECK-NEXT: br i1 [[CMP]], label [[T:%.*]], label [[F:%.*]]
+; CHECK: T:
+; CHECK-NEXT: ret ptr [[OTHER]]
+; CHECK: F:
+; CHECK-NEXT: [[R:%.*]] = tail call ptr @llvm.ptrmask.p0.i64(ptr [[X]], i64 -1)
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %cmp = icmp eq ptr %x, null
+ br i1 %cmp, label %T, label %F
+T:
+ ret ptr %other
+F:
+ %r = tail call ptr @llvm.ptrmask(ptr %x, i64 -1)
+ ret ptr %r
+}
|
NB: this (or any other fix we settle on) will need to be backported to release/19. |
// The RetVal might have be simplified during the inlining | ||
// process. Only propagate return attributes if we are in fact calling the | ||
// same function. | ||
if (RetVal->getCalledFunction() != NewRetVal->getCalledFunction()) |
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.
This doesn't seem like it will reliably detect the issue.
First off, if both calls are indirect, both calls to getCalledFunction() would return nullptr, in which case you aren't getting any useful information at all.
Second, even if both calls are calling the "same" function, it might not have the same behavior if the arguments are different. Or global state is different. Not sure how likely this is given the limited simplifications the inline performs, but it's hard to rule out.
So I think we need to avoid using VMap, and pass down the equivalence information some other way.
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.
This doesn't seem like it will reliably detect the issue.
First off, if both calls are indirect, both calls to getCalledFunction() would return nullptr, in which case you aren't getting any useful information at all.
Fair, I think its fine to just disable all prop for indirect functions.
Second, even if both calls are calling the "same" function, it might not have the same behavior if the arguments are different. Or global state is different. Not sure how likely this is given the limited simplifications the inline performs, but it's hard to rule out.
So I think we need to avoid using VMap, and pass down the equivalence information some other way.
Is that really a valid concern? We don't allow arbitrary transforms on the new instructions. Within the confines of what we do do, I don't see how we could change global state or produce and argument that is not semantically equiv to what was in the original.
That being said, we could also move the propagation code to the function cloning logic before simplifications/etc.
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.
Suppose you inline a function that contains two calls to foo(). The second call gets simplified away, and the simplified value is the first call to foo(). So the vmap lookup finds the first call to foo(), which has completely different arguments/state.
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.
You are worried about something like:
declare ptr @foo(ptr) memory(none) willreturn nounwind
define ptr @caller(ptr %p, i1 %c) {
%r = call ptr @callee(ptr %p, i1 %c)
ret ptr %r
}
define ptr @callee(ptr %p, i1 %c) {
%x = call ptr @foo(ptr %p)
br i1 %c, label %T, label %F
T:
ret ptr %x
F:
%r2 = call nonnull ptr @foo(ptr %p)
ret ptr %r2
}
?
Where we might prop the nonnull
from the second call to the first call if it got simplified?
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 guess the other options are either move this to before simplifications in the cloneFunction
call or track which callbases get simplified and skip them.
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.
Or we could just disable the simplifyInstruction on callbases
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.
Personally I'm for just not using simplifyInstruction on callbases in CloneFunction in inliner. Don't see much value in it in a realistic pipeline and unless I'm missing something, I think it solves the bug.
But would like to hear if anyone else has an opinion on how this should be solved.
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.
This problem sounds familiar :) The right way to handle this is to query InlinedFunctionInfo.isSimplified()
.
That is much more convenient :) |
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
/cherry-pick a9352a0 |
Failed to cherry-pick: a9352a0 https://github.com/llvm/llvm-project/actions/runs/10968306432 Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request |
/cherry-pick a9352a0 |
Failed to cherry-pick: a9352a0 https://github.com/llvm/llvm-project/actions/runs/10968564268 Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request |
…09347) - **[Inliner] Add tests for incorrect propagation of return attrs; NFC** - **[Inliner] Fix bug where attributes are propagated incorrectly** The bug stems from the fact that we assume the new (inlined) callsite is calling the same function as the original (callee) callsite. While this is typically the case, since `VMap` simplifies the new instructions, callee intrinsics callsites can end up not corresponding with the same function. This can lead to buggy propagation. (cherry picked from commit a9352a0)
…09347) - **[Inliner] Add tests for incorrect propagation of return attrs; NFC** - **[Inliner] Fix bug where attributes are propagated incorrectly** The bug stems from the fact that we assume the new (inlined) callsite is calling the same function as the original (callee) callsite. While this is typically the case, since `VMap` simplifies the new instructions, callee intrinsics callsites can end up not corresponding with the same function. This can lead to buggy propagation. (cherry picked from commit a9352a0)
…09347) - **[Inliner] Add tests for incorrect propagation of return attrs; NFC** - **[Inliner] Fix bug where attributes are propagated incorrectly** The bug stems from the fact that we assume the new (inlined) callsite is calling the same function as the original (callee) callsite. While this is typically the case, since `VMap` simplifies the new instructions, callee intrinsics callsites can end up not corresponding with the same function. This can lead to buggy propagation.
…09347) - **[Inliner] Add tests for incorrect propagation of return attrs; NFC** - **[Inliner] Fix bug where attributes are propagated incorrectly** The bug stems from the fact that we assume the new (inlined) callsite is calling the same function as the original (callee) callsite. While this is typically the case, since `VMap` simplifies the new instructions, callee intrinsics callsites can end up not corresponding with the same function. This can lead to buggy propagation.
The bug stems from the fact that we assume the new (inlined) callsite
is calling the same function as the original (callee) callsite. While
this is typically the case, since
VMap
simplifies the newinstructions, callee intrinsics callsites can end up not corresponding
with the same function.
This can lead to buggy propagation.