Skip to content

Commit 3f55f8b

Browse files
authored
[CIR] Handle non-empty null base class initialization (#168646)
This implements null base class initialization for non-empty bases.
1 parent 90ea49a commit 3f55f8b

File tree

7 files changed

+236
-10
lines changed

7 files changed

+236
-10
lines changed

clang/include/clang/CIR/Dialect/IR/CIRTypes.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,9 @@ def CIR_RecordType : CIR_Type<"Record", "record", [
657657
}
658658
llvm_unreachable("Invalid value for RecordType::getKind()");
659659
}
660+
mlir::Type getElementType(size_t idx) {
661+
return getMembers()[idx];
662+
}
660663
std::string getPrefixedName() {
661664
return getKindAsStr() + "." + getName().getValue().str();
662665
}

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
212212
&ignored);
213213
return fv.bitwiseIsEqual(fpVal);
214214
}
215+
if (const auto recordVal = mlir::dyn_cast<cir::ConstRecordAttr>(attr)) {
216+
for (const auto elt : recordVal.getMembers()) {
217+
// FIXME(cir): the record's ID should not be considered a member.
218+
if (mlir::isa<mlir::StringAttr>(elt))
219+
continue;
220+
if (!isNullValue(elt))
221+
return false;
222+
}
223+
return true;
224+
}
215225

216226
if (const auto arrayVal = mlir::dyn_cast<cir::ConstArrayAttr>(attr)) {
217227
if (mlir::isa<mlir::StringAttr>(arrayVal.getElts()))

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,46 @@ static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
240240
if (base->isEmpty())
241241
return;
242242

243-
cgf.cgm.errorNYI(base->getSourceRange(),
244-
"emitNullBaseClassInitialization: not empty");
243+
const ASTRecordLayout &layout = cgf.getContext().getASTRecordLayout(base);
244+
CharUnits nvSize = layout.getNonVirtualSize();
245+
246+
// We cannot simply zero-initialize the entire base sub-object if vbptrs are
247+
// present, they are initialized by the most derived class before calling the
248+
// constructor.
249+
SmallVector<std::pair<CharUnits, CharUnits>, 1> stores;
250+
stores.emplace_back(CharUnits::Zero(), nvSize);
251+
252+
// Each store is split by the existence of a vbptr.
253+
// TODO(cir): This only needs handling for the MS CXXABI.
254+
assert(!cir::MissingFeatures::msabi());
255+
256+
// If the type contains a pointer to data member we can't memset it to zero.
257+
// Instead, create a null constant and copy it to the destination.
258+
// TODO: there are other patterns besides zero that we can usefully memset,
259+
// like -1, which happens to be the pattern used by member-pointers.
260+
// TODO: isZeroInitializable can be over-conservative in the case where a
261+
// virtual base contains a member pointer.
262+
mlir::TypedAttr nullConstantForBase = cgf.cgm.emitNullConstantForBase(base);
263+
if (!cgf.getBuilder().isNullValue(nullConstantForBase)) {
264+
cgf.cgm.errorNYI(
265+
base->getSourceRange(),
266+
"emitNullBaseClassInitialization: base constant is not null");
267+
} else {
268+
// Otherwise, just memset the whole thing to zero. This is legal
269+
// because in LLVM, all default initializers (other than the ones we just
270+
// handled above) are guaranteed to have a bit pattern of all zeros.
271+
// TODO(cir): When the MS CXXABI is supported, we will need to iterate over
272+
// `stores` and create a separate memset for each one. For now, we know that
273+
// there will only be one store and it will begin at offset zero, so that
274+
// simplifies this code considerably.
275+
assert(stores.size() == 1 && "Expected only one store");
276+
assert(stores[0].first == CharUnits::Zero() &&
277+
"Expected store to begin at offset zero");
278+
CIRGenBuilderTy builder = cgf.getBuilder();
279+
mlir::Location loc = cgf.getLoc(base->getBeginLoc());
280+
builder.createStore(loc, builder.getConstant(loc, nullConstantForBase),
281+
destPtr);
282+
}
245283
}
246284

