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_public.ll b/lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll copy from lld/test/ELF/lto/devirt_vcall_vis_public.ll copy to lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll --- a/lld/test/ELF/lto/devirt_vcall_vis_public.ll +++ b/lld/test/ELF/lto/devirt_vcall_vis_export_dynamic.ll @@ -1,54 +1,99 @@ ; REQUIRES: x86 -; Test that --lto-whole-program-visibility enables devirtualization. +; Test that --export-dynamic[-symbol] and --dynamic-list prevents devirtualization. -; Note that the --export-dynamic used below is simply to ensure symbols are -; retained during linking. +; 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=. --export-dynamic 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: -mllvm -pass-remarks=. \ +; RUN: 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=. \ +; RUN: 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=. \ +; RUN: 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: 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: 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: ld.lld %t2.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck %s --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: ld.lld %t.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck %s --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: ld.lld %t4.o -o %t3 -save-temps --lto-whole-program-visibility \ +; RUN: -mllvm -pass-remarks=. \ +; RUN: --export-dynamic 2>&1 | FileCheck %s --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" @@ -61,6 +106,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) { @@ -76,6 +123,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) @@ -84,6 +132,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) @@ -98,6 +147,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/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,27 @@ ; 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=. \ +; RUN: 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=. \ +; RUN: 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=. \ +; RUN: 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 +32,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 %s --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 %s --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 %s --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 %s --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 +65,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. @@ -8,7 +10,7 @@ ; 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: 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ; Hybrid WPD @@ -19,7 +21,7 @@ ; 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: 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 @@ -29,17 +31,17 @@ ; 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: 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 \ @@ -48,6 +50,7 @@ ; 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 \ @@ -56,12 +59,76 @@ ; 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: 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" @@ -74,6 +141,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) { @@ -89,6 +158,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 +167,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 +182,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 @@ -8,7 +8,7 @@ ; 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: 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t2.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR ; Hybrid WPD @@ -19,7 +19,7 @@ ; 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: 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 @@ -29,7 +29,7 @@ ; 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: 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 +43,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 %s --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 +51,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 %s --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 +59,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 %s --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 +74,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 @@ -650,7 +650,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;