[CIR] Handle non-empty null base class initialization (#168646)

This implements null base class initialization for non-empty bases.
This commit is contained in:
Andy Kaylor
2025-11-19 14:30:02 -08:00
committed by GitHub
parent 90ea49a9d1
commit 3f55f8b4e5
7 changed files with 236 additions and 10 deletions

View File

@@ -657,6 +657,9 @@ def CIR_RecordType : CIR_Type<"Record", "record", [
}
llvm_unreachable("Invalid value for RecordType::getKind()");
}
mlir::Type getElementType(size_t idx) {
return getMembers()[idx];
}
std::string getPrefixedName() {
return getKindAsStr() + "." + getName().getValue().str();
}

View File

@@ -212,6 +212,16 @@ public:
&ignored);
return fv.bitwiseIsEqual(fpVal);
}
if (const auto recordVal = mlir::dyn_cast<cir::ConstRecordAttr>(attr)) {
for (const auto elt : recordVal.getMembers()) {
// FIXME(cir): the record's ID should not be considered a member.
if (mlir::isa<mlir::StringAttr>(elt))
continue;
if (!isNullValue(elt))
return false;
}
return true;
}
if (const auto arrayVal = mlir::dyn_cast<cir::ConstArrayAttr>(attr)) {
if (mlir::isa<mlir::StringAttr>(arrayVal.getElts()))

View File

@@ -240,8 +240,46 @@ static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
if (base->isEmpty())
return;
cgf.cgm.errorNYI(base->getSourceRange(),
"emitNullBaseClassInitialization: not empty");
const ASTRecordLayout &layout = cgf.getContext().getASTRecordLayout(base);
CharUnits nvSize = layout.getNonVirtualSize();
// We cannot simply zero-initialize the entire base sub-object if vbptrs are
// present, they are initialized by the most derived class before calling the
// constructor.
SmallVector<std::pair<CharUnits, CharUnits>, 1> stores;
stores.emplace_back(CharUnits::Zero(), nvSize);
// Each store is split by the existence of a vbptr.
// TODO(cir): This only needs handling for the MS CXXABI.
assert(!cir::MissingFeatures::msabi());
// If the type contains a pointer to data member we can't memset it to zero.
// Instead, create a null constant and copy it to the destination.
// TODO: there are other patterns besides zero that we can usefully memset,
// like -1, which happens to be the pattern used by member-pointers.
// TODO: isZeroInitializable can be over-conservative in the case where a
// virtual base contains a member pointer.
mlir::TypedAttr nullConstantForBase = cgf.cgm.emitNullConstantForBase(base);
if (!cgf.getBuilder().isNullValue(nullConstantForBase)) {
cgf.cgm.errorNYI(
base->getSourceRange(),
"emitNullBaseClassInitialization: base constant is not null");
} else {
// Otherwise, just memset the whole thing to zero. This is legal
// because in LLVM, all default initializers (other than the ones we just
// handled above) are guaranteed to have a bit pattern of all zeros.
// TODO(cir): When the MS CXXABI is supported, we will need to iterate over
// `stores` and create a separate memset for each one. For now, we know that
// there will only be one store and it will begin at offset zero, so that
// simplifies this code considerably.
assert(stores.size() == 1 && "Expected only one store");
assert(stores[0].first == CharUnits::Zero() &&
"Expected store to begin at offset zero");
CIRGenBuilderTy builder = cgf.getBuilder();
mlir::Location loc = cgf.getLoc(base->getBeginLoc());
builder.createStore(loc, builder.getConstant(loc, nullConstantForBase),
destPtr);
}
}
void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,

View File

