Skip to content

Commit 58c5f50

Browse files
committed
Reapply "[llvm] Teach GlobalDCE about dso_local_equivalent"
Also reapply "[llvm] Teach whole program devirtualization about relative vtables" This reverts commit 1c604a9 and 474f5ef.
1 parent 3eb0973 commit 58c5f50

14 files changed

+548
-27
lines changed

llvm/include/llvm/Analysis/TypeMetadataUtils.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void findDevirtualizableCallsForTypeCheckedLoad(
6666
/// Used for example from GlobalDCE to find an entry in a C++ vtable that
6767
/// matches a vcall offset.
6868
///
69-
/// To support Swift vtables, getPointerAtOffset can see through "relative
69+
/// To support relative vtables, getPointerAtOffset can see through "relative
7070
/// pointers", i.e. (sub-)expressions of the form of:
7171
///
7272
/// @symbol = ... {
@@ -87,8 +87,8 @@ std::pair<Function *, Constant *>
8787
getFunctionAtVTableOffset(GlobalVariable *GV, uint64_t Offset, Module &M);
8888

8989
/// Finds the same "relative pointer" pattern as described above, where the
90-
/// target is `F`, and replaces the entire pattern with a constant zero.
91-
void replaceRelativePointerUsersWithZero(Function *F);
90+
/// target is `C`, and replaces the entire pattern with a constant zero.
91+
void replaceRelativePointerUsersWithZero(Constant *C);
9292

9393
} // namespace llvm
9494

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"
@@ -668,7 +669,8 @@ static void computeFunctionSummary(
668669
/// within the initializer.
669670
static void findFuncPointers(const Constant *I, uint64_t StartingOffset,
670671
const Module &M, ModuleSummaryIndex &Index,
671-
VTableFuncList &VTableFuncs) {
672+
VTableFuncList &VTableFuncs,
673+
const GlobalVariable &OrigGV) {
672674
// First check if this is a function pointer.
673675
if (I->getType()->isPointerTy()) {
674676
auto C = I->stripPointerCasts();
@@ -696,15 +698,42 @@ static void findFuncPointers(const Constant *I, uint64_t StartingOffset,
696698
auto Offset = SL->getElementOffset(EI.index());
697699
unsigned Op = SL->getElementContainingOffset(Offset);
698700
findFuncPointers(cast<Constant>(I->getOperand(Op)),
699-
StartingOffset + Offset, M, Index, VTableFuncs);
701+
StartingOffset + Offset, M, Index, VTableFuncs, OrigGV);
700702
}
701703
} else if (auto *C = dyn_cast<ConstantArray>(I)) {
702704
ArrayType *ATy = C->getType();
703705
Type *EltTy = ATy->getElementType();
704706
uint64_t EltSize = DL.getTypeAllocSize(EltTy);
705707
for (unsigned i = 0, e = ATy->getNumElements(); i != e; ++i) {
706708
findFuncPointers(cast<Constant>(I->getOperand(i)),
707-
StartingOffset + i * EltSize, M, Index, VTableFuncs);
709+
StartingOffset + i * EltSize, M, Index, VTableFuncs,
710+
OrigGV);
711+
}
712+
} else if (const auto *CE = dyn_cast<ConstantExpr>(I)) {
713+
// For relative vtables, the next sub-component should be a trunc.
714+
if (CE->getOpcode() != Instruction::Trunc ||
715+
!(CE = dyn_cast<ConstantExpr>(CE->getOperand(0))))
716+
return;
717+
718+
// If this constant can be reduced to the offset between a function and a
719+
// global, then we know this is a valid virtual function if the RHS is the
720+
// original vtable we're scanning through.
721+
if (CE->getOpcode() == Instruction::Sub) {
722+
GlobalValue *LHS, *RHS;
723+
APSInt LHSOffset, RHSOffset;
724+
if (IsConstantOffsetFromGlobal(CE->getOperand(0), LHS, LHSOffset, DL) &&
725+
IsConstantOffsetFromGlobal(CE->getOperand(1), RHS, RHSOffset, DL) &&
726+
RHS == &OrigGV &&
727+
728+
// For relative vtables, this component should point to the callable
729+
// function without any offsets.
730+
LHSOffset == 0 &&
731+
732+
// Also, the RHS should always point to somewhere within the vtable.
733+
RHSOffset <=
734+
static_cast<uint64_t>(DL.getTypeAllocSize(OrigGV.getInitializer()->getType()))) {
735+
findFuncPointers(LHS, StartingOffset, M, Index, VTableFuncs, OrigGV);
736+
}
708737
}
709738
}
710739
}
@@ -717,7 +746,7 @@ static void computeVTableFuncs(ModuleSummaryIndex &Index,
717746
return;
718747

719748
findFuncPointers(V.getInitializer(), /*StartingOffset=*/0, M, Index,
720-
VTableFuncs);
749+
VTableFuncs, V);
721750

722751
#ifndef NDEBUG
723752
// Validate that the VTableFuncs list is ordered by offset.

llvm/lib/Analysis/TypeMetadataUtils.cpp

