From 12050a3fb7344694cfd7527d4cca0033729bcfc5 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Wed, 16 Nov 2022 22:13:22 -0800 Subject: [PATCH] [LTO] Make local linkage GlobalValue in non-prevailing COMDAT available_externally For a local linkage GlobalObject in a non-prevailing COMDAT, it remains defined while its leader has been made available_externally. This violates the COMDAT rule that its members must be retained or discarded as a unit. To fix this, update the regular LTO change D34803 to track local linkage GlobalValues, and port the code to ThinLTO (GlobalAliases are not handled.) This fixes two problems. (a) `__cxx_global_var_init` in a non-prevailing COMDAT group used to linger around (unreferenced, hence benign), and is now correctly discarded. ``` int foo(); inline int v = foo(); ``` (b) Fix https://github.com/llvm/llvm-project/issues/58215: as a size optimization, we place private `__profd_` in a COMDAT with a `__profc_` key. When FuncImport.cpp makes `__profc_` available_externally due to a non-prevailing COMDAT, `__profd_` incorrectly remains private. This change makes the `__profd_` available_externally. ``` cat > c.h <<'eof' extern void bar(); inline __attribute__((noinline)) void foo() {} eof cat > m1.cc <<'eof' #include "c.h" int main() { bar(); foo(); } eof cat > m2.cc <<'eof' #include "c.h" __attribute__((noinline)) void bar() { foo(); } eof clang -O2 -fprofile-generate=./t m1.cc m2.cc -flto -fuse-ld=lld -o t_gen rm -fr t && ./t_gen && llvm-profdata show -function=foo t/default_*.profraw clang -O2 -fprofile-generate=./t m1.cc m2.cc -flto=thin -fuse-ld=lld -o t_gen rm -fr t && ./t_gen && llvm-profdata show -function=foo t/default_*.profraw ``` If a GlobalAlias references a GlobalValue which is just changed to available_externally, change the GlobalAlias as well (e.g. C5/D5 comdats due to cc1 -mconstructor-aliases). The GlobalAlias may be referenced by other available_externally functions, so it cannot easily be removed. Depends on D137441: we use available_externally to mark a GlobalAlias in a non-prevailing COMDAT, similar to how we handle GlobalVariable/Function. GlobalAlias may refer to a ConstantExpr, not changing GlobalAlias to GlobalVariable gives flexibility for future extensions (the use case is niche. For simplicity we don't handle it yet). In addition, available_externally GlobalAlias is the most straightforward implementation and retains the aliasee information to help optimizers. See windows-vftable.ll: Windows vftable uses an alias pointing to a private constant where the alias is the COMDAT leader. The COMDAT use case is skeptical and ThinLTO does not discard the alias in the non-prevailing COMDAT. This patch retains the behavior. See new tests ctor-dtor-alias2.ll: depending on whether the complete object destructor emitted, when ctor/dtor aliases are used, we may see D0/D2 COMDATs in one TU and D0/D1/D2 in a D5 COMDAT in another TU. Allow such a mix-and-match with `if (GO->getComdat()->getName() == GO->getName()) NonPrevailingComdats.insert(GO->getComdat());` GlobalAlias handling in ThinLTO is still weird, but this patch should hopefully improve the situation for at least all cases I can think of. Reviewed By: tejohnson Differential Revision: https://reviews.llvm.org/D135427 --- lld/test/ELF/lto/ctor-dtor-alias2.ll | 77 +++++++++++++++++++ llvm/lib/LTO/LTO.cpp | 10 +-- llvm/lib/Transforms/IPO/FunctionImport.cpp | 36 ++++++++- llvm/test/LTO/Resolution/X86/comdat-mixed-lto.ll | 19 ++++- .../X86/Inputs/linkonce_resolution_comdat.ll | 17 ++++- llvm/test/ThinLTO/X86/ctor-dtor-alias.ll | 44 +++++++++++ llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll | 88 ++++++++++++++++++++++ .../test/ThinLTO/X86/linkonce_resolution_comdat.ll | 41 +++++++--- llvm/test/ThinLTO/X86/windows-vftable.ll | 35 +++++++++ 9 files changed, 345 insertions(+), 22 deletions(-) create mode 100644 lld/test/ELF/lto/ctor-dtor-alias2.ll create mode 100644 llvm/test/ThinLTO/X86/ctor-dtor-alias.ll create mode 100644 llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll create mode 100644 llvm/test/ThinLTO/X86/windows-vftable.ll diff --git a/lld/test/ELF/lto/ctor-dtor-alias2.ll b/lld/test/ELF/lto/ctor-dtor-alias2.ll new file mode 100644 index 0000000..fda2e95 --- /dev/null +++ b/lld/test/ELF/lto/ctor-dtor-alias2.ll @@ -0,0 +1,77 @@ +; REQUIRES: x86 +;; Test mixed D0/D2 and D5 COMDATs. The file matches llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll + +; RUN: rm -rf %t && split-file %s %t && cd %t + +;; a.bc defines D0 in comdat D0 and D2 in comdat D2. b.bc defines D0/D1/D2 in comdat D5. +; RUN: opt -module-summary a.ll -o a.bc +; RUN: opt -module-summary b.ll -o b.bc +; RUN: ld.lld -shared a.bc b.bc -o out.so +; RUN: llvm-nm -D out.so + +;; Although D0/D2 in b.bc is non-prevailing, keep D1/D2 as definitions, otherwise +;; the output may have an undefined and unsatisfied D1. +; CHECK: W _ZN1AIiED0Ev +; CHECK-NEXT: W _ZN1AIiED1Ev +; CHECK-NEXT: W _ZN1AIiED2Ev +; CHECK-NEXT: U _ZdlPv +; CHECK-NEXT: T aa +; CHECK-NEXT: T bb + +;--- a.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$_ZN1AIiED2Ev = comdat any + +$_ZN1AIiED0Ev = comdat any + +define void @aa() { +entry: + %a = alloca ptr, align 8 + call void @_ZN1AIiED2Ev(ptr noundef nonnull %a) + ret void +} + +define linkonce_odr void @_ZN1AIiED2Ev(ptr noundef nonnull %this) unnamed_addr comdat { + ret void +} + +define linkonce_odr void @_ZN1AIiED0Ev(ptr noundef nonnull %this) unnamed_addr comdat { +entry: + call void @_ZN1AIiED2Ev(ptr noundef nonnull %this) + call void @_ZdlPv(ptr noundef %this) + ret void +} + +declare void @_ZdlPv(ptr noundef) + +;--- b.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$_ZN1AIiED5Ev = comdat any + +$_ZTV1AIiE = comdat any + +@_ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev + +define weak_odr void @_ZN1AIiED2Ev(ptr noundef nonnull %this) unnamed_addr comdat($_ZN1AIiED5Ev) { + ret void +} + +define weak_odr void @_ZN1AIiED0Ev(ptr noundef nonnull %this) unnamed_addr comdat($_ZN1AIiED5Ev) { +entry: + call void @_ZN1AIiED1Ev(ptr noundef nonnull %this) + call void @_ZdlPv(ptr noundef %this) + ret void +} + +declare void @_ZdlPv(ptr noundef) + +define void @bb(ptr noundef %a) { +entry: + call void @_ZN1AIiED1Ev(ptr noundef nonnull %a) + call void @_ZdlPv(ptr noundef %a) + ret void +} diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp index d3f29a6..9bfbabc 100644 --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -712,11 +712,11 @@ handleNonPrevailingComdat(GlobalValue &GV, if (!NonPrevailingComdats.count(C)) return; - // Additionally need to drop externally visible global values from the comdat - // to available_externally, so that there aren't multiply defined linker - // errors. - if (!GV.hasLocalLinkage()) - GV.setLinkage(GlobalValue::AvailableExternallyLinkage); + // Additionally need to drop all global values from the comdat to + // available_externally, to satisfy the COMDAT requirement that all members + // are discarded as a unit. The non-local linkage global values avoid + // duplicate definition linker errors. + GV.setLinkage(GlobalValue::AvailableExternallyLinkage); if (auto GO = dyn_cast(&GV)) GO->setComdat(nullptr); diff --git a/llvm/lib/Transforms/IPO/FunctionImport.cpp b/llvm/lib/Transforms/IPO/FunctionImport.cpp index b589ec7..7c99465 100644 --- a/llvm/lib/Transforms/IPO/FunctionImport.cpp +++ b/llvm/lib/Transforms/IPO/FunctionImport.cpp @@ -1051,6 +1051,7 @@ bool llvm::convertToDeclaration(GlobalValue &GV) { void llvm::thinLTOFinalizeInModule(Module &TheModule, const GVSummaryMapTy &DefinedGlobals, bool PropagateAttrs) { + DenseSet NonPrevailingComdats; auto FinalizeInModule = [&](GlobalValue &GV, bool Propagate = false) { // See if the global summary analysis computed a new resolved linkage. const auto &GS = DefinedGlobals.find(GV.getGUID()); @@ -1128,8 +1129,11 @@ void llvm::thinLTOFinalizeInModule(Module &TheModule, // as this is a declaration for the linker, and will be dropped eventually. // It is illegal for comdats to contain declarations. auto *GO = dyn_cast_or_null(&GV); - if (GO && GO->isDeclarationForLinker() && GO->hasComdat()) + if (GO && GO->isDeclarationForLinker() && GO->hasComdat()) { + if (GO->getComdat()->getName() == GO->getName()) + NonPrevailingComdats.insert(GO->getComdat()); GO->setComdat(nullptr); + } }; // Process functions and global now @@ -1139,6 +1143,36 @@ void llvm::thinLTOFinalizeInModule(Module &TheModule, FinalizeInModule(GV); for (auto &GV : TheModule.aliases()) FinalizeInModule(GV); + + // For a non-prevailing comdat, all its members must be available_externally. + // FinalizeInModule has handled non-local-linkage GlobalValues. Here we handle + // local linkage GlobalValues. + if (NonPrevailingComdats.empty()) + return; + for (auto &GO : TheModule.global_objects()) { + if (auto *C = GO.getComdat(); C && NonPrevailingComdats.count(C)) { + GO.setComdat(nullptr); + GO.setLinkage(GlobalValue::AvailableExternallyLinkage); + } + } + bool Changed; + do { + Changed = false; + // If an alias references a GlobalValue in a non-prevailing comdat, change + // it to available_externally. For simplicity we only handle GlobalValue and + // ConstantExpr with a base object. ConstantExpr without a base object is + // unlikely used in a COMDAT. + for (auto &GA : TheModule.aliases()) { + if (GA.hasAvailableExternallyLinkage()) + continue; + GlobalObject *Obj = GA.getAliaseeObject(); + assert(Obj && "aliasee without an base object is unimplemented"); + if (Obj->hasAvailableExternallyLinkage()) { + GA.setLinkage(GlobalValue::AvailableExternallyLinkage); + Changed = true; + } + } + } while (Changed); } /// Run internalization on \p TheModule based on symmary analysis. diff --git a/llvm/test/LTO/Resolution/X86/comdat-mixed-lto.ll b/llvm/test/LTO/Resolution/X86/comdat-mixed-lto.ll index d3730f4..96d8f31 100644 --- a/llvm/test/LTO/Resolution/X86/comdat-mixed-lto.ll +++ b/llvm/test/LTO/Resolution/X86/comdat-mixed-lto.ll @@ -8,7 +8,7 @@ ; The copy of C from this module is prevailing. The copy of C from the ; regular LTO module is not prevailing, and will be dropped to ; available_externally. -; RUN: llvm-lto2 run -r=%t1.o,C,pl -r=%t2.o,C,l -r=%t2.o,testglobfunc,lxp -r=%t1.o,testglobfunc,lx -o %t3 %t1.o %t2.o -save-temps +; RUN: llvm-lto2 run -r=%t1.o,C,pl -r=%t2.o,C,l -r=%t1.o,testglobfunc,lxp -r=%t2.o,testglobfunc,lx -o %t3 %t1.o %t2.o -save-temps ; The Input module (regular LTO) is %t3.0. Check to make sure that we removed ; __cxx_global_var_init and testglobfunc from comdat. Also check to ensure @@ -16,8 +16,21 @@ ; have linker multiply defined errors as it is no longer in a comdat and ; would clash with the copy from this module. ; RUN: llvm-dis %t3.0.0.preopt.bc -o - | FileCheck %s -; CHECK: define internal void @__cxx_global_var_init() section ".text.startup" { -; CHECK: define available_externally dso_local void @testglobfunc() section ".text.startup" { + +; CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @C }] +; CHECK: @C = available_externally dso_local global %"class.Test::ptr" zeroinitializer, align 4 +; CHECK-NOT: declare +; CHECK: declare dso_local void @__cxx_global_var_init() section ".text.startup" +; CHECK-NOT: declare + +; Check the behavior with the prevailing testglobfunc in %t2.o. +; RUN: llvm-lto2 run -r=%t1.o,C,pl -r=%t2.o,C,l -r=%t1.o,testglobfunc,lx -r=%t2.o,testglobfunc,plx -o %t4 %t1.o %t2.o -save-temps +; RUN: llvm-dis %t4.0.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK2 + +; CHECK2: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @C }] +; CHECK2: @C = available_externally dso_local global %"class.Test::ptr" zeroinitializer, align 4 +; CHECK2: declare dso_local void @__cxx_global_var_init() section ".text.startup" +; CHECK2: define available_externally dso_local void @testglobfunc() section ".text.startup" { ; ModuleID = 'comdat-mixed-lto.o' source_filename = "comdat-mixed-lto.cpp" diff --git a/llvm/test/ThinLTO/X86/Inputs/linkonce_resolution_comdat.ll b/llvm/test/ThinLTO/X86/Inputs/linkonce_resolution_comdat.ll index 92b5182..f5b3130 100644 --- a/llvm/test/ThinLTO/X86/Inputs/linkonce_resolution_comdat.ll +++ b/llvm/test/ThinLTO/X86/Inputs/linkonce_resolution_comdat.ll @@ -1,13 +1,24 @@ target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" -$c2 = comdat any +$f = comdat any +$g = comdat any -define linkonce_odr i32 @f(i8*) unnamed_addr comdat($c2) { +@g_private = private global i32 41, comdat($g) + +define linkonce_odr i32 @f(i8*) unnamed_addr comdat($f) { + ret i32 41 +} + +define linkonce_odr i32 @g() unnamed_addr comdat($g) { ret i32 41 } -define i32 @g() { +define internal void @g_internal() unnamed_addr comdat($g) { + ret void +} + +define i32 @h() { %i = call i32 @f(i8* null) ret i32 %i } diff --git a/llvm/test/ThinLTO/X86/ctor-dtor-alias.ll b/llvm/test/ThinLTO/X86/ctor-dtor-alias.ll new file mode 100644 index 0000000..736555a --- /dev/null +++ b/llvm/test/ThinLTO/X86/ctor-dtor-alias.ll @@ -0,0 +1,44 @@ +;; The constructor alias example is reduced from +;; +;; template +;; struct A { A() {} virtual ~A() {} }; +;; template struct A; +;; void *foo() { return new A; } +;; +;; clang -c -fpic -O1 -flto=thin a.cc && cp a.o b.o && ld.lld -shared a.o b.so + +; RUN: opt -module-summary %s -o %t1.bc +; RUN: cp %t1.bc %t2.bc +; RUN: llvm-lto2 run %t1.bc %t2.bc -r=%t1.bc,_ZTV1A,pl -r=%t1.bc,_ZN1AD0Ev,pl -r=%t1.bc,_ZN1AD1Ev,pl -r=%t1.bc,_ZN1AD2Ev,pl -r=%t1.bc,D1_a,pl -r=%t1.bc,D1_a_a,pl \ +; RUN: -r=%t2.bc,_ZTV1A,l -r=%t2.bc,_ZN1AD0Ev,l -r=%t2.bc,_ZN1AD1Ev,l -r=%t2.bc,_ZN1AD2Ev,l -r=%t2.bc,D1_a,l -r=%t2.bc,D1_a_a,l -o %t3 --save-temps +; RUN: llvm-dis < %t3.2.1.promote.bc | FileCheck %s + +; CHECK: @_ZTV1A = available_externally dso_local unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1AD1Ev, ptr @_ZN1AD0Ev] } +; CHECK: @D1_a = weak_odr dso_local unnamed_addr alias void (ptr), ptr @_ZN1AD1Ev +; CHECK: @_ZN1AD1Ev = weak_odr dso_local unnamed_addr alias void (ptr), ptr @_ZN1AD2Ev +; CHECK: @D1_a_a = weak_odr dso_local unnamed_addr alias void (ptr), ptr @D1_a +; CHECK: define weak_odr dso_local void @_ZN1AD2Ev(ptr noundef nonnull %0) unnamed_addr comdat($_ZN1AD5Ev) { +; CHECK: define available_externally dso_local void @_ZN1AD0Ev(ptr noundef nonnull %0) unnamed_addr { + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$_ZN1AD5Ev = comdat any +$_ZTV1A = comdat any + +@_ZTV1A = weak_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1AD1Ev, ptr @_ZN1AD0Ev] }, comdat + +@D1_a = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AD1Ev +@_ZN1AD1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AD2Ev +@D1_a_a = weak_odr unnamed_addr alias void (ptr), ptr @D1_a + +define weak_odr void @_ZN1AD2Ev(ptr noundef nonnull %0) unnamed_addr comdat($_ZN1AD5Ev) { + ret void +} + +define weak_odr void @_ZN1AD0Ev(ptr noundef nonnull %0) unnamed_addr comdat($_ZN1AD5Ev) { + call void @D1_a(ptr noundef nonnull %0) + call void @D1_a_a(ptr noundef nonnull %0) + call void @_ZN1AD1Ev(ptr noundef nonnull %0) + ret void +} diff --git a/llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll b/llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll new file mode 100644 index 0000000..c52e509 --- /dev/null +++ b/llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll @@ -0,0 +1,88 @@ +;; Test mixed D0/D2 and D5 COMDATs. Reduced from: +;; +;; // a.cc +;; template +;; struct A final { virtual ~A() {} }; +;; extern "C" void aa() { A a; } +;; // b.cc +;; template +;; struct A final { virtual ~A() {} }; +;; template struct A; +;; extern "C" void bb(A *a) { delete a; } +;; +;; clang -c -fpic -O0 -flto=thin a.cc && ld.lld -shared a.o b.o +;; +;; The file matches lld/test/ELF/lto/ctor-dtor-alias2.ll + +; RUN: rm -rf %t && split-file %s %t && cd %t + +;; a.bc defines D0 in comdat D0 and D2 in comdat D2. b.bc defines D0/D1/D2 in comdat D5. +; RUN: opt -module-summary a.ll -o a.bc +; RUN: opt -module-summary b.ll -o b.bc +; RUN: llvm-lto2 run a.bc b.bc -r=a.bc,aa,px -r=a.bc,_ZN1AIiED0Ev,px -r=a.bc,_ZN1AIiED2Ev,px -r=a.bc,_ZdlPv, \ +; RUN: -r=b.bc,bb,px -r=b.bc,_ZN1AIiED0Ev, -r=b.bc,_ZN1AIiED1Ev,px -r=b.bc,_ZN1AIiED2Ev, -r=b.bc,_ZdlPv, -o out --save-temps +; RUN: llvm-dis < out.2.1.promote.bc | FileCheck %s + +;; Although D0/D2 in b.bc is non-prevailing, keep D1/D2 as definitions, otherwise +;; the output may have an undefined and unsatisfied D1. +; CHECK: @_ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev +; CHECK: define weak_odr void @_ZN1AIiED2Ev(ptr noundef nonnull %this) unnamed_addr comdat($_ZN1AIiED5Ev) { +; CHECK: define available_externally void @_ZN1AIiED0Ev(ptr noundef nonnull %this) unnamed_addr { + +;--- a.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$_ZN1AIiED2Ev = comdat any + +$_ZN1AIiED0Ev = comdat any + +define void @aa() { +entry: + %a = alloca ptr, align 8 + call void @_ZN1AIiED2Ev(ptr noundef nonnull %a) + ret void +} + +define linkonce_odr void @_ZN1AIiED2Ev(ptr noundef nonnull %this) unnamed_addr comdat { + ret void +} + +define linkonce_odr void @_ZN1AIiED0Ev(ptr noundef nonnull %this) unnamed_addr comdat { +entry: + call void @_ZN1AIiED2Ev(ptr noundef nonnull %this) + call void @_ZdlPv(ptr noundef %this) + ret void +} + +declare void @_ZdlPv(ptr noundef) + +;--- b.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$_ZN1AIiED5Ev = comdat any + +$_ZTV1AIiE = comdat any + +@_ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev + +define weak_odr void @_ZN1AIiED2Ev(ptr noundef nonnull %this) unnamed_addr comdat($_ZN1AIiED5Ev) { + ret void +} + +define weak_odr void @_ZN1AIiED0Ev(ptr noundef nonnull %this) unnamed_addr comdat($_ZN1AIiED5Ev) { +entry: + call void @_ZN1AIiED1Ev(ptr noundef nonnull %this) + call void @_ZdlPv(ptr noundef %this) + ret void +} + +declare void @_ZdlPv(ptr noundef) + +define void @bb(ptr noundef %a) { +entry: + call void @_ZN1AIiED1Ev(ptr noundef nonnull %a) + call void @_ZdlPv(ptr noundef %a) + ret void +} diff --git a/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll b/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll index 7b22180..2fb2260 100644 --- a/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll +++ b/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll @@ -1,33 +1,54 @@ -; This test ensures that we drop the preempted copy of @f from %t2.bc from its -; comdat after making it available_externally. If not we would get a -; verification error. +; This test ensures that we drop the preempted copy of @f/@g from %t2.bc from their +; comdats after making it available_externally. If not we would get a +; verification error. g_internal/g_private are changed to available_externally +; as well since it is in the same comdat of g. ; RUN: opt -module-summary %s -o %t1.bc ; RUN: opt -module-summary %p/Inputs/linkonce_resolution_comdat.ll -o %t2.bc -; RUN: llvm-lto -thinlto-action=run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc -exported-symbol=f -exported-symbol=g -thinlto-save-temps=%t3. +; RUN: llvm-lto -thinlto-action=run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc -exported-symbol=f -exported-symbol=g -exported-symbol=h -thinlto-save-temps=%t3. ; RUN: llvm-dis %t3.0.3.imported.bc -o - | FileCheck %s --check-prefix=IMPORT1 ; RUN: llvm-dis %t3.1.3.imported.bc -o - | FileCheck %s --check-prefix=IMPORT2 ; Copy from first module is prevailing and converted to weak_odr, copy ; from second module is preempted and converted to available_externally and ; removed from comdat. -; IMPORT1: define weak_odr i32 @f(i8* %0) unnamed_addr [[ATTR:#[0-9]+]] comdat($c1) { +; IMPORT1: @g_private = private global i32 43, comdat($g) +; IMPORT1: define weak_odr i32 @f(i8* %0) unnamed_addr [[ATTR:#[0-9]+]] comdat { +; IMPORT1: define weak_odr i32 @g() unnamed_addr [[ATTR]] comdat { +; IMPORT1: define internal void @g_internal() unnamed_addr comdat($g) { + +; IMPORT2: @g_private = available_externally dso_local global i32 41{{$}} ; IMPORT2: define available_externally i32 @f(i8* %0) unnamed_addr [[ATTR:#[0-9]+]] { +; IMPORT2: define available_externally i32 @g() unnamed_addr [[ATTR]] { +; IMPORT2: define available_externally dso_local void @g_internal() unnamed_addr { ; CHECK-DAG: attributes [[ATTR]] = { norecurse nounwind } -; RUN: llvm-nm -o - < %t1.bc.thinlto.o | FileCheck %s --check-prefix=NM1 +; RUN: llvm-nm %t1.bc.thinlto.o | FileCheck %s --check-prefix=NM1 ; NM1: W f +; NM1: W g -; RUN: llvm-nm -o - < %t2.bc.thinlto.o | FileCheck %s --check-prefix=NM2 +; RUN: llvm-nm %t2.bc.thinlto.o | FileCheck %s --check-prefix=NM2 ; f() would have been turned into available_externally since it is preempted, -; and inlined into g() +; and inlined into h() ; NM2-NOT: f +; NM2-NOT: g target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" -$c1 = comdat any +$f = comdat any +$g = comdat any + +@g_private = private global i32 43, comdat($g) -define linkonce_odr i32 @f(i8*) unnamed_addr comdat($c1) { +define linkonce_odr i32 @f(i8*) unnamed_addr comdat { ret i32 43 } + +define linkonce_odr i32 @g() unnamed_addr comdat { + ret i32 43 +} + +define internal void @g_internal() unnamed_addr comdat($g) { + ret void +} diff --git a/llvm/test/ThinLTO/X86/windows-vftable.ll b/llvm/test/ThinLTO/X86/windows-vftable.ll new file mode 100644 index 0000000..c38c10f --- /dev/null +++ b/llvm/test/ThinLTO/X86/windows-vftable.ll @@ -0,0 +1,35 @@ +;; Test an alias pointing to a GEP. +; RUN: opt -module-summary %s -o %t1.bc +; RUN: cp %t1.bc %t2.bc +; RUN: llvm-lto2 run %t1.bc %t2.bc -r=%t1.bc,"??_7bad_array_new_length@stdext@@6B@",pl -r=%t1.bc,"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z",pl \ +; RUN: -r=%t1.bc,"?_Throw_bad_array_new_length@std@@YAXXZ",pl \ +; RUN: -r=%t2.bc,"??_7bad_array_new_length@stdext@@6B@", -r=%t2.bc,"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z", \ +; RUN: -r=%t2.bc,"?_Throw_bad_array_new_length@std@@YAXXZ", -o %t3 --save-temps +; RUN: llvm-dis < %t3.2.1.promote.bc | FileCheck %s + +; CHECK: @anon = private unnamed_addr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr @"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z"] }, comdat($"??_7bad_array_new_length@stdext@@6B@") +; CHECK: @"??_7bad_array_new_length@stdext@@6B@" = unnamed_addr alias ptr, getelementptr inbounds ({ [4 x ptr] }, ptr @anon, i32 0, i32 0, i32 1){{$}} +; CHECK: define available_externally dso_local noundef ptr @"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z"(ptr noundef nonnull %this) { +; CHECK: define available_externally dso_local void @"?_Throw_bad_array_new_length@std@@YAXXZ"(ptr noundef nonnull %0) unnamed_addr { + +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-windows-msvc19.26.0" + +$"??_7bad_array_new_length@stdext@@6B@" = comdat largest +$"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z" = comdat any +$"?_Throw_bad_array_new_length@std@@YAXXZ" = comdat any + +@anon = private unnamed_addr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr @"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z"] }, comdat($"??_7bad_array_new_length@stdext@@6B@") + +@"??_7bad_array_new_length@stdext@@6B@" = unnamed_addr alias ptr, getelementptr inbounds ({ [4 x ptr] }, ptr @anon, i32 0, i32 0, i32 1) + +define linkonce_odr dso_local noundef ptr @"??_Gbad_array_new_length@stdext@@UEAAPEAXI@Z"(ptr noundef nonnull %this) comdat { +entry: + ret ptr %this +} + +define linkonce_odr dso_local void @"?_Throw_bad_array_new_length@std@@YAXXZ"(ptr noundef nonnull %0) unnamed_addr comdat { +entry: + store ptr @"??_7bad_array_new_length@stdext@@6B@", ptr %0, align 8 + ret void +} -- 2.7.4