Skip to content

Commit 5d54d34

Browse files
authored
[CIR] Add support for initializing classes with multiple vtables (llvm#155275)
This adds support for initializing the vptr members in a class that requires multiple vtables because of multiple inheritence. This still does not handle virtual bases.
1 parent 71ce079 commit 5d54d34

File tree

3 files changed

+189
-52
lines changed

3 files changed

+189
-52
lines changed

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "CIRGenFunction.h"
1515
#include "CIRGenValue.h"
1616

17+
#include "clang/AST/EvaluatedExprVisitor.h"
1718
#include "clang/AST/ExprCXX.h"
1819
#include "clang/AST/RecordLayout.h"
1920
#include "clang/AST/Type.h"
@@ -124,6 +125,32 @@ static bool isInitializerOfDynamicClass(const CXXCtorInitializer *baseInit) {
124125
return baseClassDecl->isDynamicClass();
125126
}
126127

128+
namespace {
129+
/// A visitor which checks whether an initializer uses 'this' in a
130+
/// way which requires the vtable to be properly set.
131+
struct DynamicThisUseChecker
132+
: ConstEvaluatedExprVisitor<DynamicThisUseChecker> {
133+
using super = ConstEvaluatedExprVisitor<DynamicThisUseChecker>;
134+
135+
bool usesThis = false;
136+
137+
DynamicThisUseChecker(const ASTContext &c) : super(c) {}
138+
139+
// Black-list all explicit and implicit references to 'this'.
140+
//
141+
// Do we need to worry about external references to 'this' derived
142+
// from arbitrary code? If so, then anything which runs arbitrary
143+
// external code might potentially access the vtable.
144+
void VisitCXXThisExpr(const CXXThisExpr *e) { usesThis = true; }
145+
};
146+
} // end anonymous namespace
147+
148+
static bool baseInitializerUsesThis(ASTContext &c, const Expr *init) {
149+
DynamicThisUseChecker checker(c);
150+
checker.Visit(init);
151+
return checker.usesThis;
152+
}
153+
127154
/// Gets the address of a direct base class within a complete object.
128155
/// This should only be used for (1) non-virtual bases or (2) virtual bases
129156
/// when the type is known to be complete (e.g. in complete destructors).
@@ -166,10 +193,8 @@ void CIRGenFunction::emitBaseInitializer(mlir::Location loc,
166193
// If the initializer for the base (other than the constructor
167194
// itself) accesses 'this' in any way, we need to initialize the
168195
// vtables.
169-
if (classDecl->isDynamicClass()) {
170-
cgm.errorNYI(loc, "emitBaseInitializer: dynamic class");
171-
return;
172-
}
196+
if (baseInitializerUsesThis(getContext(), baseInit->getInit()))
197+
initializeVTablePointers(loc, classDecl);
173198

174199
// We can pretend to be a complete class because it only matters for
175200
// virtual bases, and we only do virtual bases for complete ctors.
@@ -260,6 +285,34 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
260285
}
261286
}
262287