@@ -1521,6 +1521,101 @@ ConstantEmitter::~ConstantEmitter() {
"not finalized after being initialized for non-abstract emission");
}
static mlir::TypedAttr emitNullConstantForBase(CIRGenModule &cgm,
mlir::Type baseType,
const CXXRecordDecl *baseDecl);
static mlir::TypedAttr emitNullConstant(CIRGenModule &cgm, const RecordDecl *rd,
bool asCompleteObject) {
const CIRGenRecordLayout &layout = cgm.getTypes().getCIRGenRecordLayout(rd);
mlir::Type ty = (asCompleteObject ? layout.getCIRType()
: layout.getBaseSubobjectCIRType());
auto recordTy = mlir::cast<cir::RecordType>(ty);
unsigned numElements = recordTy.getNumElements();
SmallVector<mlir::Attribute> elements(numElements);
auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
// Fill in all the bases.
if (cxxrd) {
for (const CXXBaseSpecifier &base : cxxrd->bases()) {
if (base.isVirtual()) {
// Ignore virtual bases; if we're laying out for a complete
// object, we'll lay these out later.
continue;
}
const auto *baseDecl = base.getType()->castAsCXXRecordDecl();
// Ignore empty bases.
if (isEmptyRecordForLayout(cgm.getASTContext(), base.getType()) ||
cgm.getASTContext()
.getASTRecordLayout(baseDecl)
.getNonVirtualSize()
.isZero())
continue;
unsigned fieldIndex = layout.getNonVirtualBaseCIRFieldNo(baseDecl);
mlir::Type baseType = recordTy.getElementType(fieldIndex);
elements[fieldIndex] = emitNullConstantForBase(cgm, baseType, baseDecl);
}
}
// Fill in all the fields.
for (const FieldDecl *field : rd->fields()) {
// Fill in non-bitfields. (Bitfields always use a zero pattern, which we
// will fill in later.)
if (!field->isBitField() &&
!isEmptyFieldForLayout(cgm.getASTContext(), field)) {
unsigned fieldIndex = layout.getCIRFieldNo(field);
elements[fieldIndex] = cgm.emitNullConstantAttr(field->getType());
}
// For unions, stop after the first named field.
if (rd->isUnion()) {
if (field->getIdentifier())
break;
if (const auto *fieldRD = field->getType()->getAsRecordDecl())
if (fieldRD->findFirstNamedDataMember())
break;
}
}
// Fill in the virtual bases, if we're working with the complete object.
if (cxxrd && asCompleteObject) {
for ([[maybe_unused]] const CXXBaseSpecifier &vbase : cxxrd->vbases()) {
cgm.errorNYI(vbase.getSourceRange(), "emitNullConstant: virtual base");
return {};
}
}
// Now go through all other fields and zero them out.
for (unsigned i = 0; i != numElements; ++i) {
if (!elements[i]) {
cgm.errorNYI(rd->getSourceRange(), "emitNullConstant: field not zeroed");
return {};
}
}
mlir::MLIRContext *mlirContext = recordTy.getContext();
return cir::ConstRecordAttr::get(recordTy,
mlir::ArrayAttr::get(mlirContext, elements));
}
/// Emit the null constant for a base subobject.
static mlir::TypedAttr emitNullConstantForBase(CIRGenModule &cgm,
mlir::Type baseType,
const CXXRecordDecl *baseDecl) {
const CIRGenRecordLayout &baseLayout =
cgm.getTypes().getCIRGenRecordLayout(baseDecl);
// Just zero out bases that don't have any pointer to data members.
if (baseLayout.isZeroInitializableAsBase())
return cgm.getBuilder().getZeroInitAttr(baseType);
// Otherwise, we can just use its null constant.
return emitNullConstant(cgm, baseDecl, /*asCompleteObject=*/false);
}
mlir::Attribute ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &d) {
// Make a quick check if variable can be default NULL initialized
// and avoid going through rest of code which may do, for c++11,
@@ -1820,23 +1915,32 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
}
mlir::Value CIRGenModule::emitNullConstant(QualType t, mlir::Location loc) {
if (t->getAs<PointerType>()) {
return builder.getNullPtr(getTypes().convertTypeForMem(t), loc);
}
return builder.getConstant(loc, emitNullConstantAttr(t));
}
mlir::TypedAttr CIRGenModule::emitNullConstantAttr(QualType t) {
if (t->getAs<PointerType>())
return builder.getConstNullPtrAttr(getTypes().convertTypeForMem(t));
if (getTypes().isZeroInitializable(t))
return builder.getNullValue(getTypes().convertTypeForMem(t), loc);
return builder.getZeroInitAttr(getTypes().convertTypeForMem(t));
if (getASTContext().getAsConstantArrayType(t)) {
errorNYI("CIRGenModule::emitNullConstant ConstantArrayType");
errorNYI("CIRGenModule::emitNullConstantAttr ConstantArrayType");
return {};
}
if (t->isRecordType())
errorNYI("CIRGenModule::emitNullConstant RecordType");
if (const RecordType *rt = t->getAs<RecordType>())
return ::emitNullConstant(*this, rt->getDecl(), /*asCompleteObject=*/true);
assert(t->isMemberDataPointerType() &&
"Should only see pointers to data members here!");
errorNYI("CIRGenModule::emitNullConstant unsupported type");
errorNYI("CIRGenModule::emitNullConstantAttr unsupported type");
return {};
}
mlir::TypedAttr
CIRGenModule::emitNullConstantForBase(const CXXRecordDecl *record) {
return ::emitNullConstant(*this, record, false);
}