247285
void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,

clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,101 @@ ConstantEmitter::~ConstantEmitter() {
15211521
"not finalized after being initialized for non-abstract emission");
15221522
}
15231523

1524+
static mlir::TypedAttr emitNullConstantForBase(CIRGenModule &cgm,
1525+
mlir::Type baseType,
1526+
const CXXRecordDecl *baseDecl);
1527+
1528+
static mlir::TypedAttr emitNullConstant(CIRGenModule &cgm, const RecordDecl *rd,
1529+
bool asCompleteObject) {
1530+
const CIRGenRecordLayout &layout = cgm.getTypes().getCIRGenRecordLayout(rd);
1531+
mlir::Type ty = (asCompleteObject ? layout.getCIRType()
1532+
: layout.getBaseSubobjectCIRType());
1533+
auto recordTy = mlir::cast<cir::RecordType>(ty);
1534+
1535+
unsigned numElements = recordTy.getNumElements();
1536+
SmallVector<mlir::Attribute> elements(numElements);
1537+
1538+
auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
1539+
// Fill in all the bases.
1540+
if (cxxrd) {
1541+
for (const CXXBaseSpecifier &base : cxxrd->bases()) {
1542+
if (base.isVirtual()) {
1543+
// Ignore virtual bases; if we're laying out for a complete
1544+
// object, we'll lay these out later.
1545+
continue;
1546+
}
1547+
1548+
const auto *baseDecl = base.getType()->castAsCXXRecordDecl();
1549+
// Ignore empty bases.
1550+
if (isEmptyRecordForLayout(cgm.getASTContext(), base.getType()) ||
1551+
cgm.getASTContext()
1552+
.getASTRecordLayout(baseDecl)
1553+
.getNonVirtualSize()
1554+
.isZero())
1555+
continue;
1556+
1557+
unsigned fieldIndex = layout.getNonVirtualBaseCIRFieldNo(baseDecl);
1558+
mlir::Type baseType = recordTy.getElementType(fieldIndex);
1559+
elements[fieldIndex] = emitNullConstantForBase(cgm, baseType, baseDecl);
1560+
}
1561+
}
1562+
1563+
// Fill in all the fields.
1564+
for (const FieldDecl *field : rd->fields()) {
1565+
// Fill in non-bitfields. (Bitfields always use a zero pattern, which we
1566+
// will fill in later.)
1567+
if (!field->isBitField() &&
1568+
!isEmptyFieldForLayout(cgm.getASTContext(), field)) {
1569+
unsigned fieldIndex = layout.getCIRFieldNo(field);
1570+
elements[fieldIndex] = cgm.emitNullConstantAttr(field->getType());
1571+
}
1572+
1573+
// For unions, stop after the first named field.
1574+
if (rd->isUnion()) {
1575+
if (field->getIdentifier())
1576+
break;
1577+
if (const auto *fieldRD = field->getType()->getAsRecordDecl())
1578+
if (fieldRD->findFirstNamedDataMember())
1579+
break;
1580+
}
1581+
}
1582+
1583+
// Fill in the virtual bases, if we're working with the complete object.
1584+
if (cxxrd && asCompleteObject) {
1585+
for ([[maybe_unused]] const CXXBaseSpecifier &vbase : cxxrd->vbases()) {
1586+
cgm.errorNYI(vbase.getSourceRange(), "emitNullConstant: virtual base");
1587+
return {};
1588+
}
1589+
}
1590+
1591+
// Now go through all other fields and zero them out.
1592+
for (unsigned i = 0; i != numElements; ++i) {
1593+
if (!elements[i]) {
1594+
cgm.errorNYI(rd->getSourceRange(), "emitNullConstant: field not zeroed");
1595+
return {};
1596+
}
1597+
}
1598+
1599+
mlir::MLIRContext *mlirContext = recordTy.getContext();
1600+
return cir::ConstRecordAttr::get(recordTy,
1601+
mlir::ArrayAttr::get(mlirContext, elements));
1602+
}
1603+
1604+
/// Emit the null constant for a base subobject.
1605+
static mlir::TypedAttr emitNullConstantForBase(CIRGenModule &cgm,
1606+
mlir::Type baseType,
1607+
const CXXRecordDecl *baseDecl) {
1608+
const CIRGenRecordLayout &baseLayout =
1609+
cgm.getTypes().getCIRGenRecordLayout(baseDecl);
1610+
1611+
// Just zero out bases that don't have any pointer to data members.
1612+
if (baseLayout.isZeroInitializableAsBase())
1613+
return cgm.getBuilder().getZeroInitAttr(baseType);
1614+
1615+
// Otherwise, we can just use its null constant.
1616+
return emitNullConstant(cgm, baseDecl, /*asCompleteObject=*/false);
1617+
}
1618+
15241619
mlir::Attribute ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &d) {
15251620
// Make a quick check if variable can be default NULL initialized
15261621
// and avoid going through rest of code which may do, for c++11,
@@ -1820,23 +1915,32 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
18201915
}
18211916

