[llvm] [ThinLTO][WPD] Suppress WPD on a class if any superclass is visible in a shared library for index-based and hybrid WPD (PR #131721)
Mingming Liu via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 18 11:53:14 PDT 2025
https://github.com/mingmingl-llvm updated https://github.com/llvm/llvm-project/pull/131721
>From 40f913c404c6335ac3d68229e9c630cf7a72f3a4 Mon Sep 17 00:00:00 2001
From: mingmingl <mingmingl at google.com>
Date: Mon, 17 Mar 2025 20:27:38 -0700
Subject: [PATCH 1/2] Propagate ExportDynamic from base type to compatible
derived types in WPD.
---
llvm/lib/LTO/LTO.cpp | 2 +-
.../ThinLTO/X86/nodevirt_exort_dynamic.ll | 111 ++++++++++++++++++
llvm/tools/llvm-lto2/llvm-lto2.cpp | 21 ++++
3 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index e895a46b8cd77..6ec2dc381a932 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1905,7 +1905,7 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache,
auto IsVisibleToRegularObj = [&](StringRef name) {
auto It = GlobalResolutions->find(name);
return (It == GlobalResolutions->end() ||
- It->second.VisibleOutsideSummary);
+ It->second.VisibleOutsideSummary || It->second.ExportDynamic);
};
getVisibleToRegularObjVtableGUIDs(ThinLTO.CombinedIndex,
diff --git a/llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll b/llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll
new file mode 100644
index 0000000000000..ea33291a75161
--- /dev/null
+++ b/llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll
@@ -0,0 +1,111 @@
+; RUN: rm -rf %t && mkdir %t && cd %t
+
+; Generate unsplit module with summary for ThinLTO index-based WPD.
+; RUN: opt -thinlto-bc -o summary.o %s
+
+; RUN: llvm-dis -o - summary.o
+
+;; TODO: Implement the fix for WPD in regular or hybrid LTO, and add test coverage.
+
+; Index based WPD
+; For `_ZTI7Derived`, the 'llvm-lto2' resolution arguments specifies `VisibleOutsideSummary` as false
+; and `ExportDynamic` as false. The callsite inside @_ZN4Base8dispatchEv
+; got devirtualized.
+; RUN: llvm-lto2 run summary.o -save-temps -pass-remarks=. \
+; RUN: -o tmp \
+; RUN: --whole-program-visibility-enabled-in-lto=true \
+; RUN: --validate-all-vtables-have-type-infos=true \
+; RUN: --all-vtables-have-type-infos=true \
+; RUN: -r=summary.o,__cxa_pure_virtual, \
+; RUN: -r=summary.o,_ZN8DerivedNC2Ev,x \
+; RUN: -r=summary.o,_ZN4Base8dispatchEv,px \
+; RUN: -r=summary.o,_ZN7DerivedC2Ev, \
+; RUN: -r=summary.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=summary.o,_ZTS4Base, \
+; RUN: -r=summary.o,_ZTV8DerivedN,p \
+; RUN: -r=summary.o,_ZTI8DerivedN,p \
+; RUN: -r=summary.o,_ZTI4Base, \
+; RUN: -r=summary.o,_ZTS8DerivedN,p \
+; RUN: -r=summary.o,_ZTI7Derived, \
+; RUN: -r=summary.o,_ZTV4Base 2>&1 | FileCheck --allow-empty %s --check-prefix=REMARK
+
+; REMARK: single-impl: devirtualized a call to _ZN8DerivedN5printEv
+
+; Index based WPD
+; For `_ZTI7Derived`, the 'llvm-lto2' resolution arguments specifies `VisibleOutsideSummary` as false
+; and `ExportDynamic` as true. The callsite inside @_ZN4Base8dispatchEv won't
+; get devirtualized.
+; RUN: llvm-lto2 run summary.o -save-temps -pass-remarks=. \
+; RUN: -o tmp \
+; RUN: --whole-program-visibility-enabled-in-lto=true \
+; RUN: --validate-all-vtables-have-type-infos=true \
+; RUN: --all-vtables-have-type-infos=true \
+; RUN: -r=summary.o,__cxa_pure_virtual, \
+; RUN: -r=summary.o,_ZN8DerivedNC2Ev,x \
+; RUN: -r=summary.o,_ZN4Base8dispatchEv,px \
+; RUN: -r=summary.o,_ZN7DerivedC2Ev, \
+; RUN: -r=summary.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=summary.o,_ZTS4Base, \
+; RUN: -r=summary.o,_ZTV8DerivedN,p \
+; RUN: -r=summary.o,_ZTI8DerivedN,p \
+; RUN: -r=summary.o,_ZTI4Base, \
+; RUN: -r=summary.o,_ZTS8DerivedN,p \
+; RUN: -r=summary.o,_ZTI7Derived,d \
+; RUN: -r=summary.o,_ZTV4Base 2>&1 | FileCheck %s --allow-empty --implicit-check-not='single-impl: devirtualized a call to'
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at _ZTV8DerivedN = linkonce_odr hidden constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI8DerivedN, ptr @_ZN8DerivedN5printEv] }, !type !0, !type !1, !type !2, !type !3, !type !4, !type !5, !vcall_visibility !6
+ at _ZTI8DerivedN = linkonce_odr hidden constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS8DerivedN, ptr @_ZTI7Derived }
+ at _ZTS8DerivedN = linkonce_odr hidden constant [10 x i8] c"8DerivedN\00", align 1
+ at _ZTI7Derived = external constant ptr
+ at _ZTV4Base = linkonce_odr hidden constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI4Base, ptr @__cxa_pure_virtual] }, !type !0, !type !1, !vcall_visibility !6
+ at _ZTI4Base = linkonce_odr hidden constant { ptr, ptr } { ptr null, ptr @_ZTS4Base }
+ at _ZTS4Base = linkonce_odr hidden constant [6 x i8] c"4Base\00", align 1
+
+ at llvm.used = appending global [1 x ptr] [ptr @_ZN8DerivedNC2Ev], section "llvm.metadata"
+
+define hidden void @_ZN4Base8dispatchEv(ptr %this) {
+entry:
+ %this.addr = alloca ptr
+ store ptr %this, ptr %this.addr
+ %this1 = load ptr, ptr %this.addr
+ %vtable = load ptr, ptr %this1
+ %0 = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS7Derived")
+ call void @llvm.assume(i1 %0)
+ %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+ %1 = load ptr, ptr %vfn
+ call void %1(ptr %this1)
+ ret void
+}
+
+define linkonce_odr hidden void @_ZN8DerivedNC2Ev(ptr %this) #0 {
+entry:
+ %this.addr = alloca ptr
+ store ptr %this, ptr %this.addr
+ %this1 = load ptr, ptr %this.addr
+ call void @_ZN7DerivedC2Ev(ptr %this1)
+ store ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV8DerivedN, i32 0, i32 0, i32 2), ptr %this1
+ ret void
+}
+
+define linkonce_odr hidden void @_ZN8DerivedN5printEv(ptr %this) #0 {
+entry:
+ ret void
+}
+
+attributes #0 = { noinline optnone }
+
+declare void @__cxa_pure_virtual()
+declare i1 @llvm.type.test(ptr, metadata)
+declare void @llvm.assume(i1)
+declare void @_ZN7DerivedC2Ev(ptr)
+
+!0 = !{i64 16, !"_ZTS4Base"}
+!1 = !{i64 16, !"_ZTSM4BaseFvvE.virtual"}
+!2 = !{i64 16, !"_ZTS7Derived"}
+!3 = !{i64 16, !"_ZTSM7DerivedFvvE.virtual"}
+!4 = !{i64 16, !"_ZTS8DerivedN"}
+!5 = !{i64 16, !"_ZTSM8DerivedNFvvE.virtual"}
+!6 = !{i64 0}
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index dbb7f2f3028aa..0317a610a25a3 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -187,6 +187,18 @@ static cl::opt<bool> EnableFreestanding(
cl::desc("Enable Freestanding (disable builtins / TLI) during LTO"),
cl::Hidden);
+static cl::opt<bool> WholeProgramVisibilityEnabledInLTO(
+ "whole-program-visibility-enabled-in-lto",
+ cl::desc("Enable whole program visibility during LTO"), cl::Hidden);
+
+static cl::opt<bool> ValidateAllVtablesHaveTypeInfos(
+ "validate-all-vtables-have-type-infos",
+ cl::desc("Validate that all vtables have type infos in LTO"), cl::Hidden);
+
+static cl::opt<bool>
+ AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
+ cl::desc("All vtables have type infos"));
+
extern cl::opt<cl::boolOrDefault> LoadBitcodeIntoNewDbgInfoFormat;
extern cl::opt<cl::boolOrDefault> PreserveInputDbgFormat;
@@ -257,6 +269,8 @@ static int run(int argc, char **argv) {
Res.VisibleToRegularObj = true;
else if (C == 'r')
Res.LinkerRedefined = true;
+ else if (C == 'd')
+ Res.ExportDynamic = true;
else {
llvm::errs() << "invalid character " << C << " in resolution: " << R
<< '\n';
@@ -332,6 +346,13 @@ static int run(int argc, char **argv) {
Conf.PTO.LoopVectorization = Conf.OptLevel > 1;
Conf.PTO.SLPVectorization = Conf.OptLevel > 1;
+ if (WholeProgramVisibilityEnabledInLTO.getNumOccurrences() > 0)
+ Conf.HasWholeProgramVisibility = WholeProgramVisibilityEnabledInLTO;
+ if (ValidateAllVtablesHaveTypeInfos.getNumOccurrences() > 0)
+ Conf.ValidateAllVtablesHaveTypeInfos = ValidateAllVtablesHaveTypeInfos;
+ if (AllVtablesHaveTypeInfos.getNumOccurrences() > 0)
+ Conf.AllVtablesHaveTypeInfos = AllVtablesHaveTypeInfos;
+
ThinBackend Backend;
if (ThinLTODistributedIndexes)
Backend = createWriteIndexesThinBackend(llvm::hardware_concurrency(Threads),
>From 6be44643eacc9a4a17e90f51bb42b3107df869d7 Mon Sep 17 00:00:00 2001
From: mingmingl <mingmingl at google.com>
Date: Tue, 18 Mar 2025 11:14:20 -0700
Subject: [PATCH 2/2] implement the fix for hybrid LTO, and update test cases
---
llvm/lib/LTO/LTO.cpp | 3 +-
..._dynamic.ll => nodevirt_export_dynamic.ll} | 85 ++++++++++++++-----
2 files changed, 66 insertions(+), 22 deletions(-)
rename llvm/test/ThinLTO/X86/{nodevirt_exort_dynamic.ll => nodevirt_export_dynamic.ll} (55%)
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 6ec2dc381a932..fe829c868d3c2 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1314,7 +1314,8 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {
// expected to be handled separately.
auto IsVisibleToRegularObj = [&](StringRef name) {
auto It = GlobalResolutions->find(name);
- return (It == GlobalResolutions->end() || It->second.VisibleOutsideSummary);
+ return (It == GlobalResolutions->end() ||
+ It->second.VisibleOutsideSummary || It->second.ExportDynamic);
};
// If allowed, upgrade public vcall visibility metadata to linkage unit
diff --git a/llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll b/llvm/test/ThinLTO/X86/nodevirt_export_dynamic.ll
similarity index 55%
rename from llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll
rename to llvm/test/ThinLTO/X86/nodevirt_export_dynamic.ll
index ea33291a75161..5dc809d2cab1f 100644
--- a/llvm/test/ThinLTO/X86/nodevirt_exort_dynamic.ll
+++ b/llvm/test/ThinLTO/X86/nodevirt_export_dynamic.ll
@@ -1,12 +1,12 @@
; RUN: rm -rf %t && mkdir %t && cd %t
+; Tests that devirtualization is suppressed on a class when its compatible
+; class could be referenced from dynamic linker that is not visible to the
+; linker.
+
; Generate unsplit module with summary for ThinLTO index-based WPD.
; RUN: opt -thinlto-bc -o summary.o %s
-; RUN: llvm-dis -o - summary.o
-
-;; TODO: Implement the fix for WPD in regular or hybrid LTO, and add test coverage.
-
; Index based WPD
; For `_ZTI7Derived`, the 'llvm-lto2' resolution arguments specifies `VisibleOutsideSummary` as false
; and `ExportDynamic` as false. The callsite inside @_ZN4Base8dispatchEv
@@ -21,15 +21,12 @@
; RUN: -r=summary.o,_ZN4Base8dispatchEv,px \
; RUN: -r=summary.o,_ZN7DerivedC2Ev, \
; RUN: -r=summary.o,_ZN8DerivedN5printEv,px \
-; RUN: -r=summary.o,_ZTS4Base, \
; RUN: -r=summary.o,_ZTV8DerivedN,p \
; RUN: -r=summary.o,_ZTI8DerivedN,p \
-; RUN: -r=summary.o,_ZTI4Base, \
; RUN: -r=summary.o,_ZTS8DerivedN,p \
; RUN: -r=summary.o,_ZTI7Derived, \
-; RUN: -r=summary.o,_ZTV4Base 2>&1 | FileCheck --allow-empty %s --check-prefix=REMARK
+; RUN: 2>&1 | FileCheck --allow-empty %s --check-prefix=REMARK
-; REMARK: single-impl: devirtualized a call to _ZN8DerivedN5printEv
; Index based WPD
; For `_ZTI7Derived`, the 'llvm-lto2' resolution arguments specifies `VisibleOutsideSummary` as false
@@ -45,24 +42,70 @@
; RUN: -r=summary.o,_ZN4Base8dispatchEv,px \
; RUN: -r=summary.o,_ZN7DerivedC2Ev, \
; RUN: -r=summary.o,_ZN8DerivedN5printEv,px \
-; RUN: -r=summary.o,_ZTS4Base, \
; RUN: -r=summary.o,_ZTV8DerivedN,p \
; RUN: -r=summary.o,_ZTI8DerivedN,p \
-; RUN: -r=summary.o,_ZTI4Base, \
; RUN: -r=summary.o,_ZTS8DerivedN,p \
; RUN: -r=summary.o,_ZTI7Derived,d \
-; RUN: -r=summary.o,_ZTV4Base 2>&1 | FileCheck %s --allow-empty --implicit-check-not='single-impl: devirtualized a call to'
+; RUN: 2>&1 | FileCheck %s --allow-empty --implicit-check-not='single-impl: devirtualized a call to'
+
+
+; Hybrid LTO WPD
+; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o hybrid.o %s
+; RUN: llvm-lto2 run hybrid.o -save-temps -pass-remarks=. \
+; RUN: -o hybrid \
+; RUN: --whole-program-visibility-enabled-in-lto=true \
+; RUN: --validate-all-vtables-have-type-infos=true \
+; RUN: --all-vtables-have-type-infos=true \
+; RUN: -r=hybrid.o,__cxa_pure_virtual, \
+; RUN: -r=hybrid.o,_ZN8DerivedNC2Ev,x \
+; RUN: -r=hybrid.o,_ZN4Base8dispatchEv,px \
+; RUN: -r=hybrid.o,_ZN7DerivedC2Ev, \
+; RUN: -r=hybrid.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=hybrid.o,_ZTV8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTS8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI7Derived, \
+; RUN: -r=hybrid.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=hybrid.o,_ZTV8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI8DerivedN,p \
+; RUN: 2>&1 | FileCheck --allow-empty %s --check-prefix=REMARK
+
+; Hybrid LTO WPD
+; RUN: llvm-lto2 run hybrid.o -save-temps -pass-remarks=. \
+; RUN: -o hybrid \
+; RUN: --whole-program-visibility-enabled-in-lto=true \
+; RUN: --validate-all-vtables-have-type-infos=true \
+; RUN: --all-vtables-have-type-infos=true \
+; RUN: -r=hybrid.o,__cxa_pure_virtual, \
+; RUN: -r=hybrid.o,_ZN8DerivedNC2Ev,x \
+; RUN: -r=hybrid.o,_ZN4Base8dispatchEv,px \
+; RUN: -r=hybrid.o,_ZN7DerivedC2Ev, \
+; RUN: -r=hybrid.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=hybrid.o,_ZTV8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTS8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI7Derived,d \
+; RUN: -r=hybrid.o,_ZN8DerivedN5printEv,px \
+; RUN: -r=hybrid.o,_ZTV8DerivedN,p \
+; RUN: -r=hybrid.o,_ZTI8DerivedN,p \
+; RUN: 2>&1 | FileCheck --allow-empty %s --implicit-check-not='single-impl: devirtualized a call to'
+
+
+; In regular LTO, global resolutions (as expected) show symbols are visible
+; outside summary (when they come from regular LTO module without summaries).
+; In the setting of this test case (equivalent of `-Wl,--lto-whole-program-visibility -Wl,--lto-validate-all-vtables-have-type-infos` in lld),
+; devirtualization will be suppressed even if the compatible class is not
+; referenced from shared libraries. So regular LTO test coverage is not meaningful.
+
+; REMARK: single-impl: devirtualized a call to _ZN8DerivedN5printEv
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
- at _ZTV8DerivedN = linkonce_odr hidden constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI8DerivedN, ptr @_ZN8DerivedN5printEv] }, !type !0, !type !1, !type !2, !type !3, !type !4, !type !5, !vcall_visibility !6
+ at _ZTV8DerivedN = linkonce_odr hidden constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI8DerivedN, ptr @_ZN8DerivedN5printEv] }, !type !0, !type !1, !type !2, !type !3, !vcall_visibility !6
@_ZTI8DerivedN = linkonce_odr hidden constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS8DerivedN, ptr @_ZTI7Derived }
@_ZTS8DerivedN = linkonce_odr hidden constant [10 x i8] c"8DerivedN\00", align 1
@_ZTI7Derived = external constant ptr
- at _ZTV4Base = linkonce_odr hidden constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI4Base, ptr @__cxa_pure_virtual] }, !type !0, !type !1, !vcall_visibility !6
- at _ZTI4Base = linkonce_odr hidden constant { ptr, ptr } { ptr null, ptr @_ZTS4Base }
- at _ZTS4Base = linkonce_odr hidden constant [6 x i8] c"4Base\00", align 1
@llvm.used = appending global [1 x ptr] [ptr @_ZN8DerivedNC2Ev], section "llvm.metadata"
@@ -102,10 +145,10 @@ declare i1 @llvm.type.test(ptr, metadata)
declare void @llvm.assume(i1)
declare void @_ZN7DerivedC2Ev(ptr)
-!0 = !{i64 16, !"_ZTS4Base"}
-!1 = !{i64 16, !"_ZTSM4BaseFvvE.virtual"}
-!2 = !{i64 16, !"_ZTS7Derived"}
-!3 = !{i64 16, !"_ZTSM7DerivedFvvE.virtual"}
-!4 = !{i64 16, !"_ZTS8DerivedN"}
-!5 = !{i64 16, !"_ZTSM8DerivedNFvvE.virtual"}
+!0 = !{i64 16, !"_ZTS7Derived"}
+!1 = !{i64 16, !"_ZTSM7DerivedFvvE.virtual"}
+!2 = !{i64 16, !"_ZTS8DerivedN"}
+!3 = !{i64 16, !"_ZTSM8DerivedNFvvE.virtual"}
+;!4 = !{i64 16, !"_ZTS8DerivedN"}
+;!5 = !{i64 16, !"_ZTSM8DerivedNFvvE.virtual"}
!6 = !{i64 0}
More information about the llvm-commits
mailing list