Skip to content

Commit 13c8970

Browse files
authored
[CIR] Add support for non-virtual base class initialization (#148080)
This change adds support for initializing non-virtual base classes during the prologue of a derived class' constructor.
1 parent a265829 commit 13c8970

File tree

5 files changed

+261
-4
lines changed

5 files changed

+261
-4
lines changed

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,75 @@ static void emitMemberInitializer(CIRGenFunction &cgf,
117117
cgf.emitInitializerForField(field, lhs, memberInit->getInit());
118118
}
119119

120+
static bool isInitializerOfDynamicClass(const CXXCtorInitializer *baseInit) {
121+
const Type *baseType = baseInit->getBaseClass();
122+
const auto *baseClassDecl =
123+
cast<CXXRecordDecl>(baseType->castAs<RecordType>()->getDecl());
124+
return baseClassDecl->isDynamicClass();
125+
}
126+
127+
/// Gets the address of a direct base class within a complete object.
128+
/// This should only be used for (1) non-virtual bases or (2) virtual bases
129+
/// when the type is known to be complete (e.g. in complete destructors).
130+
///
131+
/// The object pointed to by 'thisAddr' is assumed to be non-null.
132+
Address CIRGenFunction::getAddressOfDirectBaseInCompleteClass(
133+
mlir::Location loc, Address thisAddr, const CXXRecordDecl *derived,
134+
const CXXRecordDecl *base, bool baseIsVirtual) {
135+
// 'thisAddr' must be a pointer (in some address space) to Derived.
136+
assert(thisAddr.getElementType() == convertType(derived));
137+
138+
// Compute the offset of the virtual base.
139+
CharUnits offset;
140+
const ASTRecordLayout &layout = getContext().getASTRecordLayout(derived);
141+
if (baseIsVirtual)
142+
offset = layout.getVBaseClassOffset(base);
143+
else
144+
offset = layout.getBaseClassOffset(base);
145+
146+
return builder.createBaseClassAddr(loc, thisAddr, convertType(base),
147+
offset.getQuantity(),
148+
/*assumeNotNull=*/true);
149+
}
150+
151+
void CIRGenFunction::emitBaseInitializer(mlir::Location loc,
152+
const CXXRecordDecl *classDecl,
153+
CXXCtorInitializer *baseInit) {
154+
assert(curFuncDecl && "loading 'this' without a func declaration?");
155+
assert(isa<CXXMethodDecl>(curFuncDecl));
156+
157+
assert(baseInit->isBaseInitializer() && "Must have base initializer!");
158+
159+
Address thisPtr = loadCXXThisAddress();
160+
161+
const Type *baseType = baseInit->getBaseClass();
162+
const auto *baseClassDecl =
163+
cast<CXXRecordDecl>(baseType->castAs<RecordType>()->getDecl());
164+
165+
bool isBaseVirtual = baseInit->isBaseVirtual();
166+
167+
// If the initializer for the base (other than the constructor
168+
// itself) accesses 'this' in any way, we need to initialize the
169+
// vtables.
170+
if (classDecl->isDynamicClass()) {
171+
cgm.errorNYI(loc, "emitBaseInitializer: dynamic class");
172+
return;
173+
}
174+
175+
// We can pretend to be a complete class because it only matters for
176+
// virtual bases, and we only do virtual bases for complete ctors.
177+
Address v = getAddressOfDirectBaseInCompleteClass(
178+
loc, thisPtr, classDecl, baseClassDecl, isBaseVirtual);
179+
assert(!cir::MissingFeatures::aggValueSlotGC());
180+
AggValueSlot aggSlot = AggValueSlot::forAddr(
181+
v, Qualifiers(), AggValueSlot::IsDestructed, AggValueSlot::IsNotAliased,
182+
getOverlapForBaseInit(classDecl, baseClassDecl, isBaseVirtual));
183+
184+
emitAggExpr(baseInit->getInit(), aggSlot);
185+
186+
assert(!cir::MissingFeatures::requiresCleanups());
187+
}
188+
120189
/// This routine generates necessary code to initialize base classes and
121190
/// non-static data members belonging to this constructor.
122191
void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
@@ -154,12 +223,29 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
154223
return;
155224
}
156225