View File

@@ -461,6 +461,12 @@ public:
/// expression of the given type.
mlir::Value emitNullConstant(QualType t, mlir::Location loc);
mlir::TypedAttr emitNullConstantAttr(QualType t);
/// Return a null constant appropriate for zero-initializing a base class with
/// the given type. This is usually, but not always, an LLVM null constant.
mlir::TypedAttr emitNullConstantForBase(const CXXRecordDecl *record);
llvm::StringRef getMangledName(clang::GlobalDecl gd);
void emitTentativeDefinition(const VarDecl *d);

View File

@@ -184,6 +184,11 @@ public:
return fieldIdxMap.lookup(fd);
}
unsigned getNonVirtualBaseCIRFieldNo(const CXXRecordDecl *rd) const {
assert(nonVirtualBases.count(rd) && "Invalid non-virtual base!");
return nonVirtualBases.lookup(rd);
}
/// Check whether this struct can be C++ zero-initialized
/// with a zeroinitializer.
bool isZeroInitializable() const { return zeroInitializable; }

View File

@@ -29,3 +29,63 @@ void test_empty_base_null_init() {
// OGCG-NEXT: entry:
// OGCG-NEXT: %[[B:.*]] = alloca %struct.B
// OGCG-NEXT: ret void
struct C {
int c;
C() = default;
C(int); // This constructor triggers the null base class initialization.
};
struct D : C {
};
void test_non_empty_base_null_init() {
D{};
}
// CIR: cir.func {{.*}} @_Z29test_non_empty_base_null_initv()
// CIR: %[[TMP:.*]] = cir.alloca !rec_D, !cir.ptr<!rec_D>, ["agg.tmp.ensured"]
// CIR: %[[BASE:.*]] = cir.base_class_addr %[[TMP]] : !cir.ptr<!rec_D> nonnull [0] -> !cir.ptr<!rec_C>
// CIR: %[[ZERO:.*]] = cir.const #cir.const_record<{#cir.int<0> : !s32i}> : !rec_C
// CIR: cir.store{{.*}} %[[ZERO]], %[[BASE]]
// LLVM: define{{.*}} void @_Z29test_non_empty_base_null_initv()
// LLVM: %[[TMP:.*]] = alloca %struct.D
// LLVM: store %struct.C zeroinitializer, ptr %[[TMP]]
// OGCG: define {{.*}} void @_Z29test_non_empty_base_null_initv()
// OGCG: %[[TMP:.*]] = alloca %struct.D
// OGCG: %[[BASE:.*]] = getelementptr inbounds i8, ptr %[[TMP]], i64 0
// OGCG: call void @llvm.memset.p0.i64(ptr{{.*}} %[[BASE]], i8 0, i64 4, i1 false)
struct E {
int e;
};
struct F : E {
F() = default;
F(int);
};
struct G : F {
};
void test_base_chain_null_init() {
G{};
}
// CIR: cir.func {{.*}} @_Z25test_base_chain_null_initv()
// CIR: %[[TMP:.*]] = cir.alloca !rec_G, !cir.ptr<!rec_G>, ["agg.tmp.ensured"]
// CIR: %[[BASE:.*]] = cir.base_class_addr %[[TMP]] : !cir.ptr<!rec_G> nonnull [0] -> !cir.ptr<!rec_F>
// CIR: %[[ZERO:.*]] = cir.const #cir.const_record<{#cir.zero : !rec_E}> : !rec_F
// CIR: cir.store{{.*}} %[[ZERO]], %[[BASE]]
// LLVM: define{{.*}} void @_Z25test_base_chain_null_initv()
// LLVM: %[[TMP:.*]] = alloca %struct.G
// LLVM: store %struct.F zeroinitializer, ptr %[[TMP]]
// OGCG: define {{.*}} void @_Z25test_base_chain_null_initv()
// OGCG: %[[TMP:.*]] = alloca %struct.G
// OGCG: %[[BASE:.*]] = getelementptr inbounds i8, ptr %[[TMP]], i64 0
// OGCG: call void @llvm.memset.p0.i64(ptr{{.*}} %[[BASE]], i8 0, i64 4, i1 false)