-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[MergeFuncs] Don't introduce calls to (linkonce,weak)_odr functions. #125050
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-clang @llvm/pr-subscribers-llvm-transforms Author: Florian Hahn (fhahn) ChangesAvoid creating new calls to weak_odr functions when merging 2 functions. Consider 2 functions below, both present in 2 modules. Without this Note that the 2 optimizations are vaild in isolation, but the linker then There may be other linkage types we need to be more careful about as
Full diff: https://github.com/llvm/llvm-project/pull/125050.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index e8508416f54275..d991ec64445ee9 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -891,8 +891,8 @@ bool MergeFunctions::writeThunkOrAlias(Function *F, Function *G) {
// Merge two equivalent functions. Upon completion, Function G is deleted.
void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
- if (F->isInterposable()) {
- assert(G->isInterposable());
+ if (F->isInterposable() || G->hasWeakODRLinkage()) {
+ assert(G->isInterposable() || G->hasWeakODRLinkage());
// Both writeThunkOrAlias() calls below must succeed, either because we can
// create aliases for G and NewF, or because a thunk for F is profitable.
diff --git a/llvm/test/Transforms/MergeFunc/merge-weak-odr.ll b/llvm/test/Transforms/MergeFunc/merge-weak-odr.ll
new file mode 100644
index 00000000000000..d2e89092b9a826
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-weak-odr.ll
@@ -0,0 +1,112 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs --version 5
+; RUN: opt -p mergefunc -S %s | FileCheck %s
+
+define weak_odr hidden void @weak_odr_caller_of_foo_1(ptr %p) {
+entry:
+ tail call void @foo(ptr %p)
+ tail call void @foo(ptr %p)
+ tail call void @foo(ptr %p)
+ ret void
+}
+
+define weak_odr hidden void @weak_odr_caller_of_foo_2(ptr %p) {
+entry:
+ tail call void @foo(ptr %p)
+ tail call void @foo(ptr %p)
+ tail call void @foo(ptr %p)
+ ret void
+}
+
+declare void @foo(ptr)
+
+define hidden void @weak_odr_caller_of_bar_1(ptr %p) {
+entry:
+ tail call void @bar(ptr %p)
+ tail call void @bar(ptr %p)
+ tail call void @bar(ptr %p)
+ ret void
+}
+
+define weak_odr hidden void @non_weak_caller_of_bar_2(ptr %p) {
+entry:
+ tail call void @bar(ptr %p)
+ tail call void @bar(ptr %p)
+ tail call void @bar(ptr %p)
+ ret void
+}
+
+declare void @bar(ptr)
+
+define hidden void @non_weak_caller_of_zar_1(ptr %p) {
+entry:
+ tail call void @zar(ptr %p)
+ tail call void @zar(ptr %p)
+ tail call void @zar(ptr %p)
+ ret void
+}
+
+define weak_odr hidden void @weak_odr_caller_of_zar_2(ptr %p) {
+entry:
+ tail call void @zar(ptr %p)
+ tail call void @zar(ptr %p)
+ tail call void @zar(ptr %p)
+ ret void
+}
+
+declare void @zar(ptr)
+; CHECK-LABEL: define private void @0(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: tail call void @foo(ptr [[P]])
+; CHECK-NEXT: tail call void @foo(ptr [[P]])
+; CHECK-NEXT: tail call void @foo(ptr [[P]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define weak_odr hidden void @non_weak_caller_of_bar_2(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: tail call void @bar(ptr [[P]])
+; CHECK-NEXT: tail call void @bar(ptr [[P]])
+; CHECK-NEXT: tail call void @bar(ptr [[P]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define private void @1(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: tail call void @zar(ptr [[P]])
+; CHECK-NEXT: tail call void @zar(ptr [[P]])
+; CHECK-NEXT: tail call void @zar(ptr [[P]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define weak_odr hidden void @weak_odr_caller_of_foo_2(
+; CHECK-SAME: ptr [[TMP0:%.*]]) {
+; CHECK-NEXT: tail call void @[[GLOB0:[0-9]+]](ptr [[TMP0]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define weak_odr hidden void @weak_odr_caller_of_foo_1(
+; CHECK-SAME: ptr [[TMP0:%.*]]) {
+; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define hidden void @weak_odr_caller_of_bar_1(
+; CHECK-SAME: ptr [[TMP0:%.*]]) {
+; CHECK-NEXT: tail call void @non_weak_caller_of_bar_2(ptr [[TMP0]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define weak_odr hidden void @weak_odr_caller_of_zar_2(
+; CHECK-SAME: ptr [[TMP0:%.*]]) {
+; CHECK-NEXT: tail call void @[[GLOB1:[0-9]+]](ptr [[TMP0]])
+; CHECK-NEXT: ret void
+;
+;
+; CHECK-LABEL: define hidden void @non_weak_caller_of_zar_1(
+; CHECK-SAME: ptr [[TMP0:%.*]]) {
+; CHECK-NEXT: tail call void @[[GLOB1]](ptr [[TMP0]])
+; CHECK-NEXT: ret void
+;
|
43ff025
to
ad40253
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
; CHECK-NEXT: ret void | ||
; | ||
; | ||
; CHECK-LABEL: define hidden void @non_linkonce_caller_of_zar_1( |
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.
*caller_of_bar*
and *caller_of_zar*
have identical structures except for their order in the source, but their generated code differs. Can we ensure deterministic code generation regardless of their order?
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.
Yes that's why I added the test case, but forgot to flag it here
The scenario below should hopefully better illustrate what is causing the issue. It would be great to confirm if I am not missing anything that makes the scenario invalid. Module X
Module Y
Module X after function merging:
Module Y is unchanged. Then the linker picks @"A" from module Y and @"B" from module X. Now there's an infinite call cycle |
@@ -891,8 +891,10 @@ bool MergeFunctions::writeThunkOrAlias(Function *F, Function *G) { | |||
|
|||
// Merge two equivalent functions. Upon completion, Function G is deleted. | |||
void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) { | |||
if (F->isInterposable()) { | |||
assert(G->isInterposable()); | |||
if (F->isInterposable() || G->hasWeakODRLinkage() || |
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'm not familiar with this code - what is this guarding? And is it intended that you are checking F in the first check but G in the newly added checks?
Do we need to worry about AvailableExternallyLinkage? ThinLTO converts non-prevailing linkonce_odr and those won't stick around as out of line copies, so might be ok as I think the infinite loop you are describing can't happen in that case?
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.
If I read correctly, this is going to happen with or without LTO, and having the prevailing symbol info doesn't help here. Creating the infinite loop is one thing, but having one call the other (if either of them is interposable) will break the runtime since providing a runtime replacement will cause either no replacement or replacing one function will cause replace both functions. In this case, you can only use the thunk to merge them.
So I think we just just check here:
if (F->isInterposable() || G->isInterposable())
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.
The check here is to decide whether we cannot use F
instead of G
. If the condition is true, we don't use F
for G
but introduce new function with the implementation of F
that is called by both F
and G
.
If the condition is false, we update all callers of G
to use F
and update G
to call F
.
As @cachemeifyoucan the issue occurs without (Thin)LTO.
Unfortunately linkonce_odr
& weak_odr
aren't considered interposable (https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/GlobalValue.h#L426). linkonce_odr
and weak_odr
functions are guaranteed to be replaced by something semantically equivalent, but they could have different semantically equivalent implementations in modules, where merging can be done in one module but not the other.
I tried to provide a more detailed example here: #125050 (comment)
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 see, it is rely on ODR to assume if the replacement happens, it is functionally equivalent so it is safe.
Maybe it is safer to replace all references of isInterposable()
in this pass with the stricter definition here that includes ODR.
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.
Unfortunately replacing all reference of isInterposable
gives worse results, in particular, if we have a function F
that is not interposable and not linkonce/weak ODR and another function G
that is link once/weak ODR, then G
should be able to call F
.
I also updated the comparator to order ODR functions before non-ODR functions to consistently support the case above.
cbe754c
to
293f3ec
Compare
293f3ec
to
dad2026
Compare
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 found another case where we would introduce calls to linkonce_odr/weak_odr functions, if we merge 3+ linkonce_odr/weak_odr functions.
To fix this, I updated the order to always ensure that the ODR functions come first.
I also updated the code to replace all direct callers of ODR functions to call the thunk
dad2026
to
b1ce602
Compare
Avoid creating new calls to weak_odr functions when merging 2 functions. Consider 2 functions below, both present in 2 modules. Without this patch, MergeFuncs in the first module may optimize A to call B and in the second module B to call A. Note that the 2 optimizations are vaild in isolation, but the linker then could pick A from module 1 (which calls B) and B from module 2 which calls A, introducing an infinte call cycle. There may be other linkage types we need to be more careful about as well. define weak_odr hidden void @"A"(ptr %p) { entry: tail call void @"foo"(ptr %p) ret void } define weak_odr hidden void @"B"(ptr %p) { entry: tail call void @"foo"(ptr %p) ret void }
b1ce602
to
c077b08
Compare
…functions. (#125050) Avoid creating new calls to linkonce_odr/weak_odr functions when merging 2 functions, as this may introduce an infinite call cycle. Consider 2 functions below, both present in 2 modules. Module X -- define linkonce_odr void @"A"() { call void @"foo"() } define linkonce_odr void @"B"() { call void @"foo"() } --- Module Y --- global @"g" = @"B" define linkonce_odr void @"A"() { %l = load @"g" call void %l() } define linkonce_odr void @"B"() { call void @"foo"() } --- @"A" and @"B" in both modules are semantically equivalent Module X after function merging: --- define linkonce_odr void @"A"() { call void @"foo"() } define linkonce_odr void @"B"() { call void @"A"() } --- Module Y is unchanged. Then the linker picks @"A" from module Y and @"B" from module X. Now there's an infinite call cycle PR: llvm/llvm-project#125050
Tests for llvm#125050. (cherry picked from commit aa77085)
…lvm#125050) Avoid creating new calls to linkonce_odr/weak_odr functions when merging 2 functions, as this may introduce an infinite call cycle. Consider 2 functions below, both present in 2 modules. Module X -- define linkonce_odr void @"A"() { call void @"foo"() } define linkonce_odr void @"B"() { call void @"foo"() } --- Module Y --- global @"g" = @"B" define linkonce_odr void @"A"() { %l = load @"g" call void %l() } define linkonce_odr void @"B"() { call void @"foo"() } --- @"A" and @"B" in both modules are semantically equivalent Module X after function merging: --- define linkonce_odr void @"A"() { call void @"foo"() } define linkonce_odr void @"B"() { call void @"A"() } --- Module Y is unchanged. Then the linker picks @"A" from module Y and @"B" from module X. Now there's an infinite call cycle PR: llvm#125050 (cherry picked from commit f10e0f7)
Avoid creating new calls to linkonce_odr/weak_odr functions when
merging 2 functions.
Consider 2 functions below, both present in 2 modules. Without this
patch, MergeFuncs in the first module may optimize A to call B while in
another module B may call A via a function pointer, i.e. no merging is
happening.
Note that the 2 optimizations are vaild in isolation, but the linker then
could pick A from module 1 (which calls B) and B from module 2 which
calls A, introducing an infinte call cycle.
There may be other linkage types we need to be more careful about as
well.