Skip to content

Commit db28818

Browse files
committed
[llvm] Teach whole program devirtualization about relative vtables
Prior to this patch, WPD was not acting on relative-vtables in C++. This involves teaching WPD about these things: - llvm.load.relative which is how relative-vtables are indexed (instead of GEP) - dso_local_equivalent which is used in the vtable itself when taking the offset between a virtual function and vtable - Update llvm/test/ThinLTO/X86/devirt.ll to use opaque pointers and add equivalent tests for RV Differential Revision: https://reviews.llvm.org/D134320
1 parent bbddaad commit db28818

File tree

12 files changed

+411
-11
lines changed

12 files changed

+411
-11
lines changed

llvm/include/llvm/Analysis/TypeMetadataUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void findDevirtualizableCallsForTypeCheckedLoad(
6464
/// Used for example from GlobalDCE to find an entry in a C++ vtable that
6565
/// matches a vcall offset.
6666
///
67-
/// To support Swift vtables, getPointerAtOffset can see through "relative
67+
/// To support relative vtables, getPointerAtOffset can see through "relative
6868
/// pointers", i.e. (sub-)expressions of the form of:
6969
///
7070
/// @symbol = ... {

llvm/lib/Analysis/ModuleSummaryAnalysis.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "llvm/ADT/StringRef.h"
2323
#include "llvm/Analysis/BlockFrequencyInfo.h"
2424
#include "llvm/Analysis/BranchProbabilityInfo.h"
25+
#include "llvm/Analysis/ConstantFolding.h"
2526
#include "llvm/Analysis/IndirectCallPromotionAnalysis.h"
2627
#include "llvm/Analysis/LoopInfo.h"
2728
#include "llvm/Analysis/MemoryProfileInfo.h"
@@ -580,7 +581,8 @@ static void computeFunctionSummary(
580581
/// within the initializer.
581582
static void findFuncPointers(const Constant *I, uint64_t StartingOffset,
582583
const Module &M, ModuleSummaryIndex &Index,
583-
VTableFuncList &VTableFuncs) {
584+
VTableFuncList &VTableFuncs,
585+
const GlobalVariable &OrigGV) {
584586
// First check if this is a function pointer.
585587
if (I->getType()->isPointerTy()) {
586588
auto C = I->stripPointerCasts();
@@ -608,15 +610,42 @@ static void findFuncPointers(const Constant *I, uint64_t StartingOffset,
608610
auto Offset = SL->getElementOffset(EI.index());
609611
unsigned Op = SL->getElementContainingOffset(Offset);
610612
findFuncPointers(cast<Constant>(I->getOperand(Op)),
611-
StartingOffset + Offset, M, Index, VTableFuncs);
613+
StartingOffset + Offset, M, Index, VTableFuncs, OrigGV);
612614
}
613615
} else if (auto *C = dyn_cast<ConstantArray>(I)) {
614616
ArrayType *ATy = C->getType();
615617
Type *EltTy = ATy->getElementType();
616618
uint64_t EltSize = DL.getTypeAllocSize(EltTy);
617619
for (unsigned i = 0, e = ATy->getNumElements(); i != e; ++i) {
618620
findFuncPointers(cast<Constant>(I->getOperand(i)),
619-
StartingOffset + i * EltSize, M, Index, VTableFuncs);
621+
StartingOffset + i * EltSize, M, Index, VTableFuncs,
622+
OrigGV);
623+
}
624+
} else if (const auto *CE = dyn_cast<ConstantExpr>(I)) {
625+
// For relative vtables, the next sub-component should be a trunc.
626+
if (CE->getOpcode() != Instruction::Trunc ||
627+
!(CE = dyn_cast<ConstantExpr>(CE->getOperand(0))))
628+
return;
629+
630+
// If this constant can be reduced to the offset between a function and a
631+
// global, then we know this is a valid virtual function if the RHS is the
632+
// original vtable we're scanning through.
633+
if (CE->getOpcode() == Instruction::Sub) {
634+
GlobalValue *LHS, *RHS;
635+
APSInt LHSOffset, RHSOffset;
636+
if (IsConstantOffsetFromGlobal(CE->getOperand(0), LHS, LHSOffset, DL) &&
637+
IsConstantOffsetFromGlobal(CE->getOperand(1), RHS, RHSOffset, DL) &&
638+
RHS == &OrigGV &&
639+
640+
// For relative vtables, this component should point to the callable
641+
// function without any offsets.
642+
LHSOffset == 0 &&
643+
644+
// Also, the RHS should always point to somewhere within the vtable.
645+
RHSOffset <=
646+
static_cast<uint64_t>(DL.getTypeAllocSize(OrigGV.getInitializer()->getType()))) {
647+
findFuncPointers(LHS, StartingOffset, M, Index, VTableFuncs, OrigGV);
648+
}
620649
}
621650
}
622651
}
@@ -629,7 +658,7 @@ static void computeVTableFuncs(ModuleSummaryIndex &Index,
629658
return;
630659

631660
findFuncPointers(V.getInitializer(), /*StartingOffset=*/0, M, Index,
632-
VTableFuncs);
661+
VTableFuncs, V);
633662

