Skip to content

Commit 3c4c205

Browse files
committed
[WPD][lld] Test handling of vtable definition from shared libraries
Adds a lld test for a case that the handling added for dynamically exported symbols in 1487747 already fixes. Because isExportDynamic returns true when the symbol is SharedKind with default visibility, it will treat as dynamically exported and block devirtualization when the definition of a vtable comes from a shared library. This is desireable as it is dangerous to devirtualize in that case, since there could be hidden overrides in the shared library. Typically that happens when the shared library header contains available externally definitions, which applications can override. An example is std::error_category, which is overridden in LLVM and causing failures after a self build with WPD enabled, because libstdc++ contains hidden overrides of the virtual base class methods. The regular LTO case in the new test already worked, but there are 2 fixes in this patch needed for the index-only case and the hybrid LTO case. For the index-only case, WPD should not simply ignore available externally vtables. A follow on fix will be made to clang to emit type metadata for those vtables, which the new test is modeling. For the hybrid case, we need to ensure when the module is split that any llvm.*used globals are cloned to the regular LTO split module so available externally vtable definitions are not prematurely deleted. Another follow on fix will add the equivalent gold test, which requires a small fix to the plugin to treat symbols in dynamic libraries the same way lld already is. Differential Revision: https://reviews.llvm.org/D96721
1 parent e741916 commit 3c4c205

File tree

4 files changed

+140
-6
lines changed

4 files changed

+140
-6
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
2+
target triple = "x86_64-grtev4-linux-gnu"
3+
4+
%struct.A = type { i32 (...)** }
5+
6+
@_ZTV1A = unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !vcall_visibility !1
7+
8+
define i32 @_ZN1A1fEi(%struct.A* %this, i32 %a) #0 {
9+
ret i32 0;
10+
}
11+
12+
define i32 @_ZN1A1nEi(%struct.A* %this, i32 %a) #0 {
13+
ret i32 0;
14+
}
15+
16+
attributes #0 = { noinline optnone }
17+
18+
!0 = !{i64 16, !"_ZTS1A"}
19+
!1 = !{i64 0}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
; REQUIRES: x86
2+
;; Test that symbols defined in shared libraries prevent devirtualization.
3+
4+
;; First check that we get devirtualization when the defs are in the
5+
;; LTO unit.
6+
7+
;; Index based WPD
8+
;; Generate unsplit module with summary for ThinLTO index-based WPD.
9+
; RUN: opt --thinlto-bc -o %t1a.o %s
10+
; RUN: opt --thinlto-bc -o %t2a.o %S/Inputs/devirt_vcall_vis_shared_def.ll
11+
; RUN: ld.lld %t1a.o %t2a.o -o %t3a -save-temps --lto-whole-program-visibility \
12+
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
13+
; RUN: llvm-dis %t1a.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR
14+
15+
;; Hybrid WPD
16+
;; Generate split module with summary for hybrid Thin/Regular LTO WPD.
17+
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t1b.o %s
18+
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t2b.o %S/Inputs/devirt_vcall_vis_shared_def.ll
19+
; RUN: ld.lld %t1b.o %t2b.o -o %t3b -save-temps --lto-whole-program-visibility \
20+
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
21+
; RUN: llvm-dis %t1b.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR
22+
23+
;; Regular LTO WPD
24+
; RUN: opt -o %t1c.o %s
25+
; RUN: opt -o %t2c.o %S/Inputs/devirt_vcall_vis_shared_def.ll
26+
; RUN: ld.lld %t1c.o %t2c.o -o %t3c -save-temps --lto-whole-program-visibility \
27+
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
28+
; RUN: llvm-dis %t3c.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR
29+
30+
; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
31+
32+
;; Check that WPD fails with when linking against a shared library
33+
;; containing the strong defs of the vtables.
34+
; RUN: ld.lld %t2c.o -o %t.so -shared
35+
36+
;; Index based WPD
37+
; RUN: ld.lld %t1a.o %t.so -o %t4a --lto-whole-program-visibility \
38+
; RUN: -mllvm -pass-remarks=. 2>&1 | count 0
39+
40+
;; Hybrid WPD
41+
; RUN: ld.lld %t1b.o %t.so -o %t4b --lto-whole-program-visibility \
42+
; RUN: -mllvm -pass-remarks=. 2>&1 | count 0
43+
44+
;; Regular LTO WPD
45+
; RUN: ld.lld %t1c.o %t.so -o %t4c --lto-whole-program-visibility \
46+
; RUN: -mllvm -pass-remarks=. 2>&1 | count 0
47+
48+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
49+
target triple = "x86_64-grtev4-linux-gnu"
50+
51+
%struct.A = type { i32 (...)** }
52+
%struct.B = type { %struct.A }
53+
54+
@_ZTV1A = available_externally unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !vcall_visibility !2
55+
@_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 !2
56+
57+
;; Prevent the vtables from being dead code eliminated.
58+
@llvm.used = appending global [2 x i8*] [ i8* bitcast ( { [4 x i8*] }* @_ZTV1A to i8*), i8* bitcast ( { [4 x i8*] }* @_ZTV1B to i8*)]
59+
60+
; CHECK-IR-LABEL: define dso_local i32 @_start
61+
define i32 @_start(%struct.A* %obj, i32 %a) {
62+
entry:
63+
%0 = bitcast %struct.A* %obj to i8***
64+
%vtable = load i8**, i8*** %0
65+
%1 = bitcast i8** %vtable to i8*
66+
%p = call i1 @llvm.type.test(i8* %1, metadata !"_ZTS1A")
67+
call void @llvm.assume(i1 %p)
68+
%fptrptr = getelementptr i8*, i8** %vtable, i32 1
69+
%2 = bitcast i8** %fptrptr to i32 (%struct.A*, i32)**
70+
%fptr1 = load i32 (%struct.A*, i32)*, i32 (%struct.A*, i32)** %2, align 8
71+
72+
;; Check that the call was devirtualized.
73+
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
74+
; CHECK-NODEVIRT-IR: %call = tail call i32 %fptr1
75+
%call = tail call i32 %fptr1(%struct.A* nonnull %obj, i32 %a)
76+
77+
ret i32 %call
78+
}
79+
; CHECK-IR-LABEL: ret i32
80+
; CHECK-IR-LABEL: }
81+
82+
declare i1 @llvm.type.test(i8*, metadata)
83+
declare void @llvm.assume(i1)
84+
85+
define available_externally i32 @_ZN1A1fEi(%struct.A* %this, i32 %a) #0 {
86+
ret i32 0
87+
}
88+
89+
define available_externally i32 @_ZN1A1nEi(%struct.A* %this, i32 %a) #0 {
90+
ret i32 0
91+
}
92+
93+
define linkonce_odr i32 @_ZN1B1fEi(%struct.B* %this, i32 %a) #0 {
94+
ret i32 0
95+
}
96+
97+
;; Make sure we don't inline or otherwise optimize out the direct calls.
98+
attributes #0 = { noinline optnone }
99+
100+
!0 = !{i64 16, !"_ZTS1A"}
101+
!1 = !{i64 16, !"_ZTS1B"}
102+
!2 = !{i64 0}

llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ void splitAndWriteThinLTOBitcode(
275275
ValueToValueMapTy VMap;
276276
std::unique_ptr<Module> MergedM(
277277
CloneModule(M, VMap, [&](const GlobalValue *GV) -> bool {
278+
// Clone any llvm.*used globals to ensure the included values are
279+
// not deleted.
280+
if (GV->getName() == "llvm.used" ||
281+
GV->getName() == "llvm.compiler.used")
282+
return true;
278283
if (const auto *C = GV->getComdat())
279284
if (MergedMComdats.count(C))
280285
return true;

llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -994,10 +994,10 @@ bool DevirtIndex::tryFindVirtualCallTargets(
994994
std::vector<ValueInfo> &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo,
995995
uint64_t ByteOffset) {
996996
for (const TypeIdOffsetVtableInfo &P : TIdInfo) {
997-
// Find the first non-available_externally linkage vtable initializer.
997+
// Find a representative copy of the vtable initializer.
998998
// We can have multiple available_externally, linkonce_odr and weak_odr
999-
// vtable initializers, however we want to skip available_externally as they
1000-
// do not have type metadata attached, and therefore the summary will not
999+
// vtable initializers, however currently clang does not attach type
1000+
// metadata to available_externally, and therefore the summary will not
10011001
// contain any vtable functions. We can also have multiple external
10021002
// vtable initializers in the case of comdats, which we cannot check here.
10031003
// The linker should give an error in this case.
@@ -1014,14 +1014,22 @@ bool DevirtIndex::tryFindVirtualCallTargets(
10141014
return false;
10151015
LocalFound = true;
10161016
}
1017-
if (!GlobalValue::isAvailableExternallyLinkage(S->linkage())) {
1018-
VS = cast<GlobalVarSummary>(S->getBaseObject());
1017+
auto *CurVS = cast<GlobalVarSummary>(S->getBaseObject());
1018+
if (!CurVS->vTableFuncs().empty()) {
1019+
VS = CurVS;
10191020
// We cannot perform whole program devirtualization analysis on a vtable
10201021
// with public LTO visibility.
10211022
if (VS->getVCallVisibility() == GlobalObject::VCallVisibilityPublic)
10221023
return false;
1023-
}
1024+
} else
1025+
// Currently clang will not attach the necessary type metadata to
1026+
// available_externally vtables.
1027+
assert(GlobalValue::isAvailableExternallyLinkage(S->linkage()));
10241028
}
1029+
// There will be no VS if all copies are available_externally having no
1030+
// type metadata. In that case we can't safely perform WPD.
1031+
if (!VS)
1032+
return false;
10251033
if (!VS->isLive())
10261034
continue;
10271035
for (auto VTP : VS->vTableFuncs()) {

0 commit comments

Comments
 (0)