diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -246,6 +246,11 @@ r.VisibleToRegularObj = config->relocatable || sym->isUsedInRegularObj || (r.Prevailing && sym->includeInDynsym()) || usedStartStop.count(objSym.getSectionName()); + // Identify symbols exported dynamically, and that therefore could be + // referenced by a shared library not visible to the linker. + r.VisibleToDynamicLinker = + sym->isExportDynamic(sym->kind(), sym->visibility) || + sym->inDynamicList; const auto *dr = dyn_cast(sym); r.FinalDefinitionInLinkageUnit = (isExec || sym->visibility != STV_DEFAULT) && dr && diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -221,13 +221,13 @@ // non-lazy object causes a runtime error. void fetch() const; -private: static bool isExportDynamic(Kind k, uint8_t visibility) { if (k == SharedKind) return visibility == llvm::ELF::STV_DEFAULT; return config->shared || config->exportDynamic; } +private: void resolveUndefined(const Undefined &other); void resolveCommon(const CommonSymbol &other); void resolveDefined(const Defined &other); diff --git a/lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll b/lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll @@ -0,0 +1,182 @@ +; REQUIRES: x86 +;; Test that --export-dynamic[-symbol] and --dynamic-list prevents devirtualization. + +;; First check that we get devirtualization without any export dynamic options. + +;; Index based WPD +;; Generate unsplit module with summary for ThinLTO index-based WPD. +; RUN: opt --thinlto-bc -o %t2.o %s +; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR + +;; Hybrid WPD +;; Generate split module with summary for hybrid Thin/Regular LTO WPD. +; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t.o %s +; RUN: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR + +;; Regular LTO WPD +; RUN: opt -o %t4.o %s +; RUN: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR + +; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi +; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi + +;; Check that all WPD fails with --export-dynamic. + +;; Index based WPD +; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR + +;; Hybrid WPD +; RUN: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR + +;; Regular LTO WPD +; RUN: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR + +;; Check that WPD fails for target _ZN1D1mEi with --export-dynamic-symbol=_ZTV1D. + +;; Index based WPD +; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Hybrid WPD +; RUN: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Regular LTO WPD +; RUN: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +; REMARK-AONLY-NOT: single-impl: +; REMARK-AONLY: single-impl: devirtualized a call to _ZN1A1nEi +; REMARK-AONLY-NOT: single-impl: + +;; Check that WPD fails for target _ZN1D1mEi with _ZTV1D in --dynamic-list. +; RUN: echo "{ _ZTV1D; };" > %t.list + +;; Index based WPD +; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Hybrid WPD +; RUN: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Regular LTO WPD +; RUN: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +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-grtev4-linux-gnu" + +%struct.A = type { i32 (...)** } +%struct.B = type { %struct.A } +%struct.C = type { %struct.A } +%struct.D = type { i32 (...)** } + +@_ZTV1B = linkonce_odr unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !1, !vcall_visibility !5 +@_ZTV1C = linkonce_odr unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2, !vcall_visibility !5 +@_ZTV1D = linkonce_odr unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3, !vcall_visibility !5 + +;; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [3 x i8*] [ i8* bitcast ( { [4 x i8*] }* @_ZTV1B to i8*), i8* bitcast ( { [4 x i8*] }* @_ZTV1C to i8*), i8* bitcast ( { [3 x i8*] }* @_ZTV1D to i8*)] + +; CHECK-IR-LABEL: define dso_local i32 @_start +define i32 @_start(%struct.A* %obj, %struct.D* %obj2, i32 %a) { +entry: + %0 = bitcast %struct.A* %obj to i8*** + %vtable = load i8**, i8*** %0 + %1 = bitcast i8** %vtable to i8* + %p = call i1 @llvm.type.test(i8* %1, metadata !"_ZTS1A") + call void @llvm.assume(i1 %p) + %fptrptr = getelementptr i8*, i8** %vtable, i32 1 + %2 = bitcast i8** %fptrptr to i32 (%struct.A*, i32)** + %fptr1 = load i32 (%struct.A*, i32)*, i32 (%struct.A*, i32)** %2, align 8 + + ;; Check that the call was devirtualized. + ; CHECK-IR: %call = tail call i32 @_ZN1A1nEi + ; CHECK-AONLY-IR: %call = tail call i32 @_ZN1A1nEi + ; CHECK-NODEVIRT-IR: %call = tail call i32 %fptr1 + %call = tail call i32 %fptr1(%struct.A* nonnull %obj, i32 %a) + + %3 = bitcast i8** %vtable to i32 (%struct.A*, i32)** + %fptr22 = load i32 (%struct.A*, i32)*, i32 (%struct.A*, i32)** %3, align 8 + + ;; We still have to call it as virtual. + ; CHECK-IR: %call3 = tail call i32 %fptr22 + ; CHECK-AONLY-IR: %call3 = tail call i32 %fptr22 + ; CHECK-NODEVIRT-IR: %call3 = tail call i32 %fptr22 + %call3 = tail call i32 %fptr22(%struct.A* nonnull %obj, i32 %call) + + %4 = bitcast %struct.D* %obj2 to i8*** + %vtable2 = load i8**, i8*** %4 + %5 = bitcast i8** %vtable2 to i8* + %p2 = call i1 @llvm.type.test(i8* %5, metadata !4) + call void @llvm.assume(i1 %p2) + + %6 = bitcast i8** %vtable2 to i32 (%struct.D*, i32)** + %fptr33 = load i32 (%struct.D*, i32)*, i32 (%struct.D*, i32)** %6, align 8 + + ;; Check that the call was devirtualized. + ; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi + ; CHECK-AONLY-IR: %call4 = tail call i32 %fptr33 + ; CHECK-NODEVIRT-IR: %call4 = tail call i32 %fptr33 + %call4 = tail call i32 %fptr33(%struct.D* nonnull %obj2, i32 %call3) + ret i32 %call4 +} +; CHECK-IR-LABEL: ret i32 +; CHECK-IR-LABEL: } + +declare i1 @llvm.type.test(i8*, metadata) +declare void @llvm.assume(i1) + +define i32 @_ZN1B1fEi(%struct.B* %this, i32 %a) #0 { + ret i32 0; +} + +define i32 @_ZN1A1nEi(%struct.A* %this, i32 %a) #0 { + ret i32 0; +} + +define i32 @_ZN1C1fEi(%struct.C* %this, i32 %a) #0 { + ret i32 0; +} + +define i32 @_ZN1D1mEi(%struct.D* %this, i32 %a) #0 { + ret i32 0; +} + +;; Make sure we don't inline or otherwise optimize out the direct calls. +attributes #0 = { noinline optnone } + +!0 = !{i64 16, !"_ZTS1A"} +!1 = !{i64 16, !"_ZTS1B"} +!2 = !{i64 16, !"_ZTS1C"} +!3 = !{i64 16, !4} +!4 = distinct !{} +!5 = !{i64 0} diff --git a/lld/test/ELF/lto/devirt_vcall_vis_public.ll b/lld/test/ELF/lto/devirt_vcall_vis_public.ll --- a/lld/test/ELF/lto/devirt_vcall_vis_public.ll +++ b/lld/test/ELF/lto/devirt_vcall_vis_public.ll @@ -1,27 +1,24 @@ ; REQUIRES: x86 ;; Test that --lto-whole-program-visibility enables devirtualization. -;; Note that the --export-dynamic used below is simply to ensure symbols are -;; retained during linking. - ;; Index based WPD ;; Generate unsplit module with summary for ThinLTO index-based WPD. ; RUN: opt --thinlto-bc -o %t2.o %s ; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Hybrid WPD ;; Generate split module with summary for hybrid Thin/Regular LTO WPD. ; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t.o %s ; RUN: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Regular LTO WPD ; RUN: opt -o %t4.o %s ; RUN: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi @@ -32,21 +29,25 @@ ;; Index based WPD ; RUN: ld.lld %t2.o -o %t3 -save-temps \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: -mllvm -pass-remarks=. \ +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Ensure --no-lto-whole-program-visibility overrides explicit --lto-whole-program-visibility. ; RUN: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility --no-lto-whole-program-visibility \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: -mllvm -pass-remarks=. \ +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Hybrid WPD ; RUN: ld.lld %t.o -o %t3 -save-temps \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: -mllvm -pass-remarks=. \ +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Regular LTO WPD ; RUN: ld.lld %t4.o -o %t3 -save-temps \ -; RUN: -mllvm -pass-remarks=. --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: -mllvm -pass-remarks=. \ +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" @@ -61,6 +62,8 @@ @_ZTV1C = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2, !vcall_visibility !5 @_ZTV1D = constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3, !vcall_visibility !5 +; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [3 x i8*] [ i8* bitcast ( { [4 x i8*] }* @_ZTV1B to i8*), i8* bitcast ( { [4 x i8*] }* @_ZTV1C to i8*), i8* bitcast ( { [3 x i8*] }* @_ZTV1D to i8*)] ; CHECK-IR-LABEL: define dso_local i32 @_start define i32 @_start(%struct.A* %obj, %struct.D* %obj2, i32 %a) { diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h --- a/llvm/include/llvm/LTO/LTO.h +++ b/llvm/include/llvm/LTO/LTO.h @@ -364,6 +364,10 @@ /// summary). bool VisibleOutsideSummary = false; + /// The symbol was exported dynamically, and therefore could be referenced + /// by a shared library not visible to the linker. + bool VisibleToDynamicLinker = false; + bool UnnamedAddr = true; /// True if module contains the prevailing definition. @@ -434,6 +438,10 @@ // Use Optional to distinguish false from not yet initialized. Optional EnableSplitLTOUnit; + + // Identify symbols exported dynamically, and that therefore could be + // referenced by a shared library not visible to the linker. + DenseSet DynamicExportSymbols; }; /// The resolution for a symbol. The linker must provide a SymbolResolution for @@ -441,7 +449,7 @@ struct SymbolResolution { SymbolResolution() : Prevailing(0), FinalDefinitionInLinkageUnit(0), VisibleToRegularObj(0), - LinkerRedefined(0) {} + VisibleToDynamicLinker(0), LinkerRedefined(0) {} /// The linker has chosen this definition of the symbol. unsigned Prevailing : 1; @@ -453,6 +461,10 @@ /// The definition of this symbol is visible outside of the LTO unit. unsigned VisibleToRegularObj : 1; + /// The symbol was exported dynamically, and therefore could be referenced + /// by a shared library not visible to the linker. + unsigned VisibleToDynamicLinker : 1; + /// Linker redefined version of the symbol which appeared in -wrap or -defsym /// linker option. unsigned LinkerRedefined : 1; diff --git a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h --- a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h +++ b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h @@ -239,10 +239,12 @@ uint64_t ByteOffset; }; -void updateVCallVisibilityInModule(Module &M, - bool WholeProgramVisibilityEnabledInLTO); -void updateVCallVisibilityInIndex(ModuleSummaryIndex &Index, - bool WholeProgramVisibilityEnabledInLTO); +void updateVCallVisibilityInModule( + Module &M, bool WholeProgramVisibilityEnabledInLTO, + const DenseSet &DynamicExportSymbols); +void updateVCallVisibilityInIndex( + ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO, + const DenseSet &DynamicExportSymbols); /// Perform index-based whole program devirtualization on the \p Summary /// index. Any devirtualized targets used by a type test in another module diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -553,6 +553,8 @@ // from a module that does not have a summary. GlobalRes.VisibleOutsideSummary |= (Res.VisibleToRegularObj || Sym.isUsed() || !InSummary); + + GlobalRes.VisibleToDynamicLinker |= Res.VisibleToDynamicLinker; } } @@ -950,6 +952,9 @@ if (Res.second.VisibleOutsideSummary && Res.second.Prevailing) GUIDPreservedSymbols.insert(GUID); + if (Res.second.VisibleToDynamicLinker) + DynamicExportSymbols.insert(GUID); + GUIDPrevailingResolutions[GUID] = Res.second.Prevailing ? PrevailingType::Yes : PrevailingType::No; } @@ -1034,7 +1039,8 @@ // If allowed, upgrade public vcall visibility metadata to linkage unit // visibility before whole program devirtualization in the optimizer. updateVCallVisibilityInModule(*RegularLTO.CombinedModule, - Conf.HasWholeProgramVisibility); + Conf.HasWholeProgramVisibility, + DynamicExportSymbols); if (Conf.PreOptModuleHook && !Conf.PreOptModuleHook(0, *RegularLTO.CombinedModule)) @@ -1382,7 +1388,8 @@ // If allowed, upgrade public vcall visibility to linkage unit visibility in // the summaries before whole program devirtualization below. updateVCallVisibilityInIndex(ThinLTO.CombinedIndex, - Conf.HasWholeProgramVisibility); + Conf.HasWholeProgramVisibility, + DynamicExportSymbols); // Perform index-based WPD. This will return immediately if there are // no index entries in the typeIdMetadata map (e.g. if we are instead diff --git a/llvm/lib/LTO/LTOCodeGenerator.cpp b/llvm/lib/LTO/LTOCodeGenerator.cpp --- a/llvm/lib/LTO/LTOCodeGenerator.cpp +++ b/llvm/lib/LTO/LTOCodeGenerator.cpp @@ -562,7 +562,10 @@ // via the internal option. Must be done before WPD invoked via the optimizer // pipeline run below. updateVCallVisibilityInModule(*MergedModule, - /* WholeProgramVisibilityEnabledInLTO */ false); + /* WholeProgramVisibilityEnabledInLTO */ false, + // FIXME: This needs linker information via a + // TBD new interface. + /* DynamicExportSymbols */ {}); // We always run the verifier once on the merged module, the `DisableVerify` // parameter only applies to subsequent verify. diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp --- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -1007,7 +1007,10 @@ // linker option in the old LTO API, but this call allows it to be specified // via the internal option. Must be done before WPD below. updateVCallVisibilityInIndex(*Index, - /* WholeProgramVisibilityEnabledInLTO */ false); + /* WholeProgramVisibilityEnabledInLTO */ false, + // FIXME: This needs linker information via a + // TBD new interface. + /* DynamicExportSymbols */ {}); // Perform index-based WPD. This will return immediately if there are // no index entries in the typeIdMetadata map (e.g. if we are instead diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp --- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp +++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp @@ -777,8 +777,9 @@ /// If whole program visibility asserted, then upgrade all public vcall /// visibility metadata on vtable definitions to linkage unit visibility in /// Module IR (for regular or hybrid LTO). -void updateVCallVisibilityInModule(Module &M, - bool WholeProgramVisibilityEnabledInLTO) { +void updateVCallVisibilityInModule( + Module &M, bool WholeProgramVisibilityEnabledInLTO, + const DenseSet &DynamicExportSymbols) { if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO)) return; for (GlobalVariable &GV : M.globals()) @@ -786,22 +787,29 @@ // the vtable definitions. We won't have an existing vcall_visibility // metadata on vtable definitions with public visibility. if (GV.hasMetadata(LLVMContext::MD_type) && - GV.getVCallVisibility() == GlobalObject::VCallVisibilityPublic) + GV.getVCallVisibility() == GlobalObject::VCallVisibilityPublic && + // Don't upgrade the visibility for symbols exported to the dynamic + // linker, as we have no information on their eventual use. + !DynamicExportSymbols.count(GV.getGUID())) GV.setVCallVisibilityMetadata(GlobalObject::VCallVisibilityLinkageUnit); } /// If whole program visibility asserted, then upgrade all public vcall /// visibility metadata on vtable definition summaries to linkage unit /// visibility in Module summary index (for ThinLTO). -void updateVCallVisibilityInIndex(ModuleSummaryIndex &Index, - bool WholeProgramVisibilityEnabledInLTO) { +void updateVCallVisibilityInIndex( + ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO, + const DenseSet &DynamicExportSymbols) { if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO)) return; for (auto &P : Index) { for (auto &S : P.second.SummaryList) { auto *GVar = dyn_cast(S.get()); if (!GVar || GVar->vTableFuncs().empty() || - GVar->getVCallVisibility() != GlobalObject::VCallVisibilityPublic) + GVar->getVCallVisibility() != GlobalObject::VCallVisibilityPublic || + // Don't upgrade the visibility for symbols exported to the dynamic + // linker, as we have no information on their eventual use. + DynamicExportSymbols.count(P.first)) continue; GVar->setVCallVisibility(GlobalObject::VCallVisibilityLinkageUnit); } diff --git a/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll b/llvm/test/tools/gold/X86/devirt_vcall_vis_export_dynamic.ll copy from llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll copy to llvm/test/tools/gold/X86/devirt_vcall_vis_export_dynamic.ll --- a/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll +++ b/llvm/test/tools/gold/X86/devirt_vcall_vis_export_dynamic.ll @@ -1,4 +1,6 @@ -;; Test that plugin option whole-program-visibility enables devirtualization. +;; Test that --export-dynamic[-symbol] and --dynamic-list prevents devirtualization. + +;; First check that we get devirtualization without any export dynamic options. ;; Index based WPD ;; Generate unsplit module with summary for ThinLTO index-based WPD. @@ -7,8 +9,7 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t2.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t2.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Hybrid WPD @@ -18,8 +19,7 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Regular LTO WPD @@ -28,40 +28,104 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t4.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t4.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi ; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi -;; Try everything again but without -whole-program-visibility to confirm -;; WPD fails +;; Check that all WPD fails with --export-dynamic. ;; Index based WPD ; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t2.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Hybrid WPD ; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Regular LTO WPD ; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t4.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: --export-dynamic 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR +;; Check that WPD fails for target _ZN1D1mEi with --export-dynamic-symbol=_ZTV1D. + +;; Index based WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t2.o -o %t3 \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Hybrid WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t.o -o %t3 \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Regular LTO WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t4.o -o %t3 \ +; RUN: --export-dynamic-symbol=_ZTV1D 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +; REMARK-AONLY-NOT: single-impl: +; REMARK-AONLY: single-impl: devirtualized a call to _ZN1A1nEi +; REMARK-AONLY-NOT: single-impl: + +;; Check that WPD fails for target _ZN1D1mEi with _ZTV1D in --dynamic-list. +; RUN: echo "{ _ZTV1D; };" > %t.list + +;; Index based WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t2.o -o %t3 \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Hybrid WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t.o -o %t3 \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + +;; Regular LTO WPD +; RUN: %gold -m elf_x86_64 -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: --plugin-opt=whole-program-visibility \ +; RUN: --plugin-opt=save-temps \ +; RUN: --plugin-opt=-pass-remarks=. \ +; RUN: %t4.o -o %t3 \ +; RUN: --dynamic-list=%t.list 2>&1 | FileCheck %s --check-prefix=REMARK-AONLY +; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-AONLY-IR + 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-grtev4-linux-gnu" @@ -70,10 +134,12 @@ %struct.C = type { %struct.A } %struct.D = type { i32 (...)** } -@_ZTV1B = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !1, !vcall_visibility !5 -@_ZTV1C = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2, !vcall_visibility !5 -@_ZTV1D = constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3, !vcall_visibility !5 +@_ZTV1B = linkonce_odr unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !1, !vcall_visibility !5 +@_ZTV1C = linkonce_odr unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2, !vcall_visibility !5 +@_ZTV1D = linkonce_odr unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3, !vcall_visibility !5 +;; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [3 x i8*] [ i8* bitcast ( { [4 x i8*] }* @_ZTV1B to i8*), i8* bitcast ( { [4 x i8*] }* @_ZTV1C to i8*), i8* bitcast ( { [3 x i8*] }* @_ZTV1D to i8*)] ; CHECK-IR-LABEL: define dso_local i32 @_start define i32 @_start(%struct.A* %obj, %struct.D* %obj2, i32 %a) { @@ -89,6 +155,7 @@ ;; Check that the call was devirtualized. ; CHECK-IR: %call = tail call i32 @_ZN1A1nEi + ; CHECK-AONLY-IR: %call = tail call i32 @_ZN1A1nEi ; CHECK-NODEVIRT-IR: %call = tail call i32 %fptr1 %call = tail call i32 %fptr1(%struct.A* nonnull %obj, i32 %a) @@ -97,6 +164,7 @@ ;; We still have to call it as virtual. ; CHECK-IR: %call3 = tail call i32 %fptr22 + ; CHECK-AONLY-IR: %call3 = tail call i32 %fptr22 ; CHECK-NODEVIRT-IR: %call3 = tail call i32 %fptr22 %call3 = tail call i32 %fptr22(%struct.A* nonnull %obj, i32 %call) @@ -111,6 +179,7 @@ ;; Check that the call was devirtualized. ; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi + ; CHECK-AONLY-IR: %call4 = tail call i32 %fptr33 ; CHECK-NODEVIRT-IR: %call4 = tail call i32 %fptr33 %call4 = tail call i32 %fptr33(%struct.D* nonnull %obj2, i32 %call3) ret i32 %call4 diff --git a/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll b/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll --- a/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll +++ b/llvm/test/tools/gold/X86/devirt_vcall_vis_public.ll @@ -7,8 +7,7 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t2.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t2.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Hybrid WPD @@ -18,8 +17,7 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ;; Regular LTO WPD @@ -28,8 +26,7 @@ ; RUN: --plugin-opt=whole-program-visibility \ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ -; RUN: %t4.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: %t4.o -o %t3 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi @@ -43,7 +40,7 @@ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t2.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Hybrid WPD @@ -51,7 +48,7 @@ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR ;; Regular LTO WPD @@ -59,7 +56,7 @@ ; RUN: --plugin-opt=save-temps \ ; RUN: --plugin-opt=-pass-remarks=. \ ; RUN: %t4.o -o %t3 \ -; RUN: --export-dynamic 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty +; RUN: 2>&1 | FileCheck /dev/null --implicit-check-not single-impl --allow-empty ; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" @@ -74,6 +71,8 @@ @_ZTV1C = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2, !vcall_visibility !5 @_ZTV1D = constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3, !vcall_visibility !5 +; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [3 x i8*] [ i8* bitcast ( { [4 x i8*] }* @_ZTV1B to i8*), i8* bitcast ( { [4 x i8*] }* @_ZTV1C to i8*), i8* bitcast ( { [3 x i8*] }* @_ZTV1D to i8*)] ; CHECK-IR-LABEL: define dso_local i32 @_start define i32 @_start(%struct.A* %obj, %struct.D* %obj2, i32 %a) { diff --git a/llvm/tools/gold/gold-plugin.cpp b/llvm/tools/gold/gold-plugin.cpp --- a/llvm/tools/gold/gold-plugin.cpp +++ b/llvm/tools/gold/gold-plugin.cpp @@ -765,6 +765,9 @@ case LDPR_PREVAILING_DEF_IRONLY_EXP: R.Prevailing = !isUndefined(Sym); + // Identify symbols exported dynamically, and that therefore could be + // referenced by a shared library not visible to the linker. + R.VisibleToDynamicLinker = true; if (!Res.CanOmitFromDynSym) R.VisibleToRegularObj = true; break; diff --git a/llvm/tools/opt/opt.cpp b/llvm/tools/opt/opt.cpp --- a/llvm/tools/opt/opt.cpp +++ b/llvm/tools/opt/opt.cpp @@ -660,7 +660,8 @@ // specified by an internal option. This is normally done during LTO which is // not performed via opt. updateVCallVisibilityInModule(*M, - /* WholeProgramVisibilityEnabledInLTO */ false); + /* WholeProgramVisibilityEnabledInLTO */ false, + /* DynamicExportSymbols */ {}); // Figure out what stream we are supposed to write to... std::unique_ptr Out;