18221917
mlir::Value CIRGenModule::emitNullConstant(QualType t, mlir::Location loc) {
1823-
if (t->getAs<PointerType>()) {
1824-
return builder.getNullPtr(getTypes().convertTypeForMem(t), loc);
1825-
}
1918+
return builder.getConstant(loc, emitNullConstantAttr(t));
1919+
}
1920+
1921+
mlir::TypedAttr CIRGenModule::emitNullConstantAttr(QualType t) {
1922+
if (t->getAs<PointerType>())
1923+
return builder.getConstNullPtrAttr(getTypes().convertTypeForMem(t));
18261924

18271925
if (getTypes().isZeroInitializable(t))
1828-
return builder.getNullValue(getTypes().convertTypeForMem(t), loc);
1926+
return builder.getZeroInitAttr(getTypes().convertTypeForMem(t));
18291927

18301928
if (getASTContext().getAsConstantArrayType(t)) {
1831-
errorNYI("CIRGenModule::emitNullConstant ConstantArrayType");
1929+
errorNYI("CIRGenModule::emitNullConstantAttr ConstantArrayType");
1930+
return {};
18321931
}
18331932

1834-
if (t->isRecordType())
1835-
errorNYI("CIRGenModule::emitNullConstant RecordType");
1933+
if (const RecordType *rt = t->getAs<RecordType>())
1934+
return ::emitNullConstant(*this, rt->getDecl(), /*asCompleteObject=*/true);
18361935

18371936
assert(t->isMemberDataPointerType() &&
18381937
"Should only see pointers to data members here!");
18391938

1840-
errorNYI("CIRGenModule::emitNullConstant unsupported type");
1939+
errorNYI("CIRGenModule::emitNullConstantAttr unsupported type");
18411940
return {};
18421941
}
1942+
1943+
mlir::TypedAttr
1944+
CIRGenModule::emitNullConstantForBase(const CXXRecordDecl *record) {
1945+
return ::emitNullConstant(*this, record, false);
1946+
}

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,12 @@ class CIRGenModule : public CIRGenTypeCache {
461461
/// expression of the given type.
462462
mlir::Value emitNullConstant(QualType t, mlir::Location loc);
463463

464+
mlir::TypedAttr emitNullConstantAttr(QualType t);
465+
466+
/// Return a null constant appropriate for zero-initializing a base class with
467+
/// the given type. This is usually, but not always, an LLVM null constant.
468+
mlir::TypedAttr emitNullConstantForBase(const CXXRecordDecl *record);
469+
464470
llvm::StringRef getMangledName(clang::GlobalDecl gd);
465471

466472
void emitTentativeDefinition(const VarDecl *d);

clang/lib/CIR/CodeGen/CIRGenRecordLayout.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ class CIRGenRecordLayout {
184184
return fieldIdxMap.lookup(fd);
185185
}
186186

187+
unsigned getNonVirtualBaseCIRFieldNo(const CXXRecordDecl *rd) const {
188+
assert(nonVirtualBases.count(rd) && "Invalid non-virtual base!");
189+
return nonVirtualBases.lookup(rd);
190+
}
191+
187192
/// Check whether this struct can be C++ zero-initialized
188193
/// with a zeroinitializer.
189194
bool isZeroInitializable() const { return zeroInitializable; }

clang/test/CIR/CodeGen/ctor-null-init.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,63 @@ void test_empty_base_null_init() {
2929
// OGCG-NEXT: entry:
3030
// OGCG-NEXT: %[[B:.*]] = alloca %struct.B
3131
// OGCG-NEXT: ret void
32+
33+
34+
struct C {
35+
int c;
36+
C() = default;
37+
C(int); // This constructor triggers the null base class initialization.
38+
};
39+
40+
struct D : C {
41+
};
42+
43+
void test_non_empty_base_null_init() {
44+
D{};
45+
}
46+
47+
// CIR: cir.func {{.*}} @_Z29test_non_empty_base_null_initv()
48+
// CIR: %[[TMP:.*]] = cir.alloca !rec_D, !cir.ptr<!rec_D>, ["agg.tmp.ensured"]
49+
// CIR: %[[BASE:.*]] = cir.base_class_addr %[[TMP]] : !cir.ptr<!rec_D> nonnull [0] -> !cir.ptr<!rec_C>
50+
// CIR: %[[ZERO:.*]] = cir.const #cir.const_record<{#cir.int<0> : !s32i}> : !rec_C
51+
// CIR: cir.store{{.*}} %[[ZERO]], %[[BASE]]
52+
53+
// LLVM: define{{.*}} void @_Z29test_non_empty_base_null_initv()
54+
// LLVM: %[[TMP:.*]] = alloca %struct.D
55+
// LLVM: store %struct.C zeroinitializer, ptr %[[TMP]]
56+
57+
// OGCG: define {{.*}} void @_Z29test_non_empty_base_null_initv()
58+
// OGCG: %[[TMP:.*]] = alloca %struct.D
59+
// OGCG: %[[BASE:.*]] = getelementptr inbounds i8, ptr %[[TMP]], i64 0
60+
// OGCG: call void @llvm.memset.p0.i64(ptr{{.*}} %[[BASE]], i8 0, i64 4, i1 false)
61+
62+
struct E {
63+
int e;
64+
};
65+
66+
struct F : E {
67+
F() = default;
68+
F(int);
69+
};
70+
71+
struct G : F {
72+
};
73+
74+
void test_base_chain_null_init() {
75+
G{};
76+
}
77+
78+
// CIR: cir.func {{.*}} @_Z25test_base_chain_null_initv()
79+
// CIR: %[[TMP:.*]] = cir.alloca !rec_G, !cir.ptr<!rec_G>, ["agg.tmp.ensured"]
80+
// CIR: %[[BASE:.*]] = cir.base_class_addr %[[TMP]] : !cir.ptr<!rec_G> nonnull [0] -> !cir.ptr<!rec_F>
81+
// CIR: %[[ZERO:.*]] = cir.const #cir.const_record<{#cir.zero : !rec_E}> : !rec_F
82+
// CIR: cir.store{{.*}} %[[ZERO]], %[[BASE]]
83+
84+
// LLVM: define{{.*}} void @_Z25test_base_chain_null_initv()
85+
// LLVM: %[[TMP:.*]] = alloca %struct.G
86+
// LLVM: store %struct.F zeroinitializer, ptr %[[TMP]]
87+
88+
// OGCG: define {{.*}} void @_Z25test_base_chain_null_initv()
89+
// OGCG: %[[TMP:.*]] = alloca %struct.G
90+
// OGCG: %[[BASE:.*]] = getelementptr inbounds i8, ptr %[[TMP]], i64 0
91+
// OGCG: call void @llvm.memset.p0.i64(ptr{{.*}} %[[BASE]], i8 0, i64 4, i1 false)

0 commit comments

Comments
 (0)