Skip to content

[MergeFuncs] Don't introduce calls to (linkonce,weak)_odr functions. #10112

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 3 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions clang/test/CodeGenCXX/merge-functions.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// REQUIRES: x86-registered-target
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O0 -fmerge-functions -emit-llvm -o - -x c++ < %s | FileCheck %s -implicit-check-not=_ZN1A1gEiPi
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O1 -fmerge-functions -emit-llvm -o - -x c++ < %s | FileCheck %s -implicit-check-not=_ZN1A1gEiPi
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O0 -fmerge-functions -emit-llvm -o - -x c++ < %s | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O1 -fmerge-functions -emit-llvm -o - -x c++ < %s | FileCheck %s

// Basic functionality test. Function merging doesn't kick in on functions that
// are too simple.
Expand All @@ -10,4 +10,8 @@ struct A {
virtual int g(int x, int *p) { return x ? *p : 1; }
} a;

// CHECK: define {{.*}} @_ZN1A1fEiPi
// CHECK: define linkonce_odr noundef i32 @_ZN1A1gEiPi(
// CHECK: tail call noundef i32 @0(

// CHECK: define linkonce_odr noundef i32 @_ZN1A1fEiPi(
// CHECK: tail call noundef i32 @0(
31 changes: 29 additions & 2 deletions llvm/lib/Transforms/IPO/MergeFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,10 +862,20 @@ bool MergeFunctions::writeThunkOrAlias(Function *F, Function *G) {
return false;
}

/// Returns true if \p F is either weak_odr or linkonce_odr.
static bool isODR(const Function *F) {
return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
}