634663
#ifndef NDEBUG
635664
// Validate that the VTableFuncs list is ordered by offset.

llvm/lib/Analysis/TypeMetadataUtils.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ static void findLoadCallsAtConstantOffset(
6767
findLoadCallsAtConstantOffset(M, DevirtCalls, User, Offset + GEPOffset,
6868
CI, DT);
6969
}
70+
} else if (auto *Call = dyn_cast<CallInst>(User)) {
71+
if (Call->getIntrinsicID() == llvm::Intrinsic::load_relative) {
72+
if (auto *LoadOffset = dyn_cast<ConstantInt>(Call->getOperand(1))) {
73+
findCallsAtConstantOffset(DevirtCalls, nullptr, User,
74+
Offset + LoadOffset->getSExtValue(), CI,
75+
DT);
76+
}
77+
}
7078
}
7179
}
7280
}
@@ -129,6 +137,12 @@ void llvm::findDevirtualizableCallsForTypeCheckedLoad(
129137

130138
Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M,
131139
Constant *TopLevelGlobal) {
140+
// TODO: Ideally it would be the caller who knows if it's appropriate to strip
141+
// the DSOLocalEquicalent. More generally, it would feel more appropriate to
142+
// have two functions that handle absolute and relative pointers separately.
143+
if (auto *Equiv = dyn_cast<DSOLocalEquivalent>(I))
144+
I = Equiv->getGlobalValue();
145+
132146
if (I->getType()->isPointerTy()) {
133147
if (Offset == 0)
134148
return I;
@@ -159,7 +173,7 @@ Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M,
159173
Offset % ElemSize, M, TopLevelGlobal);
160174
}
161175

