Skip to content

[CIR] Add support for derived class declarations #142823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,18 @@ def CIR_RecordType : CIR_Type<"Record", "record",
let genVerifyDecl = 1;

let builders = [
// Create an identified and complete record type.
TypeBuilder<(ins
"llvm::ArrayRef<mlir::Type>":$members,
"mlir::StringAttr":$name,
"bool":$packed,
"bool":$padded,
"RecordKind":$kind
), [{
return $_get($_ctxt, members, name, /*complete=*/true, packed, padded,
kind);
}]>,

// Create an identified and incomplete record type.
TypeBuilder<(ins
"mlir::StringAttr":$name,
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ struct MissingFeatures {
static bool cxxSupport() { return false; }
static bool recordZeroInit() { return false; }
static bool zeroSizeRecordMembers() { return false; }
static bool recordLayoutVirtualBases() { return false; }

// CXXABI
static bool cxxABI() { return false; }
Expand Down
28 changes: 28 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
llvm_unreachable("Unsupported record kind");
}

/// Get a CIR named record type.
///
/// If a record already exists and is complete, but the client tries to fetch
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have some asserts here for this condition?

/// it with a different set of attributes, this method will crash.
cir::RecordType getCompleteRecordTy(llvm::ArrayRef<mlir::Type> members,
llvm::StringRef name, bool packed,
bool padded) {
const auto nameAttr = getStringAttr(name);
auto kind = cir::RecordType::RecordKind::Struct;
assert(!cir::MissingFeatures::astRecordDeclAttr());

// Create or get the record.
auto type =
getType<cir::RecordType>(members, nameAttr, packed, padded, kind);

// If we found an existing type, verify that either it is incomplete or
// it matches the requested attributes.
assert(!type.isIncomplete() ||
(type.getMembers() == members && type.getPacked() == packed &&
type.getPadded() == padded));

// Complete an incomplete record or ensure the existing complete record
// matches the requested attributes.
type.complete(members, packed, padded);

return type;
}

/// Get an incomplete CIR struct type. If we have a complete record
/// declaration, we may create an incomplete type and then add the
/// members, so \p rd here may be complete.
Expand Down
16 changes: 15 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,18 @@ class CIRGenRecordLayout {
/// as a complete object.
cir::RecordType completeObjectType;

/// The CIR type for the non-virtual part of this record layout; used when
/// laying it out as a base subobject.
cir::RecordType baseSubobjectType;

/// Map from (non-bit-field) record field to the corresponding cir record type
/// field no. This info is populated by the record builder.
llvm::DenseMap<const clang::FieldDecl *, unsigned> fieldIdxMap;

// FIXME: Maybe we could use CXXBaseSpecifier as the key and use a single map
// for both virtual and non-virtual bases.
llvm::DenseMap<const clang::CXXRecordDecl *, unsigned> nonVirtualBases;

/// False if any direct or indirect subobject of this class, when considered
/// as a complete object, requires a non-zero bitpattern when
/// zero-initialized.
Expand All @@ -45,16 +53,22 @@ class CIRGenRecordLayout {
unsigned zeroInitializableAsBase : 1;

public:
CIRGenRecordLayout(cir::RecordType completeObjectType, bool zeroInitializable,
CIRGenRecordLayout(cir::RecordType completeObjectType,
cir::RecordType baseSubobjectType, bool zeroInitializable,
bool zeroInitializableAsBase)
: completeObjectType(completeObjectType),
baseSubobjectType(baseSubobjectType),
zeroInitializable(zeroInitializable),
zeroInitializableAsBase(zeroInitializableAsBase) {}

/// Return the "complete object" LLVM type associated with
/// this record.
cir::RecordType getCIRType() const { return completeObjectType; }

/// Return the "base subobject" LLVM type associated with
/// this record.
cir::RecordType getBaseSubobjectCIRType() const { return baseSubobjectType; }

/// Return cir::RecordType element number that corresponds to the field FD.
unsigned getCIRFieldNo(const clang::FieldDecl *fd) const {
fd = fd->getCanonicalDecl();
Expand Down
108 changes: 92 additions & 16 deletions clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ struct CIRRecordLowering final {
// member type that ensures correct rounding.
struct MemberInfo final {
CharUnits offset;
enum class InfoKind { Field } kind;
enum class InfoKind { Field, Base } kind;
mlir::Type data;
union {
const FieldDecl *fieldDecl;
// CXXRecordDecl will be used here when base types are supported.
const CXXRecordDecl *cxxRecordDecl;
};
MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data,
const FieldDecl *fieldDecl = nullptr)
: offset(offset), kind(kind), data(data), fieldDecl(fieldDecl) {};
: offset{offset}, kind{kind}, data{data}, fieldDecl{fieldDecl} {}
MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data,
const CXXRecordDecl *rd)
: offset{offset}, kind{kind}, data{data}, cxxRecordDecl{rd} {}
// MemberInfos are sorted so we define a < operator.
bool operator<(const MemberInfo &other) const {
return offset < other.offset;
Expand All @@ -71,6 +74,8 @@ struct CIRRecordLowering final {
/// Inserts padding everywhere it's needed.
void insertPadding();

void accumulateBases(const CXXRecordDecl *cxxRecordDecl);
void accumulateVPtrs();
void accumulateFields();

CharUnits bitsToCharUnits(uint64_t bitOffset) {
Expand All @@ -89,6 +94,9 @@ struct CIRRecordLowering final {
bool isZeroInitializable(const FieldDecl *fd) {
return cirGenTypes.isZeroInitializable(fd->getType());
}
bool isZeroInitializable(const RecordDecl *rd) {
return cirGenTypes.isZeroInitializable(rd);
}

/// Wraps cir::IntType with some implicit arguments.
mlir::Type getUIntNType(uint64_t numBits) {
Expand All @@ -112,6 +120,11 @@ struct CIRRecordLowering final {
: cir::ArrayType::get(type, numberOfChars.getQuantity());
}

// Gets the CIR BaseSubobject type from a CXXRecordDecl.
mlir::Type getStorageType(const CXXRecordDecl *RD) {
return cirGenTypes.getCIRGenRecordLayout(RD).getBaseSubobjectCIRType();
}

mlir::Type getStorageType(const FieldDecl *fieldDecl) {
mlir::Type type = cirGenTypes.convertTypeForMem(fieldDecl->getType());
if (fieldDecl->isBitField()) {
Expand Down Expand Up @@ -145,6 +158,7 @@ struct CIRRecordLowering final {
// Output fields, consumed by CIRGenTypes::computeRecordLayout
llvm::SmallVector<mlir::Type, 16> fieldTypes;
llvm::DenseMap<const FieldDecl *, unsigned> fieldIdxMap;
llvm::DenseMap<const CXXRecordDecl *, unsigned> nonVirtualBases;
cir::CIRDataLayout dataLayout;

LLVM_PREFERRED_TYPE(bool)
Expand Down Expand Up @@ -179,24 +193,20 @@ void CIRRecordLowering::lower() {
return;
}

assert(!cir::MissingFeatures::cxxSupport());

assert(!cir::MissingFeatures::recordLayoutVirtualBases());
CharUnits size = astRecordLayout.getSize();

accumulateFields();

if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl)) {
if (cxxRecordDecl->getNumBases() > 0) {
CIRGenModule &cgm = cirGenTypes.getCGModule();
cgm.errorNYI(recordDecl->getSourceRange(),
"CIRRecordLowering::lower: derived CXXRecordDecl");
return;
}
accumulateVPtrs();
accumulateBases(cxxRecordDecl);
if (members.empty()) {
appendPaddingBytes(size);
assert(!cir::MissingFeatures::bitfields());
return;
}
assert(!cir::MissingFeatures::recordLayoutVirtualBases());
}

llvm::stable_sort(members);
Expand All @@ -223,8 +233,10 @@ void CIRRecordLowering::fillOutputFields() {
fieldTypes.size() - 1;
// A field without storage must be a bitfield.
assert(!cir::MissingFeatures::bitfields());
} else if (member.kind == MemberInfo::InfoKind::Base) {
nonVirtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
}
assert(!cir::MissingFeatures::cxxSupport());
assert(!cir::MissingFeatures::recordLayoutVirtualBases());
}
}

Expand Down Expand Up @@ -254,9 +266,14 @@ void CIRRecordLowering::calculateZeroInit() {
continue;
zeroInitializable = zeroInitializableAsBase = false;
return;
} else if (member.kind == MemberInfo::InfoKind::Base) {
if (isZeroInitializable(member.cxxRecordDecl))
continue;
zeroInitializable = false;
if (member.kind == MemberInfo::InfoKind::Base)
zeroInitializableAsBase = false;
}
// TODO(cir): handle base types
assert(!cir::MissingFeatures::cxxSupport());
assert(!cir::MissingFeatures::recordLayoutVirtualBases());
}
}

Expand Down Expand Up @@ -317,6 +334,27 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
lowering.lower();

// If we're in C++, compute the base subobject type.
cir::RecordType baseTy;
if (llvm::isa<CXXRecordDecl>(rd) && !rd->isUnion() &&
!rd->hasAttr<FinalAttr>()) {
baseTy = *ty;
if (lowering.astRecordLayout.getNonVirtualSize() !=
lowering.astRecordLayout.getSize()) {
CIRRecordLowering baseLowering(*this, rd, /*Packed=*/lowering.packed);
baseLowering.lower();
std::string baseIdentifier = getRecordTypeName(rd, ".base");
baseTy =
builder.getCompleteRecordTy(baseLowering.fieldTypes, baseIdentifier,
baseLowering.packed, baseLowering.padded);
// TODO(cir): add something like addRecordTypeName

// BaseTy and Ty must agree on their packedness for getCIRFieldNo to work
// on both of them with the same index.
assert(lowering.packed == baseLowering.packed &&
"Non-virtual and complete types must agree on packedness");
}
}

if (llvm::isa<CXXRecordDecl>(rd) && !rd->isUnion() &&
!rd->hasAttr<FinalAttr>()) {
if (lowering.astRecordLayout.getNonVirtualSize() !=
Expand All @@ -332,10 +370,13 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded);

auto rl = std::make_unique<CIRGenRecordLayout>(
ty ? *ty : cir::RecordType(), (bool)lowering.zeroInitializable,
(bool)lowering.zeroInitializableAsBase);
ty ? *ty : cir::RecordType{}, baseTy ? baseTy : cir::RecordType{},
(bool)lowering.zeroInitializable, (bool)lowering.zeroInitializableAsBase);

assert(!cir::MissingFeatures::recordZeroInit());

rl->nonVirtualBases.swap(lowering.nonVirtualBases);

assert(!cir::MissingFeatures::cxxSupport());
assert(!cir::MissingFeatures::bitfields());

Expand Down Expand Up @@ -415,3 +456,38 @@ void CIRRecordLowering::lowerUnion() {
if (layoutSize % getAlignment(storageType))
packed = true;
}

void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
// If we've got a primary virtual base, we need to add it with the bases.
if (astRecordLayout.isPrimaryBaseVirtual()) {
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
"accumulateBases: primary virtual base");
}

// Accumulate the non-virtual bases.
for ([[maybe_unused]] const auto &base : cxxRecordDecl->bases()) {
if (base.isVirtual()) {
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
"accumulateBases: virtual base");
continue;
}
// Bases can be zero-sized even if not technically empty if they
// contain only a trailing array member.
const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
if (!baseDecl->isEmpty() &&
!astContext.getASTRecordLayout(baseDecl).getNonVirtualSize().isZero()) {
members.push_back(MemberInfo(astRecordLayout.getBaseClassOffset(baseDecl),
MemberInfo::InfoKind::Base,
getStorageType(baseDecl), baseDecl));
}
}
}

void CIRRecordLowering::accumulateVPtrs() {
if (astRecordLayout.hasOwnVFPtr())
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
"accumulateVPtrs: hasOwnVFPtr");
if (astRecordLayout.hasOwnVBPtr())
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
"accumulateVPtrs: hasOwnVBPtr");
}
7 changes: 4 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,10 @@ mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *rd) {

// Force conversion of non-virtual base classes recursively.
if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(rd)) {
if (cxxRecordDecl->getNumBases() > 0) {
cgm.errorNYI(rd->getSourceRange(),
"convertRecordDeclType: derived CXXRecordDecl");
for (const auto &base : cxxRecordDecl->bases()) {
if (base.isVirtual())
continue;
convertRecordDeclType(base.getType()->castAs<RecordType>()->getDecl());
}
}

Expand Down
19 changes: 19 additions & 0 deletions clang/test/CIR/CodeGen/class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s

// CIR: !rec_IncompleteC = !cir.record<class "IncompleteC" incomplete>
// CIR: !rec_Base = !cir.record<class "Base" {!s32i}>
// CIR: !rec_CompleteC = !cir.record<class "CompleteC" {!s32i, !s8i}>
// CIR: !rec_Derived = !cir.record<class "Derived" {!rec_Base, !s32i}>

// Note: LLVM and OGCG do not emit the type for incomplete classes.

// LLVM: %class.CompleteC = type { i32, i8 }
// LLVM: %class.Derived = type { %class.Base, i32 }
// LLVM: %class.Base = type { i32 }

// OGCG: %class.CompleteC = type { i32, i8 }
// OGCG: %class.Derived = type { %class.Base, i32 }
// OGCG: %class.Base = type { i32 }

class IncompleteC;
IncompleteC *p;
Expand All @@ -32,3 +38,16 @@ CompleteC cc;
// CIR: cir.global external @cc = #cir.zero : !rec_CompleteC
// LLVM: @cc = global %class.CompleteC zeroinitializer
// OGCG: @cc = global %class.CompleteC zeroinitializer

class Base {
public:
int a;
};

class Derived : public Base {
public:
int b;
};

int use(Derived *d) { return d->b; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a CRTP test as well? Something like:

template<typename Derived>
struct CRTP {
  Derived *otherDerived /* initializer left out, but add something to make sure this isn't ub */;
   void callThing() {
        static_cast<Derived>(*this)->thing();
        otherDerived->thing();
    }
};
struct D : CRTP<D> {
  void thing(){}
};

void use( D&d) {
   d.callThing();
}

I suspect it'll work fine, but its awkward enough with this sorta thing that it might be demonstrative.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't handle this yet because it requires an implicit DerivedToBase cast, which will be in an upcoming patch.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, hrmph. Well, file this one away then :)