// Merge two equivalent functions. Upon completion, Function G is deleted.
void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
if (F->isInterposable()) {
assert(G->isInterposable());

// Create a new thunk that both F and G can call, if F cannot call G directly.
// That is the case if F is either interposable or if G is either weak_odr or
// linkonce_odr.
if (F->isInterposable() || (isODR(F) && isODR(G))) {
assert((!isODR(G) || isODR(F)) &&
"if G is ODR, F must also be ODR due to ordering");

// Both writeThunkOrAlias() calls below must succeed, either because we can
// create aliases for G and NewF, or because a thunk for F is profitable.
Expand All @@ -879,13 +889,22 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
F->getAddressSpace(), "", F->getParent());
NewF->copyAttributesFrom(F);
NewF->takeName(F);
NewF->setComdat(F->getComdat());
F->setComdat(nullptr);
NewF->IsNewDbgInfoFormat = F->IsNewDbgInfoFormat;
// Ensure CFI type metadata is propagated to the new function.
copyMetadataIfPresent(F, NewF, "type");
copyMetadataIfPresent(F, NewF, "kcfi_type");
removeUsers(F);
F->replaceAllUsesWith(NewF);

// If G or NewF are (weak|linkonce)_odr, update all callers to call the
// thunk.
if (isODR(G))
replaceDirectCallers(G, F);
if (isODR(F))
replaceDirectCallers(NewF, F);

// We collect alignment before writeThunkOrAlias that overwrites NewF and
// G's content.
const MaybeAlign NewFAlign = NewF->getAlign();
Expand Down Expand Up @@ -959,16 +978,24 @@ void MergeFunctions::replaceFunctionInTree(const FunctionNode &FN,

// Ordering for functions that are equal under FunctionComparator
static bool isFuncOrderCorrect(const Function *F, const Function *G) {
if (isODR(F) != isODR(G)) {
// ODR functions before non-ODR functions. A ODR function can call a non-ODR
// function if it is not interposable, but not the other way around.
return isODR(G);
}

if (F->isInterposable() != G->isInterposable()) {
// Strong before weak, because the weak function may call the strong
// one, but not the other way around.
return !F->isInterposable();
}

if (F->hasLocalLinkage() != G->hasLocalLinkage()) {
// External before local, because we definitely have to keep the external
// function, but may be able to drop the local one.
return !F->hasLocalLinkage();
}

// Impose a total order (by name) on the replacement of functions. This is
// important when operating on more than one module independently to prevent
// cycles of thunks calling each other when the modules are linked together.
Expand Down
5 changes: 3 additions & 2 deletions llvm/test/Transforms/MergeFunc/comdat.ll
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ define linkonce_odr hidden i32 @g(i32 %x, i32 %y) comdat {
ret i32 %sum3
}

; CHECK-DAG: define linkonce_odr hidden i32 @f(i32 %x, i32 %y) comdat
; CHECK-DAG: define linkonce_odr hidden i32 @g(i32 %0, i32 %1) comdat
; CHECK-DAG: define private i32 @0(i32 %x, i32 %y) comdat($f)
; CHECK-DAG: define linkonce_odr hidden i32 @g(i32 %0, i32 %1) comdat {
; CHECK-DAG: define linkonce_odr hidden i32 @f(i32 %0, i32 %1) {

32 changes: 23 additions & 9 deletions llvm/test/Transforms/MergeFunc/linkonce_odr.ll
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 5
; RUN: opt -S -passes=mergefunc < %s | FileCheck %s -implicit-check-not=funC

; Replacments should be totally ordered on the function name.
; If we don't do this we can end up with one module defining a thunk for @funA
; and another module defining a thunk for @funB.
;
; The problem with this is that the linker could then choose these two stubs
; each of the two modules and we end up with two stubs calling each other.

; CHECK-LABEL: define linkonce_odr i32 @funA
; CHECK-NEXT: add
; CHECK: ret

; CHECK-LABEL: define linkonce_odr i32 @funB
; CHECK-NEXT: tail call i32 @funA(i32 %0, i32 %1)
; CHECK-NEXT: ret

define linkonce_odr i32 @funC(i32 %x, i32 %y) {
%sum = add i32 %x, %y
%sum2 = add i32 %x, %sum
Expand All @@ -40,3 +32,25 @@ define linkonce_odr i32 @funA(i32 %x, i32 %y) {
; @funC, however, can safely be deleted as it has no uses, and is discardable
; if unused.
@take_addr_of_funB = global ptr @funB
;.
; CHECK: @take_addr_of_funB = global ptr @funB
;.
; CHECK-LABEL: define private i32 @0(
; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[X]], [[Y]]
; CHECK-NEXT: [[SUM2:%.*]] = add i32 [[X]], [[SUM]]
; CHECK-NEXT: [[SUM3:%.*]] = add i32 [[X]], [[SUM2]]
; CHECK-NEXT: ret i32 [[SUM3]]
;
;
; CHECK-LABEL: define linkonce_odr i32 @funC(
; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]]) {
; CHECK-NEXT: [[TMP3:%.*]] = tail call i32 @[[GLOB0:[0-9]+]](i32 [[TMP0]], i32 [[TMP1]])
; CHECK-NEXT: ret i32 [[TMP3]]
;
;
; CHECK-LABEL: define linkonce_odr i32 @funB(
; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]]) {
; CHECK-NEXT: [[TMP3:%.*]] = tail call i32 @[[GLOB0]](i32 [[TMP0]], i32 [[TMP1]])
; CHECK-NEXT: ret i32 [[TMP3]]
;
75 changes: 75 additions & 0 deletions llvm/test/Transforms/MergeFunc/merge-linkonce-odr-used.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 5
; RUN: opt -p mergefunc -S %s | FileCheck %s

@llvm.used = appending global [3 x ptr] [ptr @linkonce_odr_caller_of_foo_1, ptr @linkonce_odr_caller_of_foo_2, ptr @linkonce_odr_caller_of_foo_3]

define void @caller_of_callers(ptr %p) {
call void @linkonce_odr_caller_of_foo_1(ptr %p)
call void @linkonce_odr_caller_of_foo_2(ptr %p)
call void @linkonce_odr_caller_of_foo_3(ptr %p)
ret void
}

define linkonce_odr void @linkonce_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 linkonce_odr void @linkonce_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
}

define linkonce_odr void @linkonce_odr_caller_of_foo_3(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)

;.
; CHECK: @llvm.used = appending global [3 x ptr] [ptr @linkonce_odr_caller_of_foo_1, ptr @linkonce_odr_caller_of_foo_2, ptr @linkonce_odr_caller_of_foo_3]
;.
; CHECK-LABEL: define void @caller_of_callers(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: call void @[[GLOB0:[0-9]+]](ptr [[P]])
; CHECK-NEXT: call void @[[GLOB0]](ptr [[P]])
; CHECK-NEXT: call void @[[GLOB0]](ptr [[P]])
; CHECK-NEXT: ret void
;
;
; 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 linkonce_odr void @linkonce_odr_caller_of_foo_2(
; CHECK-SAME: ptr [[TMP0:%.*]]) {
; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
; CHECK-NEXT: ret void
;
;
; CHECK-LABEL: define linkonce_odr void @linkonce_odr_caller_of_foo_1(
; CHECK-SAME: ptr [[TMP0:%.*]]) {
; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
; CHECK-NEXT: ret void
;
;
; CHECK-LABEL: define linkonce_odr void @linkonce_odr_caller_of_foo_3(
; CHECK-SAME: ptr [[TMP0:%.*]]) {
; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
; CHECK-NEXT: ret void
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 5
; RUN: opt -p mergefunc -S %s | FileCheck %s

@llvm.used = appending global [3 x ptr] [ptr @weak_odr_caller_of_foo_1, ptr @linkonce_odr_caller_of_foo_2, ptr @weak_odr_caller_of_foo_3]

define void @caller_of_callers(ptr %p) {
call void @weak_odr_caller_of_foo_1(ptr %p)
call void @linkonce_odr_caller_of_foo_2(ptr %p)
call void @weak_odr_caller_of_foo_3(ptr %p)
ret void
}

define weak_odr 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 linkonce_odr void @linkonce_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
}

define weak_odr void @weak_odr_caller_of_foo_3(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)

;.
; CHECK: @llvm.used = appending global [3 x ptr] [ptr @weak_odr_caller_of_foo_1, ptr @linkonce_odr_caller_of_foo_2, ptr @weak_odr_caller_of_foo_3]
;.
; CHECK-LABEL: define void @caller_of_callers(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: call void @[[GLOB0:[0-9]+]](ptr [[P]])
; CHECK-NEXT: call void @[[GLOB0]](ptr [[P]])
; CHECK-NEXT: call void @[[GLOB0]](ptr [[P]])
; CHECK-NEXT: ret void
;
;
; 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 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 linkonce_odr void @linkonce_odr_caller_of_foo_2(
; CHECK-SAME: ptr [[TMP0:%.*]]) {
; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
; CHECK-NEXT: ret void
;
;
; CHECK-LABEL: define weak_odr void @weak_odr_caller_of_foo_3(
; CHECK-SAME: ptr [[TMP0:%.*]]) {
; CHECK-NEXT: tail call void @[[GLOB0]](ptr [[TMP0]])
; CHECK-NEXT: ret void
;
Loading