157-
if ((*b)->isBaseInitializer()) {
226+
const mlir::Value oldThisValue = cxxThisValue;
227+
if (!constructVBases && (*b)->isBaseInitializer() && (*b)->isBaseVirtual()) {
158228
cgm.errorNYI(cd->getSourceRange(),
159-
"emitCtorPrologue: non-virtual base initializer");
229+
"emitCtorPrologue: virtual base initializer");
160230
return;
161231
}
162232

233+
// Handle non-virtual base initializers.
234+
for (; b != e && (*b)->isBaseInitializer(); b++) {
235+
assert(!(*b)->isBaseVirtual());
236+
237+
if (cgm.getCodeGenOpts().StrictVTablePointers &&
238+
cgm.getCodeGenOpts().OptimizationLevel > 0 &&
239+
isInitializerOfDynamicClass(*b)) {
240+
cgm.errorNYI(cd->getSourceRange(),
241+
"emitCtorPrologue: strict vtable pointers");
242+
return;
243+
}
244+
emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, *b);
245+
}
246+
247+
cxxThisValue = oldThisValue;
248+
163249
if (classDecl->isDynamicClass()) {
164250
cgm.errorNYI(cd->getSourceRange(),
165251
"emitCtorPrologue: initialize vtable pointers");

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,10 +1593,15 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
15931593
delegating = true;
15941594
break;
15951595
case CXXConstructionKind::VirtualBase:
1596-
case CXXConstructionKind::NonVirtualBase:
1596+
// This should just set 'forVirtualBase' to true and fall through, but
1597+
// virtual base class support is otherwise missing, so this needs to wait
1598+
// until it can be tested.
15971599
cgm.errorNYI(e->getSourceRange(),
1598-
"emitCXXConstructExpr: other construction kind");
1600+
"emitCXXConstructExpr: virtual base constructor");
15991601
return;
1602+
case CXXConstructionKind::NonVirtualBase:
1603+
type = Ctor_Base;
1604+
break;
16001605
}
16011606

16021607
emitCXXConstructorCall(cd, type, forVirtualBase, delegating, dest, e);

clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
1717

1818
#include "clang/AST/Expr.h"
19+
#include "clang/AST/RecordLayout.h"
1920
#include "clang/AST/StmtVisitor.h"
2021
#include <cstdint>
2122

@@ -362,6 +363,28 @@ void AggExprEmitter::visitCXXParenListOrInitListExpr(
362363
"visitCXXParenListOrInitListExpr Record or VariableSizeArray type");
363364
}
364365