Lines changed: 34 additions & 13 deletions
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
}
@@ -131,6 +139,12 @@ void llvm::findDevirtualizableCallsForTypeCheckedLoad(
131139

132140
Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M,
133141
Constant *TopLevelGlobal) {
142+
// TODO: Ideally it would be the caller who knows if it's appropriate to strip
143+
// the DSOLocalEquicalent. More generally, it would feel more appropriate to
144+
// have two functions that handle absolute and relative pointers separately.
145+
if (auto *Equiv = dyn_cast<DSOLocalEquivalent>(I))
146+
I = Equiv->getGlobalValue();
147+
134148
if (I->getType()->isPointerTy()) {
135149
if (Offset == 0)
136150
return I;
@@ -161,7 +175,7 @@ Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M,
161175
Offset % ElemSize, M, TopLevelGlobal);
162176
}
163177

164-
// (Swift-specific) relative-pointer support starts here.
178+
// Relative-pointer support starts here.
165179
if (auto *CI = dyn_cast<ConstantInt>(I)) {
166180
if (Offset == 0 && CI->isZero()) {
167181
return I;
@@ -221,19 +235,26 @@ llvm::getFunctionAtVTableOffset(GlobalVariable *GV, uint64_t Offset,
221235
return std::pair<Function *, Constant *>(Fn, C);
222236
}
223237

224-
void llvm::replaceRelativePointerUsersWithZero(Function *F) {
225-
for (auto *U : F->users()) {
226-
auto *PtrExpr = dyn_cast<ConstantExpr>(U);
227-
if (!PtrExpr || PtrExpr->getOpcode() != Instruction::PtrToInt)
228-
continue;
238+
static void replaceRelativePointerUserWithZero(User *U) {
239+
auto *PtrExpr = dyn_cast<ConstantExpr>(U);
240+
if (!PtrExpr || PtrExpr->getOpcode() != Instruction::PtrToInt)
241+
return;
229242

230-
for (auto *PtrToIntUser : PtrExpr->users()) {
231-
auto *SubExpr = dyn_cast<ConstantExpr>(PtrToIntUser);
232-
if (!SubExpr || SubExpr->getOpcode() != Instruction::Sub)
233-
continue;
243+
for (auto *PtrToIntUser : PtrExpr->users()) {
244+
auto *SubExpr = dyn_cast<ConstantExpr>(PtrToIntUser);
245+
if (!SubExpr || SubExpr->getOpcode() != Instruction::Sub)
246+
return;
234247

235-
SubExpr->replaceNonMetadataUsesWith(
236-
ConstantInt::get(SubExpr->getType(), 0));
237-
}
248+
SubExpr->replaceNonMetadataUsesWith(
249+
ConstantInt::get(SubExpr->getType(), 0));
250+
}
251+
}
252+
253+
void llvm::replaceRelativePointerUsersWithZero(Constant *C) {
254+
for (auto *U : C->users()) {
255+
if (auto *Equiv = dyn_cast<DSOLocalEquivalent>(U))
256+
replaceRelativePointerUsersWithZero(Equiv);
257+
else
258+
replaceRelativePointerUserWithZero(U);
238259
}
239260
}

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 {{(noundef )?}}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/GlobalDCE/virtual-functions-relative-pointers-bad.ll

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,32 @@ declare { ptr, i1 } @llvm.type.checked.load(ptr, i32, metadata)
1616

1717
; CHECK: @vtable = internal unnamed_addr constant { [3 x i32] } zeroinitializer, align 8, !type !0, !type !1, !vcall_visibility !2
1818

19+
@vtable2 = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [
20+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3 to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32),
21+
i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc4 to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32),
22+
23+
; a "bad" relative pointer because it's base is not the @vtable symbol
24+
i32 trunc (i64 sub (i64 ptrtoint (ptr @weird_ref_3 to i64), i64 ptrtoint (ptr @weird_ref_4 to i64)) to i32)
25+
]}, align 4, !type !3, !type !4, !vcall_visibility !{i64 2}
26+
!3 = !{i64 0, !"vfunc3.type"}
27+
!4 = !{i64 4, !"vfunc4.type"}
28+
29+
; CHECK: @vtable2 = internal unnamed_addr constant { [3 x i32] } zeroinitializer, align 4, !type !3, !type !4, !vcall_visibility !2
30+
1931
define internal void @vfunc1() { ret void }
2032
define internal void @vfunc2() { ret void }
2133
define internal void @weird_ref_1() { ret void }
2234
define internal void @weird_ref_2() { ret void }
35+
declare void @vfunc3()
36+
declare void @vfunc4()
37+
declare void @weird_ref_3()
38+
declare void @weird_ref_4()
2339

2440
define void @main() {
2541
%1 = ptrtoint ptr @vtable to i64 ; to keep @vtable alive
2642
call void @weird_ref_2()
43+
%2 = ptrtoint ptr @vtable2 to i64 ; to keep @vtable2 alive
44+
call void @weird_ref_4()
2745
ret void
2846
}
2947

0 commit comments

Comments
 (0)