162-
// (Swift-specific) relative-pointer support starts here.
176+
// Relative-pointer support starts here.
163177
if (auto *CI = dyn_cast<ConstantInt>(I)) {
164178
if (Offset == 0 && CI->getZExtValue() == 0) {
165179
return I;

llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ bool DevirtModule::tryFindVirtualCallTargets(
10061006
return false;
10071007

10081008
Constant *Ptr = getPointerAtOffset(TM.Bits->GV->getInitializer(),
1009-
TM.Offset + ByteOffset, M);
1009+
TM.Offset + ByteOffset, M, TM.Bits->GV);
10101010
if (!Ptr)
10111011
return false;
10121012

llvm/test/ThinLTO/X86/devirt.ll

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,36 @@
2727
; NOENABLESPLITFLAG-DAG: [[B:\^[0-9]+]] = gv: (name: "_ZTV1B", {{.*}} vTableFuncs: ((virtFunc: [[Bf]], offset: 16), (virtFunc: [[An]], offset: 24)), refs: ([[Bf]], [[An]])
2828
; NOENABLESPLITFLAG-DAG: [[C:\^[0-9]+]] = gv: (name: "_ZTV1C", {{.*}} vTableFuncs: ((virtFunc: [[Cf]], offset: 16), (virtFunc: [[An]], offset: 24)), refs: ([[An]], [[Cf]])
2929
; NOENABLESPLITFLAG-DAG: [[D:\^[0-9]+]] = gv: (name: "_ZTV1D", {{.*}} vTableFuncs: ((virtFunc: [[Dm]], offset: 16)), refs: ([[Dm]])
30+
; NOENABLESPLITFLAG-DAG: [[B_RV:\^[0-9]+]] = gv: (name: "_ZTV1B_RV", {{.*}} vTableFuncs: ((virtFunc: [[Bf]], offset: 8), (virtFunc: [[An]], offset: 12)), refs: ([[B_RV]], [[Bf]], [[An]])
31+
; NOENABLESPLITFLAG-DAG: [[C_RV:\^[0-9]+]] = gv: (name: "_ZTV1C_RV", {{.*}} vTableFuncs: ((virtFunc: [[Cf]], offset: 8), (virtFunc: [[An]], offset: 12)), refs: ([[C_RV]], [[An]], [[Cf]])
32+
; NOENABLESPLITFLAG-DAG: [[D_RV:\^[0-9]+]] = gv: (name: "_ZTV1D_RV", {{.*}} vTableFuncs: ((virtFunc: [[Dm]], offset: 8)), refs: ([[D_RV]], [[Dm]])
3033
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1A", summary: ((offset: 16, [[B]]), (offset: 16, [[C]])))
3134
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1B", summary: ((offset: 16, [[B]])))
3235
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1C", summary: ((offset: 16, [[C]])))
36+
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1A_RV", summary: ((offset: 8, [[B_RV]]), (offset: 8, [[C_RV]])))
37+
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1B_RV", summary: ((offset: 8, [[B_RV]])))
38+
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1C_RV", summary: ((offset: 8, [[C_RV]])))
3339
; Type Id on _ZTV1D should have been promoted
3440
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "1.{{.*}}", summary: ((offset: 16, [[D]])))
41+
; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "2.{{.*}}", summary: ((offset: 8, [[D_RV]])))
3542

3643
; Index based WPD
3744
; RUN: llvm-lto2 run %t2.o -save-temps -pass-remarks=. \
3845
; RUN: -whole-program-visibility \
3946
; RUN: -o %t3 \
4047
; RUN: -r=%t2.o,test,px \
48+
; RUN: -r=%t2.o,test_rv,px \
4149
; RUN: -r=%t2.o,_ZN1A1nEi,p \
4250
; RUN: -r=%t2.o,_ZN1B1fEi,p \
4351
; RUN: -r=%t2.o,_ZN1C1fEi,p \
4452
; RUN: -r=%t2.o,_ZN1D1mEi,p \
4553
; RUN: -r=%t2.o,_ZTV1B,px \
4654
; RUN: -r=%t2.o,_ZTV1C,px \
47-
; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK
55+
; RUN: -r=%t2.o,_ZTV1D,px \
56+
; RUN: -r=%t2.o,_ZTV1B_RV,px \
57+
; RUN: -r=%t2.o,_ZTV1C_RV,px \
58+
; RUN: -r=%t2.o,_ZTV1D_RV,px \
59+
; RUN: 2>&1 | FileCheck %s --check-prefix=REMARK
4860
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR
4961

5062
; Check that we're able to prevent specific function from being
@@ -54,34 +66,49 @@
5466
; RUN: -wholeprogramdevirt-skip=_ZN1A1nEi \
5567
; RUN: -o %t3 \
5668
; RUN: -r=%t2.o,test,px \
69+
; RUN: -r=%t2.o,test_rv,px \
5770
; RUN: -r=%t2.o,_ZN1A1nEi,p \
5871
; RUN: -r=%t2.o,_ZN1B1fEi,p \
5972
; RUN: -r=%t2.o,_ZN1C1fEi,p \
6073
; RUN: -r=%t2.o,_ZN1D1mEi,p \
6174
; RUN: -r=%t2.o,_ZTV1B,px \
6275
; RUN: -r=%t2.o,_ZTV1C,px \
63-
; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=SKIP
76+
; RUN: -r=%t2.o,_ZTV1D,px \
77+
; RUN: -r=%t2.o,_ZTV1B_RV,px \
78+
; RUN: -r=%t2.o,_ZTV1C_RV,px \
79+
; RUN: -r=%t2.o,_ZTV1D_RV,px \
80+
; RUN: 2>&1 | FileCheck %s --check-prefix=SKIP
6481

6582
; RUN: llvm-lto2 run %t.o -save-temps -pass-remarks=. \
6683
; RUN: -whole-program-visibility \
6784
; RUN: -o %t3 \
6885
; RUN: -r=%t.o,test,px \
86+
; RUN: -r=%t.o,test_rv,px \
6987
; RUN: -r=%t.o,_ZN1A1nEi,p \
7088
; RUN: -r=%t.o,_ZN1B1fEi,p \
7189
; RUN: -r=%t.o,_ZN1C1fEi,p \
7290
; RUN: -r=%t.o,_ZN1D1mEi,p \
7391
; RUN: -r=%t.o,_ZTV1B, \
7492
; RUN: -r=%t.o,_ZTV1C, \
7593
; RUN: -r=%t.o,_ZTV1D, \
94+
; RUN: -r=%t.o,_ZTV1B_RV, \
95+
; RUN: -r=%t.o,_ZTV1C_RV, \
96+
; RUN: -r=%t.o,_ZTV1D_RV, \
7697
; RUN: -r=%t.o,_ZN1A1nEi, \
7798
; RUN: -r=%t.o,_ZN1B1fEi, \
7899
; RUN: -r=%t.o,_ZN1C1fEi, \
79100
; RUN: -r=%t.o,_ZN1D1mEi, \
80101
; RUN: -r=%t.o,_ZTV1B,px \
81102
; RUN: -r=%t.o,_ZTV1C,px \
82-
; RUN: -r=%t.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK --dump-input=fail
103+
; RUN: -r=%t.o,_ZTV1D,px \
104+
; RUN: -r=%t.o,_ZTV1B_RV,px \
105+
; RUN: -r=%t.o,_ZTV1C_RV,px \
106+
; RUN: -r=%t.o,_ZTV1D_RV,px \
107+
; RUN: 2>&1 | FileCheck %s --check-prefix=REMARK --dump-input=fail
83108
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR
84109

110+
; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
111+
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi
85112
; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
86113
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi
87114

@@ -99,6 +126,25 @@ target triple = "x86_64-grtev4-linux-gnu"
99126
@_ZTV1C = constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr undef, ptr @_ZN1C1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !2
100127
@_ZTV1D = constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr undef, ptr @_ZN1D1mEi] }, !type !3
101128

129+
@_ZTV1B_RV = constant { [4 x i32] } { [4 x i32] [
130+
i32 0,
131+
i32 undef,
132+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1B1fEi to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @_ZTV1B_RV, i32 0, i32 0, i32 2) to i64)) to i32),
133+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1A1nEi to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @_ZTV1B_RV, i32 0, i32 0, i32 3) to i64)) to i32)
134+
] }, !type !7, !type !8
135+
136+
@_ZTV1C_RV = constant { [4 x i32] } { [4 x i32] [
137+
i32 0,
138+
i32 undef,
139+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1C1fEi to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @_ZTV1C_RV, i32 0, i32 0, i32 2) to i64)) to i32),
140+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1A1nEi to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @_ZTV1C_RV, i32 0, i32 0, i32 3) to i64)) to i32)
141+
] }, !type !7, !type !9
142+
143+
@_ZTV1D_RV = constant { [3 x i32] } { [3 x i32] [
144+
i32 0,
145+
i32 undef,
146+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1D1mEi to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1D_RV, i32 0, i32 0, i32 2) to i64)) to i32)
147+
] }, !type !10
102148