288+
static Address applyNonVirtualAndVirtualOffset(
289+
mlir::Location loc, CIRGenFunction &cgf, Address addr,
290+
CharUnits nonVirtualOffset, mlir::Value virtualOffset,
291+
const CXXRecordDecl *derivedClass, const CXXRecordDecl *nearestVBase,
292+
mlir::Type baseValueTy = {}, bool assumeNotNull = true) {
293+
// Assert that we have something to do.
294+
assert(!nonVirtualOffset.isZero() || virtualOffset != nullptr);
295+
296+
// Compute the offset from the static and dynamic components.
297+
if (!nonVirtualOffset.isZero()) {
298+
if (virtualOffset) {
299+
cgf.cgm.errorNYI(
300+
loc,
301+
"applyNonVirtualAndVirtualOffset: virtual and non-virtual offset");
302+
return Address::invalid();
303+
} else {
304+
assert(baseValueTy && "expected base type");
305+
// If no virtualOffset is present this is the final stop.
306+
return cgf.getBuilder().createBaseClassAddr(
307+
loc, addr, baseValueTy, nonVirtualOffset.getQuantity(),
308+
assumeNotNull);
309+
}
310+
}
311+
312+
cgf.cgm.errorNYI(loc, "applyNonVirtualAndVirtualOffset: virtual offset");
313+
return Address::invalid();
314+
}
315+
263316
void CIRGenFunction::initializeVTablePointer(mlir::Location loc,
264317
const VPtr &vptr) {
265318
// Compute the address point.
@@ -287,8 +340,9 @@ void CIRGenFunction::initializeVTablePointer(mlir::Location loc,
287340
// Apply the offsets.
288341
Address classAddr = loadCXXThisAddress();
289342
if (!nonVirtualOffset.isZero() || virtualOffset) {
290-
cgm.errorNYI(loc,
291-
"initializeVTablePointer: non-virtual and virtual offset");
343+
classAddr = applyNonVirtualAndVirtualOffset(
344+
loc, *this, classAddr, nonVirtualOffset, virtualOffset,
345+
vptr.vtableClass, vptr.nearestVBase, baseValueTy);
292346
}
293347

294348
// Finally, store the address point. Use the same CIR types as the field.
@@ -322,10 +376,11 @@ void CIRGenFunction::initializeVTablePointers(mlir::Location loc,
322376
CIRGenFunction::VPtrsVector
323377
CIRGenFunction::getVTablePointers(const CXXRecordDecl *vtableClass) {
324378
CIRGenFunction::VPtrsVector vptrsResult;
379+
VisitedVirtualBasesSetTy vbases;
325380
getVTablePointers(BaseSubobject(vtableClass, CharUnits::Zero()),
326381
/*NearestVBase=*/nullptr,
327382
/*OffsetFromNearestVBase=*/CharUnits::Zero(),
328-
/*BaseIsNonVirtualPrimaryBase=*/false, vtableClass,
383+
/*BaseIsNonVirtualPrimaryBase=*/false, vtableClass, vbases,
329384
vptrsResult);
330385
return vptrsResult;
331386
}
@@ -335,6 +390,7 @@ void CIRGenFunction::getVTablePointers(BaseSubobject base,
335390
CharUnits offsetFromNearestVBase,
336391
bool baseIsNonVirtualPrimaryBase,
337392
const CXXRecordDecl *vtableClass,
393+
VisitedVirtualBasesSetTy &vbases,
338394
VPtrsVector &vptrs) {
339395
// If this base is a non-virtual primary base the address point has already
340396
// been set.
@@ -346,8 +402,39 @@ void CIRGenFunction::getVTablePointers(BaseSubobject base,
346402

347403
const CXXRecordDecl *rd = base.getBase();
348404

349-
if (rd->getNumBases())
350-
cgm.errorNYI(rd->getSourceRange(), "getVTablePointers: traverse bases");
405+
for (const auto &nextBase : rd->bases()) {
406+
const auto *baseDecl =
407+
cast<CXXRecordDecl>(
408+
nextBase.getType()->castAs<RecordType>()->getOriginalDecl())
409+
->getDefinitionOrSelf();
410+
411+
// Ignore classes without a vtable.
412+
if (!baseDecl->isDynamicClass())
413+
continue;
414+
415+
CharUnits baseOffset;
416+
CharUnits baseOffsetFromNearestVBase;
417+
bool baseDeclIsNonVirtualPrimaryBase;
418+
const CXXRecordDecl *nextBaseDecl;
419+
420+
if (nextBase.isVirtual()) {
421+
cgm.errorNYI(rd->getSourceRange(), "getVTablePointers: virtual base");
422+
return;
423+
} else {
424+
const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
425+
426+
nextBaseDecl = baseDecl;
427+
baseOffset = base.getBaseOffset() + layout.getBaseClassOffset(baseDecl);
428+
baseOffsetFromNearestVBase =
429+
offsetFromNearestVBase + layout.getBaseClassOffset(baseDecl);
430+
baseDeclIsNonVirtualPrimaryBase = layout.getPrimaryBase() == baseDecl;
431+
}
432+
433+
getVTablePointers(BaseSubobject(baseDecl, baseOffset), nextBaseDecl,
434+
baseOffsetFromNearestVBase,
435+
baseDeclIsNonVirtualPrimaryBase, vtableClass, vbases,
436+
vptrs);
437+
}
351438
}
352439

353440
Address CIRGenFunction::loadCXXThisAddress() {

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,14 +574,17 @@ class CIRGenFunction : public CIRGenTypeCache {
574574
const clang::CXXRecordDecl *vtableClass;
575575
};
576576

577+
using VisitedVirtualBasesSetTy =
578+
llvm::SmallPtrSet<const clang::CXXRecordDecl *, 4>;
579+
577580
using VPtrsVector = llvm::SmallVector<VPtr, 4>;
578581
VPtrsVector getVTablePointers(const clang::CXXRecordDecl *vtableClass);
579582
void getVTablePointers(clang::BaseSubobject base,
580583
const clang::CXXRecordDecl *nearestVBase,
581584
clang::CharUnits offsetFromNearestVBase,
582585
bool baseIsNonVirtualPrimaryBase,
583586
const clang::CXXRecordDecl *vtableClass,
584-
VPtrsVector &vptrs);
587+
VisitedVirtualBasesSetTy &vbases, VPtrsVector &vptrs);
585588
/// Return the Value of the vtable pointer member pointed to by thisAddr.
586589
mlir::Value getVTablePtr(mlir::Location loc, Address thisAddr,
587590
const clang::CXXRecordDecl *vtableClass);

clang/test/CIR/CodeGen/multi-vtable.cpp

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Father {
2222

2323
class Child : public Mother, public Father {
2424
public:
25+
Child();
2526
void MotherKey() override;
2627
};
2728

@@ -36,6 +37,50 @@ void Child::MotherKey() {}
3637
// CIR-DAG: !rec_Mother = !cir.record<class "Mother" {!cir.vptr}
3738
// CIR-DAG: !rec_Child = !cir.record<class "Child" {!rec_Mother, !rec_Father}
3839

40+
// Child vtable
41+
42+
// CIR: cir.global "private" external @_ZTV5Child = #cir.vtable<{
43+
// CIR-SAME: #cir.const_array<[
44+
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
45+
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
46+
// CIR-SAME: #cir.global_view<@_ZN5Child9MotherKeyEv> : !cir.ptr<!u8i>,
47+
// CIR-SAME: #cir.global_view<@_ZN6Mother12MotherNonKeyEv> : !cir.ptr<!u8i>
48+
// CIR-SAME: ]> : !cir.array<!cir.ptr<!u8i> x 4>,
49+
// CIR-SAME: #cir.const_array<[
50+
// CIR-SAME: #cir.ptr<-8 : i64> : !cir.ptr<!u8i>,
51+
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
52+
// CIR-SAME: #cir.global_view<@_ZN6Father9FatherKeyEv> : !cir.ptr<!u8i>
53+
// CIR-SAME: ]> : !cir.array<!cir.ptr<!u8i> x 3>
54+
// CIR-SAME: }> : [[CHILD_VTABLE_TYPE]]
55+
56+
// LLVM: @_ZTV5Child = global { [4 x ptr], [3 x ptr] } {
57+
// LLVM-SAME: [4 x ptr] [
58+
// LLVM-SAME: ptr null,
59+
// LLVM-SAME: ptr null,
60+
// LLVM-SAME: ptr @_ZN5Child9MotherKeyEv,
61+
// LLVM-SAME: ptr @_ZN6Mother12MotherNonKeyEv
62+
// LLVM-SAME: ],
63+
// LLVM-SAME: [3 x ptr] [
64+
// LLVM-SAME: ptr inttoptr (i64 -8 to ptr),
65+
// LLVM-SAME: ptr null,
66+
// LLVM-SAME: ptr @_ZN6Father9FatherKeyEv
67+
// LLVM-SAME: ]
68+
// LLVM-SAME: }
69+
70+
// OGCG: @_ZTV5Child = unnamed_addr constant { [4 x ptr], [3 x ptr] } {
71+
// OGCG-SAME: [4 x ptr] [
72+
// OGCG-SAME: ptr null,
73+
// OGCG-SAME: ptr null,
74+
// OGCG-SAME: ptr @_ZN5Child9MotherKeyEv,
75+
// OGCG-SAME: ptr @_ZN6Mother12MotherNonKeyEv
76+
// OGCG-SAME: ],
77+
// OGCG-SAME: [3 x ptr] [
78+
// OGCG-SAME: ptr inttoptr (i64 -8 to ptr),
79+
// OGCG-SAME: ptr null,
80+
// OGCG-SAME: ptr @_ZN6Father9FatherKeyEv
81+
// OGCG-SAME: ]
82+
// OGCG-SAME: }
83+
3984
// Mother vtable
4085

4186
// CIR: cir.global "private" external @_ZTV6Mother = #cir.vtable<{
@@ -91,46 +136,48 @@ void Child::MotherKey() {}
91136
// OGCG-SAME: ]
92137
// OGCG-SAME: }
93138

94-
// Child vtable
95-
96-
// CIR: cir.global "private" external @_ZTV5Child = #cir.vtable<{
97-
// CIR-SAME: #cir.const_array<[
98-
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
99-
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
100-
// CIR-SAME: #cir.global_view<@_ZN5Child9MotherKeyEv> : !cir.ptr<!u8i>,
101-
// CIR-SAME: #cir.global_view<@_ZN6Mother12MotherNonKeyEv> : !cir.ptr<!u8i>
102-
// CIR-SAME: ]> : !cir.array<!cir.ptr<!u8i> x 4>,
103-
// CIR-SAME: #cir.const_array<[
104-
// CIR-SAME: #cir.ptr<-8 : i64> : !cir.ptr<!u8i>,
105-
// CIR-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
106-
// CIR-SAME: #cir.global_view<@_ZN6Father9FatherKeyEv> : !cir.ptr<!u8i>
107-
// CIR-SAME: ]> : !cir.array<!cir.ptr<!u8i> x 3>
108-
// CIR-SAME: }> : [[CHILD_VTABLE_TYPE]]
109139

110-
// LLVM: @_ZTV5Child = global { [4 x ptr], [3 x ptr] } {
111-
// LLVM-SAME: [4 x ptr] [
112-
// LLVM-SAME: ptr null,
113-
// LLVM-SAME: ptr null,
114-
// LLVM-SAME: ptr @_ZN5Child9MotherKeyEv,
115-
// LLVM-SAME: ptr @_ZN6Mother12MotherNonKeyEv
116-
// LLVM-SAME: ],
117-
// LLVM-SAME: [3 x ptr] [
118-
// LLVM-SAME: ptr inttoptr (i64 -8 to ptr),
119-
// LLVM-SAME: ptr null,
120-
// LLVM-SAME: ptr @_ZN6Father9FatherKeyEv
121-
// LLVM-SAME: ]
122-
// LLVM-SAME: }
123-
124-
// OGCG: @_ZTV5Child = unnamed_addr constant { [4 x ptr], [3 x ptr] } {
125-
// OGCG-SAME: [4 x ptr] [
126-
// OGCG-SAME: ptr null,
127-
// OGCG-SAME: ptr null,
128-
// OGCG-SAME: ptr @_ZN5Child9MotherKeyEv,
129-
// OGCG-SAME: ptr @_ZN6Mother12MotherNonKeyEv
130-
// OGCG-SAME: ],
131-
// OGCG-SAME: [3 x ptr] [
132-
// OGCG-SAME: ptr inttoptr (i64 -8 to ptr),
133-
// OGCG-SAME: ptr null,
134-
// OGCG-SAME: ptr @_ZN6Father9FatherKeyEv
135-
// OGCG-SAME: ]
136-
// OGCG-SAME: }
140+
Child::Child() {}
141+
142+
// CIR: cir.func {{.*}} @_ZN5ChildC2Ev(%[[THIS_ARG:.*]]: !cir.ptr<!rec_Child>
143+
// CIR: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
144+
// CIR: cir.store %[[THIS_ARG]], %[[THIS_ADDR]]
145+
// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]]
146+
// CIR: %[[MOTHER_BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Child> nonnull [0] -> !cir.ptr<!rec_Mother>
147+
// CIR: cir.call @_ZN6MotherC2Ev(%[[MOTHER_BASE]]) nothrow : (!cir.ptr<!rec_Mother>) -> ()
148+
// CIR: %[[FATHER_BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Child> nonnull [8] -> !cir.ptr<!rec_Father>
149+
// CIR: cir.call @_ZN6FatherC2Ev(%[[FATHER_BASE]]) nothrow : (!cir.ptr<!rec_Father>) -> ()
150+
// CIR: %[[CHILD_VPTR:.*]] = cir.vtable.address_point(@_ZTV5Child, address_point = <index = 0, offset = 2>) : !cir.vptr
151+
// CIR: %[[CHILD_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_Child> -> !cir.ptr<!cir.vptr>
152+
// CIR: cir.store{{.*}} %[[CHILD_VPTR]], %[[CHILD_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr>
153+
// CIR: %[[FATHER_IN_CHILD_VPTR:.*]] = cir.vtable.address_point(@_ZTV5Child, address_point = <index = 1, offset = 2>) : !cir.vptr
154+
// CIR: %[[FATHER_BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Child> nonnull [8] -> !cir.ptr<!rec_Father>
155+
// CIR: %[[FATHER_IN_CHILD_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[FATHER_BASE]] : !cir.ptr<!rec_Father> -> !cir.ptr<!cir.vptr>
156+
// CIR: cir.store{{.*}} %[[FATHER_IN_CHILD_VPTR]], %[[FATHER_IN_CHILD_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr>
157+
// CIR: cir.return
158+
159+
// The GEP instructions are different between LLVM and OGCG, but they calculate the same addresses.
160+
161+
// LLVM: define{{.*}} void @_ZN5ChildC2Ev(ptr{{.*}} %[[THIS_ARG:.*]])
162+
// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
163+
// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
164+
// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
165+
// LLVM: call void @_ZN6MotherC2Ev(ptr{{.*}} %[[THIS]])
166+
// LLVM: %[[FATHER_BASE:.*]] = getelementptr{{.*}} i8, ptr %[[THIS]], i32 8
167+
// LLVM: call void @_ZN6FatherC2Ev(ptr{{.*}} %[[FATHER_BASE]])
168+
// LLVM: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV5Child, i64 16), ptr %[[THIS]]
169+
// LLVM: %[[FATHER_BASE:.*]] = getelementptr{{.*}} i8, ptr %[[THIS]], i32 8
170+
// LLVM: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV5Child, i64 48), ptr %[[FATHER_BASE]]
171+
// LLVM: ret void
172+
173+
// OGCG: define{{.*}} void @_ZN5ChildC2Ev(ptr{{.*}} %[[THIS_ARG:.*]])
174+
// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
175+
// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
176+
// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
177+
// OGCG: call void @_ZN6MotherC2Ev(ptr {{.*}} %[[THIS]])
178+
// OGCG: %[[FATHER_BASE:.*]] = getelementptr{{.*}} i8, ptr %[[THIS]], i64 8
179+
// OGCG: call void @_ZN6FatherC2Ev(ptr{{.*}} %[[FATHER_BASE]])
180+
// OGCG: store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV5Child, i32 0, i32 0, i32 2), ptr %[[THIS]]
181+
// OGCG: %[[FATHER_BASE:.*]] = getelementptr{{.*}} i8, ptr %[[THIS]], i64 8
182+
// OGCG: store ptr getelementptr inbounds inrange(-16, 8) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV5Child, i32 0, i32 1, i32 2), ptr %[[FATHER_BASE]]
183+
// OGCG: ret void

0 commit comments

Comments
 (0)