366+
// TODO(cir): This could be shared with classic codegen.
367+
AggValueSlot::Overlap_t CIRGenFunction::getOverlapForBaseInit(
368+
const CXXRecordDecl *rd, const CXXRecordDecl *baseRD, bool isVirtual) {
369+
// If the most-derived object is a field declared with [[no_unique_address]],
370+
// the tail padding of any virtual base could be reused for other subobjects
371+
// of that field's class.
372+
if (isVirtual)
373+
return AggValueSlot::MayOverlap;
374+
375+
// If the base class is laid out entirely within the nvsize of the derived
376+
// class, its tail padding cannot yet be initialized, so we can issue
377+
// stores at the full width of the base class.
378+
const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
379+
if (layout.getBaseClassOffset(baseRD) +
380+
getContext().getASTRecordLayout(baseRD).getSize() <=
381+
layout.getNonVirtualSize())
382+
return AggValueSlot::DoesNotOverlap;
383+
384+
// The tail padding may contain values we need to preserve.
385+
return AggValueSlot::MayOverlap;
386+
}
387+
365388
void CIRGenFunction::emitAggExpr(const Expr *e, AggValueSlot slot) {
366389
AggExprEmitter(*this, slot).Visit(const_cast<Expr *>(e));
367390
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,19 @@ class CIRGenFunction : public CIRGenTypeCache {
562562
}
563563
Address loadCXXThisAddress();
564564

565+
/// Convert the given pointer to a complete class to the given direct base.
566+
Address getAddressOfDirectBaseInCompleteClass(mlir::Location loc,
567+
Address value,
568+
const CXXRecordDecl *derived,
569+
const CXXRecordDecl *base,
570+
bool baseIsVirtual);
571+
572+
/// Determine whether a base class initialization may overlap some other
573+
/// object.
574+
AggValueSlot::Overlap_t getOverlapForBaseInit(const CXXRecordDecl *rd,
575+
const CXXRecordDecl *baseRD,
576+
bool isVirtual);
577+
565578
/// Get an appropriate 'undef' rvalue for the given type.
566579
/// TODO: What's the equivalent for MLIR? Currently we're only using this for
567580
/// void types so it just returns RValue::get(nullptr) but it'll need
@@ -762,6 +775,9 @@ class CIRGenFunction : public CIRGenTypeCache {
762775
void emitAutoVarCleanups(const AutoVarEmission &emission);
763776
void emitAutoVarInit(const AutoVarEmission &emission);
764777

778+
void emitBaseInitializer(mlir::Location loc, const CXXRecordDecl *classDecl,
779+
CXXCtorInitializer *baseInit);
780+
765781
LValue emitBinaryOperatorLValue(const BinaryOperator *e);
766782

767783
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);

clang/test/CIR/CodeGen/ctor.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,130 @@ void init_union() {
219219
// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca {{.*}} ["s", init]
220220
// CHECK-NEXT: cir.call @_ZN14UnionInitStrukC1Ev(%[[S_ADDR]])
221221
// CHECK-NEXT: cir.return
222+
223+
struct Base {
224+
int a;
225+
Base(int val) : a(val) {}
226+
};
227+
228+
struct Derived : Base {
229+
Derived(int val) : Base(val) {}
230+
};
231+
232+
void test_derived() {
233+
Derived d(1);
234+
}
235+
236+
// CHECK: cir.func{{.*}} @_ZN4BaseC2Ei(%arg0: !cir.ptr<!rec_Base> {{.*}}, %arg1: !s32i
237+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
238+
// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
239+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
240+
// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
241+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
242+
// CHECK-NEXT: %[[A_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "a"}
243+
// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
244+
// CHECK-NEXT: cir.store{{.*}} %[[VAL]], %[[A_ADDR]]
245+
// CHECK-NEXT: cir.return
246+
247+
// CHECK: cir.func{{.*}} @_ZN7DerivedC2Ei(%arg0: !cir.ptr<!rec_Derived> {{.*}}, %arg1: !s32i
248+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
249+
// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
250+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
251+
// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
252+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
253+
// CHECK-NEXT: %[[BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
254+
// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
255+
// CHECK-NEXT: cir.call @_ZN4BaseC2Ei(%[[BASE]], %[[VAL]])
256+
// CHECK-NEXT: cir.return
257+
258+
// CHECK: cir.func{{.*}} @_ZN7DerivedC1Ei(%arg0: !cir.ptr<!rec_Derived> {{.*}}, %arg1: !s32i
259+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
260+
// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
261+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
262+
// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
263+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
264+
// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
265+
// CHECK-NEXT: cir.call @_ZN7DerivedC2Ei(%[[THIS]], %[[VAL]])
266+
// CHECK-NEXT: cir.return
267+
268+
// CHECK: cir.func{{.*}} @_Z12test_derivedv
269+
// CHECK-NEXT: %[[D_ADDR:.*]] = cir.alloca {{.*}} ["d", init]
270+
// CHECK-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
271+
// CHECK-NEXT: cir.call @_ZN7DerivedC1Ei(%[[D_ADDR]], %[[ONE]])
272+
// CHECK-NEXT: cir.return
273+
274+
struct Base2 {
275+
int b;
276+
Base2(int val) : b(val) {}
277+
};
278+
279+
struct Derived2 : Base, Base2 {
280+
int c;
281+
Derived2(int val1, int val2, int val3) : Base(val1), Base2(val2), c(val3) {}
282+
};
283+
284+
void test_derived2() {
285+
Derived2 d(1, 2, 3);
286+
}
287+
288+
// CHECK: cir.func{{.*}} @_ZN5Base2C2Ei(%arg0: !cir.ptr<!rec_Base2> {{.*}}, %arg1: !s32i
289+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
290+
// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
291+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
292+
// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
293+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
294+
// CHECK-NEXT: %[[B_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "b"}
295+
// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
296+
// CHECK-NEXT: cir.store{{.*}} %[[VAL]], %[[B_ADDR]]
297+
// CHECK-NEXT: cir.return
298+
299+
// CHECK: cir.func{{.*}} @_ZN8Derived2C2Eiii(%arg0: !cir.ptr<!rec_Derived2>
300+
// CHECK-SAME: %arg1: !s32i
301+
// CHECK-SAME: %arg2: !s32i
302+
// CHECK-SAME: %arg3: !s32i
303+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
304+
// CHECK-NEXT: %[[VAL1_ADDR:.*]] = cir.alloca {{.*}} ["val1", init]
305+
// CHECK-NEXT: %[[VAL2_ADDR:.*]] = cir.alloca {{.*}} ["val2", init]
306+
// CHECK-NEXT: %[[VAL3_ADDR:.*]] = cir.alloca {{.*}} ["val3", init]
307+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
308+
// CHECK-NEXT: cir.store %arg1, %[[VAL1_ADDR]]
309+
// CHECK-NEXT: cir.store %arg2, %[[VAL2_ADDR]]
310+
// CHECK-NEXT: cir.store %arg3, %[[VAL3_ADDR]]
311+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
312+
// CHECK-NEXT: %[[BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived2> nonnull [0] -> !cir.ptr<!rec_Base>
313+
// CHECK-NEXT: %[[VAL1:.*]] = cir.load{{.*}} %[[VAL1_ADDR]]
314+
// CHECK-NEXT: cir.call @_ZN4BaseC2Ei(%[[BASE]], %[[VAL1]])
315+
// CHECK-NEXT: %[[BASE2:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived2> nonnull [4] -> !cir.ptr<!rec_Base2>
316+
// CHECK-NEXT: %[[VAL2:.*]] = cir.load{{.*}} %[[VAL2_ADDR]]
317+
// CHECK-NEXT: cir.call @_ZN5Base2C2Ei(%[[BASE2]], %[[VAL2]])
318+
// CHECK-NEXT: %[[C_ADDR:.*]] = cir.get_member %[[THIS]][2] {name = "c"}
319+
// CHECK-NEXT: %[[VAL3:.*]] = cir.load{{.*}} %[[VAL3_ADDR]]
320+
// CHECK-NEXT: cir.store{{.*}} %[[VAL3]], %[[C_ADDR]]
321+
// CHECK-NEXT: cir.return
322+
323+
// CHECK: cir.func{{.*}} @_ZN8Derived2C1Eiii(%arg0: !cir.ptr<!rec_Derived2>
324+
// CHECK-SAME: %arg1: !s32i
325+
// CHECK-SAME: %arg2: !s32i
326+
// CHECK-SAME: %arg3: !s32i
327+
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
328+
// CHECK-NEXT: %[[VAL1_ADDR:.*]] = cir.alloca {{.*}} ["val1", init]
329+
// CHECK-NEXT: %[[VAL2_ADDR:.*]] = cir.alloca {{.*}} ["val2", init]
330+
// CHECK-NEXT: %[[VAL3_ADDR:.*]] = cir.alloca {{.*}} ["val3", init]
331+
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
332+
// CHECK-NEXT: cir.store %arg1, %[[VAL1_ADDR]]
333+
// CHECK-NEXT: cir.store %arg2, %[[VAL2_ADDR]]
334+
// CHECK-NEXT: cir.store %arg3, %[[VAL3_ADDR]]
335+
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
336+
// CHECK-NEXT: %[[VAL1:.*]] = cir.load{{.*}} %[[VAL1_ADDR]]
337+
// CHECK-NEXT: %[[VAL2:.*]] = cir.load{{.*}} %[[VAL2_ADDR]]
338+
// CHECK-NEXT: %[[VAL3:.*]] = cir.load{{.*}} %[[VAL3_ADDR]]
339+
// CHECK-NEXT: cir.call @_ZN8Derived2C2Eiii(%[[THIS]], %[[VAL1]], %[[VAL2]], %[[VAL3]])
340+
// CHECK-NEXT: cir.return
341+
342+
// CHECK: cir.func{{.*}} @_Z13test_derived2v
343+
// CHECK-NEXT: %[[D_ADDR:.*]] = cir.alloca {{.*}} ["d", init]
344+
// CHECK-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
345+
// CHECK-NEXT: %[[TWO:.*]] = cir.const #cir.int<2> : !s32i
346+
// CHECK-NEXT: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
347+
// CHECK-NEXT: cir.call @_ZN8Derived2C1Eiii(%[[D_ADDR]], %[[ONE]], %[[TWO]], %[[THREE]])
348+
// CHECK-NEXT: cir.return

0 commit comments

Comments
 (0)