103149
; CHECK-IR-LABEL: define i32 @test
104150
define i32 @test(ptr %obj, ptr %obj2, i32 %a) {
@@ -136,6 +182,43 @@ entry:
136182
; CHECK-IR-LABEL: ret i32
137183
; CHECK-IR-LABEL: }
138184

185+
declare ptr @llvm.load.relative.i32(ptr, i32)
186+
187+
; CHECK-IR-LABEL: define i32 @test_rv
188+
define i32 @test_rv(ptr %obj, ptr %obj2, i32 %a) {
189+
entry:
190+
%vtable = load ptr, ptr %obj
191+
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS1A_RV")
192+
call void @llvm.assume(i1 %p)
193+
%fptr1_rv = call ptr @llvm.load.relative.i32(ptr %vtable, i32 4)
194+
195+
; Check that the call was devirtualized.
196+
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
197+
; Ensure !prof and !callees metadata for indirect call promotion removed.
198+
; CHECK-IR-NOT: prof
199+
; CHECK-IR-NOT: callees
200+
%call = tail call i32 %fptr1_rv(ptr nonnull %obj, i32 %a), !prof !5, !callees !6
201+
202+
%fptr22_rv = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
203+
204+
; We still have to call it as virtual.
205+
; CHECK-IR: %call3 = tail call i32 %fptr22
206+
%call3 = tail call i32 %fptr22_rv(ptr nonnull %obj, i32 %call)
207+
208+
%vtable2 = load ptr, ptr %obj2
209+
%p2 = call i1 @llvm.type.test(ptr %vtable2, metadata !11)
210+
call void @llvm.assume(i1 %p2)
211+
212+
%fptr33_rv = call ptr @llvm.load.relative.i32(ptr %vtable2, i32 0)
213+
214+
; Check that the call was devirtualized.
215+
; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi
216+
%call4 = tail call i32 %fptr33_rv(ptr nonnull %obj2, i32 %call3)
217+
ret i32 %call4
218+
}
219+
; CHECK-IR-LABEL: ret i32
220+
; CHECK-IR-LABEL: }
221+
139222
declare i1 @llvm.type.test(ptr, metadata)
140223
declare void @llvm.assume(i1)
141224

@@ -165,3 +248,9 @@ attributes #0 = { noinline optnone }
165248
!4 = distinct !{}
166249
!5 = !{!"VP", i32 0, i64 1, i64 1621563287929432257, i64 1}
167250
!6 = !{ptr @_ZN1A1nEi}
251+
252+
!7 = !{i64 8, !"_ZTS1A_RV"}
253+
!8 = !{i64 8, !"_ZTS1B_RV"}
254+
!9 = !{i64 8, !"_ZTS1C_RV"}
255+
!10 = !{i64 8, !11}
256+
!11 = distinct !{}

llvm/test/Transforms/WholeProgramDevirt/Inputs/export.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@ GlobalValueMap:
55
TypeTestAssumeVCalls:
66
- GUID: 14276520915468743435 # typeid1
77
Offset: 0
8+
- GUID: 271751036925422857 # typeid1_rv
9+
Offset: 0
810
TypeCheckedLoadVCalls:
911
- GUID: 15427464259790519041 # typeid2
1012
Offset: 0
13+
- GUID: 1146149264729288256 # typeid2_rv
14+
Offset: 0
1115
TypeTestAssumeConstVCalls:
1216
- VFunc:
1317
GUID: 3515965990081467659 # typeid3
1418
Offset: 0
1519
Args: [12, 24]
20+
- VFunc:
21+
GUID: 2777626534618191571 # typeid3_rv
22+
Offset: 0
23+
Args: [12, 24]
1624
TypeCheckedLoadConstVCalls:
1725
- VFunc:
1826
GUID: 17525413373118030901 # typeid4

0 commit comments

Comments
 (0)