[clang] [CIR] Add initial support for virtual base classes (PR #155534)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 27 14:47:01 PDT 2025
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/155534
>From dc7ae0c41669c04d1288a882929058ec08501042 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 11 Jul 2025 13:18:29 -0700
Subject: [PATCH 1/2] [CIR] Add initial support for virtual base classes
This adds support for declaring a class with a virtual base class and
initializing the vptr in the constructor. This does not yet handle
constructors that require a virtual table table (VTT) implicit
argument.
---
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 6 +
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 60 ++++---
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 8 +-
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 5 +-
clang/lib/CIR/CodeGen/CIRGenRecordLayout.h | 4 +
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 152 +++++++++++++-----
clang/test/CIR/CodeGen/vbase.cpp | 70 ++++++++
clang/test/CIR/CodeGen/vtt.cpp | 45 ++++++
8 files changed, 287 insertions(+), 63 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/vbase.cpp
create mode 100644 clang/test/CIR/CodeGen/vtt.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index b5f2e1a067274..df7ffbb4a2759 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -37,6 +37,12 @@ class CIRGenCXXABI {
void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
+ /// Emit the code to initialize hidden members required to handle virtual
+ /// inheritance, if needed by the ABI.
+ virtual void
+ initializeHiddenVirtualInheritanceMembers(CIRGenFunction &cgf,
+ const CXXRecordDecl *rd) {}
+
/// Emit a single constructor/destructor with the gen type from a C++
/// constructor/destructor Decl.
virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 9106e5e4824ff..ac923635192aa 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -238,31 +238,44 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
bool constructVBases = ctorType != Ctor_Base &&
classDecl->getNumVBases() != 0 &&
!classDecl->isAbstract();
- if (constructVBases) {
- cgm.errorNYI(cd->getSourceRange(), "emitCtorPrologue: virtual base");
- return;
- }
-
- const mlir::Value oldThisValue = cxxThisValue;
- if (!constructVBases && b != e && (*b)->isBaseInitializer() &&
- (*b)->isBaseVirtual()) {
+ if (constructVBases &&
+ !cgm.getTarget().getCXXABI().hasConstructorVariants()) {
cgm.errorNYI(cd->getSourceRange(),
- "emitCtorPrologue: virtual base initializer");
+ "emitCtorPrologue: virtual base without variants");
return;
}
- // Handle non-virtual base initializers.
- for (; b != e && (*b)->isBaseInitializer(); b++) {
- assert(!(*b)->isBaseVirtual());
+ const mlir::Value oldThisValue = cxxThisValue;
+ // Initialize virtual bases.
+ auto emitInitializer = [&](CXXCtorInitializer *baseInit) {
if (cgm.getCodeGenOpts().StrictVTablePointers &&
cgm.getCodeGenOpts().OptimizationLevel > 0 &&
- isInitializerOfDynamicClass(*b)) {
+ isInitializerOfDynamicClass(baseInit)) {
+ // It's OK to continue after emitting the error here. The missing code
+ // just "launders" the 'this' pointer.
cgm.errorNYI(cd->getSourceRange(),
- "emitCtorPrologue: strict vtable pointers");
- return;
+ "emitCtorPrologue: strict vtable pointers for vbase");
}
- emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, *b);
+ emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, baseInit);
+ };
+
+ for (; b != e && (*b)->isBaseInitializer() && (*b)->isBaseVirtual(); b++) {
+ if (!constructVBases)
+ continue;
+ emitInitializer(*b);
+ }
+
+ // The loop above and the loop below could obviously be merged in their
+ // current form, but when we implement support for the MS C++ ABI, we will
+ // need to insert a branch after the last virtual base initializer, so
+ // separate loops will be useful then. The missing code is covered by the
+ // "virtual base without variants" diagnostic above.
+
+ // Handle non-virtual base initializers.
+ for (; b != e && (*b)->isBaseInitializer(); b++) {
+ assert(!(*b)->isBaseVirtual());
+ emitInitializer(*b);
}
cxxThisValue = oldThisValue;
@@ -370,7 +383,7 @@ void CIRGenFunction::initializeVTablePointers(mlir::Location loc,
initializeVTablePointer(loc, vptr);
if (rd->getNumVBases())
- cgm.errorNYI(loc, "initializeVTablePointers: virtual base");
+ cgm.getCXXABI().initializeHiddenVirtualInheritanceMembers(*this, rd);
}
CIRGenFunction::VPtrsVector
@@ -418,8 +431,17 @@ void CIRGenFunction::getVTablePointers(BaseSubobject base,
const CXXRecordDecl *nextBaseDecl;
if (nextBase.isVirtual()) {
- cgm.errorNYI(rd->getSourceRange(), "getVTablePointers: virtual base");
- return;
+ // Check if we've visited this virtual base before.
+ if (!vbases.insert(baseDecl).second)
+ continue;
+
+ const ASTRecordLayout &layout =
+ getContext().getASTRecordLayout(vtableClass);
+
+ nextBaseDecl = nearestVBase;
+ baseOffset = layout.getVBaseClassOffset(baseDecl);
+ baseOffsetFromNearestVBase = CharUnits::Zero();
+ baseDeclIsNonVirtualPrimaryBase = false;
} else {
const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 1afac6dd52c2d..469879371eb1d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1972,12 +1972,8 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
- // This should just set 'forVirtualBase' to true and fall through, but
- // virtual base class support is otherwise missing, so this needs to wait
- // until it can be tested.
- cgm.errorNYI(e->getSourceRange(),
- "emitCXXConstructExpr: virtual base constructor");
- return;
+ forVirtualBase = true;
+ [[fallthrough]];
case CXXConstructionKind::NonVirtualBase:
type = Ctor_Base;
break;
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index aaf7dc767d888..4fd5a278e1a99 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -487,9 +487,10 @@ mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructor(
CIRGenFunction &cgf, const clang::CXXRecordDecl *vtableClass,
clang::BaseSubobject base, const clang::CXXRecordDecl *nearestVBase) {
- if (base.getBase()->getNumVBases()) {
+ if ((base.getBase()->getNumVBases() || nearestVBase != nullptr) &&
+ needsVTTParameter(cgf.curGD)) {
cgm.errorNYI(cgf.curFuncDecl->getLocation(),
- "getVTableAddressPointInStructor: virtual base");
+ "getVTableAddressPointInStructorWithVTT");
}
return getVTableAddressPoint(base, vtableClass);
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
index b28afe42c39a0..914ef16c2a5ee 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -141,6 +141,10 @@ class CIRGenRecordLayout {
// for both virtual and non-virtual bases.
llvm::DenseMap<const clang::CXXRecordDecl *, unsigned> nonVirtualBases;
+ /// Map from virtual bases to their field index in the complete object.
+ llvm::DenseMap<const clang::CXXRecordDecl *, unsigned>
+ completeObjectVirtualBases;
+
/// Map from (bit-field) record field to the corresponding CIR record type
/// field no. This info is populated by record builder.
llvm::DenseMap<const clang::FieldDecl *, CIRGenBitFieldInfo> bitFields;
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 1764967329969..b27d89707101e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -41,7 +41,7 @@ struct CIRRecordLowering final {
// member type that ensures correct rounding.
struct MemberInfo final {
CharUnits offset;
- enum class InfoKind { VFPtr, Field, Base } kind;
+ enum class InfoKind { VFPtr, Field, Base, VBase, Scissor } kind;
mlir::Type data;
union {
const FieldDecl *fieldDecl;
@@ -71,17 +71,18 @@ struct CIRRecordLowering final {
void setBitFieldInfo(const FieldDecl *fd, CharUnits startOffset,
mlir::Type storageType);
- void lower();
+ void lower(bool NonVirtualBaseType);
void lowerUnion();
/// Determines if we need a packed llvm struct.
- void determinePacked();
+ void determinePacked(bool nvBaseType);
/// Inserts padding everywhere it's needed.
void insertPadding();
void computeVolatileBitfields();
- void accumulateBases(const CXXRecordDecl *cxxRecordDecl);
+ void accumulateBases();
void accumulateVPtrs();
+ void accumulateVBases();
void accumulateFields();
RecordDecl::field_iterator
accumulateBitFields(RecordDecl::field_iterator field,
@@ -96,6 +97,17 @@ struct CIRRecordLowering final {
/// Helper function to check if the target machine is BigEndian.
bool isBigEndian() const { return astContext.getTargetInfo().isBigEndian(); }
+ // The Itanium base layout rule allows virtual bases to overlap
+ // other bases, which complicates layout in specific ways.
+ //
+ // Note specifically that the ms_struct attribute doesn't change this.
+ bool isOverlappingVBaseABI() {
+ return !astContext.getTargetInfo().getCXXABI().isMicrosoft();
+ }
+ // Recursively searches all of the bases to find out if a vbase is
+ // not the primary vbase of some base class.
+ bool hasOwnStorage(const CXXRecordDecl *decl, const CXXRecordDecl *query);
+
CharUnits bitsToCharUnits(uint64_t bitOffset) {
return astContext.toCharUnitsFromBits(bitOffset);
}
@@ -184,6 +196,7 @@ struct CIRRecordLowering final {
CIRGenBuilderTy &builder;
const ASTContext &astContext;
const RecordDecl *recordDecl;
+ const CXXRecordDecl *cxxRecordDecl;
const ASTRecordLayout &astRecordLayout;
// Helpful intermediate data-structures
std::vector<MemberInfo> members;
@@ -192,6 +205,7 @@ struct CIRRecordLowering final {
llvm::DenseMap<const FieldDecl *, CIRGenBitFieldInfo> bitFields;
llvm::DenseMap<const FieldDecl *, unsigned> fieldIdxMap;
llvm::DenseMap<const CXXRecordDecl *, unsigned> nonVirtualBases;
+ llvm::DenseMap<const CXXRecordDecl *, unsigned> virtualBases;
cir::CIRDataLayout dataLayout;
LLVM_PREFERRED_TYPE(bool)
@@ -211,13 +225,14 @@ struct CIRRecordLowering final {
CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
const RecordDecl *recordDecl, bool packed)
- : cirGenTypes(cirGenTypes), builder(cirGenTypes.getBuilder()),
- astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
- astRecordLayout(
- cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
- dataLayout(cirGenTypes.getCGModule().getModule()),
- zeroInitializable(true), zeroInitializableAsBase(true), packed(packed),
- padded(false) {}
+ : cirGenTypes{cirGenTypes}, builder{cirGenTypes.getBuilder()},
+ astContext{cirGenTypes.getASTContext()}, recordDecl{recordDecl},
+ cxxRecordDecl{llvm::dyn_cast<CXXRecordDecl>(recordDecl)},
+ astRecordLayout{
+ cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
+ dataLayout{cirGenTypes.getCGModule().getModule()},
+ zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
+ padded{false} {}
void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
CharUnits startOffset,
@@ -246,27 +261,28 @@ void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
info.volatileStorageOffset = CharUnits::Zero();
}
-void CIRRecordLowering::lower() {
+void CIRRecordLowering::lower(bool nonVirtualBaseType) {
if (recordDecl->isUnion()) {
lowerUnion();
computeVolatileBitfields();
return;
}
- assert(!cir::MissingFeatures::recordLayoutVirtualBases());
- CharUnits size = astRecordLayout.getSize();
+ CharUnits size = nonVirtualBaseType ? astRecordLayout.getNonVirtualSize()
+ : astRecordLayout.getSize();
accumulateFields();
- if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl)) {
+ if (cxxRecordDecl) {
accumulateVPtrs();
- accumulateBases(cxxRecordDecl);
+ accumulateBases();
if (members.empty()) {
appendPaddingBytes(size);
computeVolatileBitfields();
return;
}
- assert(!cir::MissingFeatures::recordLayoutVirtualBases());
+ if (!nonVirtualBaseType)
+ accumulateVBases();
}
llvm::stable_sort(members);
@@ -275,7 +291,7 @@ void CIRRecordLowering::lower() {
assert(!cir::MissingFeatures::recordZeroInit());
members.push_back(makeStorageInfo(size, getUIntNType(8)));
- determinePacked();
+ determinePacked(nonVirtualBaseType);
insertPadding();
members.pop_back();
@@ -298,8 +314,9 @@ void CIRRecordLowering::fillOutputFields() {
setBitFieldInfo(member.fieldDecl, member.offset, fieldTypes.back());
} else if (member.kind == MemberInfo::InfoKind::Base) {
nonVirtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
+ } else if (member.kind == MemberInfo::InfoKind::VBase) {
+ virtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
}
- assert(!cir::MissingFeatures::recordLayoutVirtualBases());
}
}
@@ -426,8 +443,9 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
limitOffset = bitsToCharUnits(getFieldBitOffset(*probe));
goto FoundLimit;
}
- assert(!cir::MissingFeatures::cxxSupport());
- limitOffset = astRecordLayout.getDataSize();
+ limitOffset = cxxRecordDecl ? astRecordLayout.getNonVirtualSize()
+ : astRecordLayout.getDataSize();
+
FoundLimit:
CharUnits typeSize = getSize(type);
if (beginOffset + typeSize <= limitOffset) {
@@ -524,24 +542,25 @@ void CIRRecordLowering::calculateZeroInit() {
continue;
zeroInitializable = zeroInitializableAsBase = false;
return;
- } else if (member.kind == MemberInfo::InfoKind::Base) {
+ } else if (member.kind == MemberInfo::InfoKind::Base ||
+ member.kind == MemberInfo::InfoKind::VBase) {
if (isZeroInitializable(member.cxxRecordDecl))
continue;
zeroInitializable = false;
if (member.kind == MemberInfo::InfoKind::Base)
zeroInitializableAsBase = false;
}
- assert(!cir::MissingFeatures::recordLayoutVirtualBases());
}
}
-void CIRRecordLowering::determinePacked() {
+void CIRRecordLowering::determinePacked(bool nvBaseType) {
if (packed)
return;
CharUnits alignment = CharUnits::One();
-
- // TODO(cir): handle non-virtual base types
- assert(!cir::MissingFeatures::cxxSupport());
+ CharUnits nvAlignment = CharUnits::One();
+ CharUnits nvSize = !nvBaseType && cxxRecordDecl
+ ? astRecordLayout.getNonVirtualSize()
+ : CharUnits::Zero();
for (const MemberInfo &member : members) {
if (!member.data)
@@ -550,12 +569,19 @@ void CIRRecordLowering::determinePacked() {
// then the entire record must be packed.
if (member.offset % getAlignment(member.data))
packed = true;
+ if (member.offset < nvSize)
+ nvAlignment = std::max(nvAlignment, getAlignment(member.data));
alignment = std::max(alignment, getAlignment(member.data));
}
// If the size of the record (the capstone's offset) is not a multiple of the
// record's alignment, it must be packed.
if (members.back().offset % alignment)
packed = true;
+ // If the non-virtual sub-object is not a multiple of the non-virtual
+ // sub-object's alignment, it must be packed. We cannot have a packed
+ // non-virtual sub-object and an unpacked complete object or vise versa.
+ if (nvSize % nvAlignment)
+ packed = true;
// Update the alignment of the sentinel.
if (!packed)
members.back().data = getUIntNType(astContext.toBits(alignment));
@@ -589,7 +615,7 @@ std::unique_ptr<CIRGenRecordLayout>
CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
CIRRecordLowering lowering(*this, rd, /*packed=*/false);
assert(ty->isIncomplete() && "recomputing record layout?");
- lowering.lower();
+ lowering.lower(/*nonVirtualBaseType=*/false);
// If we're in C++, compute the base subobject type.
cir::RecordType baseTy;
@@ -599,7 +625,7 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
if (lowering.astRecordLayout.getNonVirtualSize() !=
lowering.astRecordLayout.getSize()) {
CIRRecordLowering baseLowering(*this, rd, /*Packed=*/lowering.packed);
- baseLowering.lower();
+ baseLowering.lower(/*NonVirtualBaseType=*/true);
std::string baseIdentifier = getRecordTypeName(rd, ".base");
baseTy =
builder.getCompleteRecordTy(baseLowering.fieldTypes, baseIdentifier,
@@ -626,8 +652,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
assert(!cir::MissingFeatures::recordZeroInit());
rl->nonVirtualBases.swap(lowering.nonVirtualBases);
+ rl->completeObjectVirtualBases.swap(lowering.virtualBases);
- assert(!cir::MissingFeatures::cxxSupport());
assert(!cir::MissingFeatures::bitfields());
// Add all the field numbers.
@@ -754,6 +780,17 @@ void CIRRecordLowering::lowerUnion() {
packed = true;
}
+bool CIRRecordLowering::hasOwnStorage(const CXXRecordDecl *decl,
+ const CXXRecordDecl *query) {
+ const ASTRecordLayout &declLayout = astContext.getASTRecordLayout(decl);
+ if (declLayout.isPrimaryBaseVirtual() && declLayout.getPrimaryBase() == query)
+ return false;
+ for (const auto &base : decl->bases())
+ if (!hasOwnStorage(base.getType()->getAsCXXRecordDecl(), query))
+ return false;
+ return true;
+}
+
/// The AAPCS that defines that, when possible, bit-fields should
/// be accessed using containers of the declared type width:
/// When a volatile bit-field is read, and its container does not overlap with
@@ -873,7 +910,7 @@ void CIRRecordLowering::computeVolatileBitfields() {
}
}
-void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
+void CIRRecordLowering::accumulateBases() {
// If we've got a primary virtual base, we need to add it with the bases.
if (astRecordLayout.isPrimaryBaseVirtual()) {
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
@@ -881,12 +918,9 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
}
// Accumulate the non-virtual bases.
- for ([[maybe_unused]] const auto &base : cxxRecordDecl->bases()) {
- if (base.isVirtual()) {
- cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
- "accumulateBases: virtual base");
+ for (const auto &base : cxxRecordDecl->bases()) {
+ if (base.isVirtual())
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();
@@ -899,6 +933,52 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
}
}
+void CIRRecordLowering::accumulateVBases() {
+ CharUnits scissorOffset = astRecordLayout.getNonVirtualSize();
+ // In the itanium ABI, it's possible to place a vbase at a dsize that is
+ // smaller than the nvsize. Here we check to see if such a base is placed
+ // before the nvsize and set the scissor offset to that, instead of the
+ // nvsize.
+ if (isOverlappingVBaseABI()) {
+ for (const auto &base : cxxRecordDecl->vbases()) {
+ const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+ if (baseDecl->isEmpty())
+ continue;
+ // If the vbase is a primary virtual base of some base, then it doesn't
+ // get its own storage location but instead lives inside of that base.
+ if (astContext.isNearlyEmpty(baseDecl) &&
+ !hasOwnStorage(cxxRecordDecl, baseDecl))
+ continue;
+ scissorOffset = std::min(scissorOffset,
+ astRecordLayout.getVBaseClassOffset(baseDecl));
+ }
+ }
+ members.push_back(MemberInfo(scissorOffset, MemberInfo::InfoKind::Scissor,
+ mlir::Type{}, cxxRecordDecl));
+ for (const auto &base : cxxRecordDecl->vbases()) {
+ const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+ if (baseDecl->isEmpty())
+ continue;
+ CharUnits offset = astRecordLayout.getVBaseClassOffset(baseDecl);
+ // If the vbase is a primary virtual base of some base, then it doesn't
+ // get its own storage location but instead lives inside of that base.
+ if (isOverlappingVBaseABI() && astContext.isNearlyEmpty(baseDecl) &&
+ !hasOwnStorage(cxxRecordDecl, baseDecl)) {
+ members.push_back(
+ MemberInfo(offset, MemberInfo::InfoKind::VBase, nullptr, baseDecl));
+ continue;
+ }
+ // If we've got a vtordisp, add it as a storage type.
+ if (astRecordLayout.getVBaseOffsetsMap()
+ .find(baseDecl)
+ ->second.hasVtorDisp())
+ members.push_back(makeStorageInfo(offset - CharUnits::fromQuantity(4),
+ getUIntNType(32)));
+ members.push_back(MemberInfo(offset, MemberInfo::InfoKind::VBase,
+ getStorageType(baseDecl), baseDecl));
+ }
+}
+
void CIRRecordLowering::accumulateVPtrs() {
if (astRecordLayout.hasOwnVFPtr())
members.push_back(MemberInfo(CharUnits::Zero(), MemberInfo::InfoKind::VFPtr,
diff --git a/clang/test/CIR/CodeGen/vbase.cpp b/clang/test/CIR/CodeGen/vbase.cpp
new file mode 100644
index 0000000000000..1d1b5e083bfc9
--- /dev/null
+++ b/clang/test/CIR/CodeGen/vbase.cpp
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
+
+struct A {
+ int a;
+};
+
+struct B: virtual A {
+ int b;
+};
+
+void ppp() { B b; }
+
+// Note: OGCG speculatively emits the VTT and VTables. This is not yet implemented in CIR.
+
+// Vtable definition for B
+// CIR: cir.global "private" external @_ZTV1B
+
+// LLVM: @_ZTV1B = external global { [3 x ptr] }
+
+// OGCG: @_ZTV1B = linkonce_odr unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr inttoptr (i64 12 to ptr), ptr null, ptr @_ZTI1B] }, comdat, align 8
+
+// Constructor for A
+// CIR: cir.func comdat linkonce_odr @_ZN1AC2Ev(%arg0: !cir.ptr<!rec_A>
+// CIR: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_A>, !cir.ptr<!cir.ptr<!rec_A>>, ["this", init]
+// CIR: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_A>, !cir.ptr<!cir.ptr<!rec_A>>
+// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_A>>, !cir.ptr<!rec_A>
+// CIR: cir.return
+
+// LLVM: define{{.*}} void @_ZN1AC2Ev(ptr %[[THIS_ARG:.*]]) {
+// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
+// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+// LLVM: ret void
+
+// Note: OGCG elides the constructor for A. This is not yet implemented in CIR.
+
+// Constructor for B
+// CIR: cir.func comdat linkonce_odr @_ZN1BC1Ev(%arg0: !cir.ptr<!rec_B>
+// CIR: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!rec_B>>, ["this", init]
+// CIR: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!rec_B>>
+// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+// CIR: %[[BASE_A_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_B> nonnull [12] -> !cir.ptr<!rec_A>
+// CIR: cir.call @_ZN1AC2Ev(%[[BASE_A_ADDR]]) nothrow : (!cir.ptr<!rec_A>) -> ()
+// CIR: %[[VTABLE:.*]] = cir.vtable.address_point(@_ZTV1B, address_point = <index = 0, offset = 3>) : !cir.vptr
+// CIR: %[[B_VPTR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_B> -> !cir.ptr<!cir.vptr>
+// CIR: cir.store align(8) %[[VTABLE]], %[[B_VPTR]] : !cir.vptr, !cir.ptr<!cir.vptr>
+// CIR: cir.return
+
+// LLVM: define{{.*}} void @_ZN1BC1Ev(ptr %[[THIS_ARG:.*]]) {
+// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
+// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+// LLVM: %[[BASE_A_ADDR:.*]] = getelementptr i8, ptr %[[THIS]], i32 12
+// LLVM: call void @_ZN1AC2Ev(ptr %[[BASE_A_ADDR]])
+// LLVM: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1B, i64 24), ptr %[[THIS]]
+// LLVM: ret void
+
+// OGCG: define{{.*}} void @_ZN1BC1Ev(ptr {{.*}} %[[THIS_ARG:.*]])
+// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
+// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+// OGCG: %[[BASE_A_ADDR:.*]] = getelementptr inbounds i8, ptr %[[THIS]], i64 12
+// OGCG: store ptr getelementptr inbounds inrange(-24, 0) ({ [3 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 3), ptr %[[THIS]]
+// OGCG: ret void
+
diff --git a/clang/test/CIR/CodeGen/vtt.cpp b/clang/test/CIR/CodeGen/vtt.cpp
new file mode 100644
index 0000000000000..631aab428840a
--- /dev/null
+++ b/clang/test/CIR/CodeGen/vtt.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+// Note: This test will be expanded to verify VTT emission and VTT implicit
+// argument handling. For now, it's just test the record layout.
+
+class A {
+public:
+ int a;
+ virtual void v() {}
+};
+
+class B : public virtual A {
+public:
+ int b;
+ virtual void w();
+};
+
+class C : public virtual A {
+public:
+ long c;
+ virtual void x() {}
+};
+
+class D : public B, public C {
+public:
+ long d;
+ virtual void y() {}
+};
+
+// This is just here to force the record types to be emitted.
+void f(D *d) {}
+
+// CIR: !rec_A2Ebase = !cir.record<struct "A.base" packed {!cir.vptr, !s32i}>
+// CIR: !rec_B2Ebase = !cir.record<struct "B.base" packed {!cir.vptr, !s32i}>
+// CIR: !rec_C2Ebase = !cir.record<struct "C.base" {!cir.vptr, !s64i}>
+// CIR: !rec_D = !cir.record<class "D" {!rec_B2Ebase, !rec_C2Ebase, !s64i, !rec_A2Ebase}>
+
+// Nothing interesting to see here yet.
+// LLVM: define{{.*}} void @_Z1fP1D
+// OGCG: define{{.*}} void @_Z1fP1D
>From 5f2fd84ef5211f80a4e2f0a8ece6fbb490791f02 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 27 Aug 2025 14:46:25 -0700
Subject: [PATCH 2/2] Incorporate review feedback
---
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 51 +++++++++-------
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 36 +++--------
clang/lib/CodeGen/CGClass.cpp | 60 +++++++++++++++----
3 files changed, 84 insertions(+), 63 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index ac923635192aa..f3de8cd50cdc0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -222,13 +222,7 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
const CXXRecordDecl *classDecl = cd->getParent();
- // This code doesn't use range-based iteration because we may need to emit
- // code between the virtual base initializers and the non-virtual base or
- // between the non-virtual base initializers and the member initializers.
- CXXConstructorDecl::init_const_iterator b = cd->init_begin(),
- e = cd->init_end();
-
- // Virtual base initializers first, if any. They aren't needed if:
+ // Virtual base initializers aren't needed if:
// - This is a base ctor variant
// - There are no vbases
// - The class is abstract, so a complete object of it cannot be constructed
@@ -245,9 +239,28 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
return;
}
+ // Create three separate ranges for the different types of initializers.
+ auto allInits = cd->inits();
+
+ // Find the boundaries between the three groups.
+ auto virtualBaseEnd = std::find_if(
+ allInits.begin(), allInits.end(), [](const CXXCtorInitializer *Init) {
+ return !(Init->isBaseInitializer() && Init->isBaseVirtual());
+ });
+
+ auto nonVirtualBaseEnd = std::find_if(virtualBaseEnd, allInits.end(),
+ [](const CXXCtorInitializer *Init) {
+ return !Init->isBaseInitializer();
+ });
+
+ // Create the three ranges.
+ auto virtualBaseInits = llvm::make_range(allInits.begin(), virtualBaseEnd);
+ auto nonVirtualBaseInits =
+ llvm::make_range(virtualBaseEnd, nonVirtualBaseEnd);
+ auto memberInits = llvm::make_range(nonVirtualBaseEnd, allInits.end());
+
const mlir::Value oldThisValue = cxxThisValue;
- // Initialize virtual bases.
auto emitInitializer = [&](CXXCtorInitializer *baseInit) {
if (cgm.getCodeGenOpts().StrictVTablePointers &&
cgm.getCodeGenOpts().OptimizationLevel > 0 &&
@@ -260,22 +273,19 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, baseInit);
};
- for (; b != e && (*b)->isBaseInitializer() && (*b)->isBaseVirtual(); b++) {
+ // Process virtual base initializers.
+ for (CXXCtorInitializer *virtualBaseInit : virtualBaseInits) {
if (!constructVBases)
continue;
- emitInitializer(*b);
+ emitInitializer(virtualBaseInit);
}
- // The loop above and the loop below could obviously be merged in their
- // current form, but when we implement support for the MS C++ ABI, we will
- // need to insert a branch after the last virtual base initializer, so
- // separate loops will be useful then. The missing code is covered by the
- // "virtual base without variants" diagnostic above.
+ assert(!cir::MissingFeatures::msabi());
- // Handle non-virtual base initializers.
- for (; b != e && (*b)->isBaseInitializer(); b++) {
- assert(!(*b)->isBaseVirtual());
- emitInitializer(*b);
+ // Then, non-virtual base initializers.
+ for (CXXCtorInitializer *nonVirtualBaseInit : nonVirtualBaseInits) {
+ assert(!nonVirtualBaseInit->isBaseVirtual());
+ emitInitializer(nonVirtualBaseInit);
}
cxxThisValue = oldThisValue;
@@ -289,8 +299,7 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
// lowering or optimization phases to keep the memory accesses more
// explicit. For now, we don't insert memcpy at all.
assert(!cir::MissingFeatures::ctorMemcpyizer());
- for (; b != e; b++) {
- CXXCtorInitializer *member = (*b);
+ for (CXXCtorInitializer *member : memberInits) {
assert(!member->isBaseInitializer());
assert(member->isAnyMemberInitializer() &&
"Delegating initializer on non-delegating constructor");
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index b27d89707101e..3603fd1f2d291 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -41,7 +41,7 @@ struct CIRRecordLowering final {
// member type that ensures correct rounding.
struct MemberInfo final {
CharUnits offset;
- enum class InfoKind { VFPtr, Field, Base, VBase, Scissor } kind;
+ enum class InfoKind { VFPtr, Field, Base, VBase } kind;
mlir::Type data;
union {
const FieldDecl *fieldDecl;
@@ -934,44 +934,22 @@ void CIRRecordLowering::accumulateBases() {
}
void CIRRecordLowering::accumulateVBases() {
- CharUnits scissorOffset = astRecordLayout.getNonVirtualSize();
- // In the itanium ABI, it's possible to place a vbase at a dsize that is
- // smaller than the nvsize. Here we check to see if such a base is placed
- // before the nvsize and set the scissor offset to that, instead of the
- // nvsize.
- if (isOverlappingVBaseABI()) {
- for (const auto &base : cxxRecordDecl->vbases()) {
- const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
- if (baseDecl->isEmpty())
- continue;
- // If the vbase is a primary virtual base of some base, then it doesn't
- // get its own storage location but instead lives inside of that base.
- if (astContext.isNearlyEmpty(baseDecl) &&
- !hasOwnStorage(cxxRecordDecl, baseDecl))
- continue;
- scissorOffset = std::min(scissorOffset,
- astRecordLayout.getVBaseClassOffset(baseDecl));
- }
- }
- members.push_back(MemberInfo(scissorOffset, MemberInfo::InfoKind::Scissor,
- mlir::Type{}, cxxRecordDecl));
for (const auto &base : cxxRecordDecl->vbases()) {
const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
- if (baseDecl->isEmpty())
+ if (isEmptyRecordForLayout(astContext, base.getType()))
continue;
CharUnits offset = astRecordLayout.getVBaseClassOffset(baseDecl);
// If the vbase is a primary virtual base of some base, then it doesn't
// get its own storage location but instead lives inside of that base.
- if (isOverlappingVBaseABI() && astContext.isNearlyEmpty(baseDecl) &&
+ if (isOverlappingVBaseABI() &&
+ astContext.isNearlyEmpty(baseDecl) &&
!hasOwnStorage(cxxRecordDecl, baseDecl)) {
- members.push_back(
- MemberInfo(offset, MemberInfo::InfoKind::VBase, nullptr, baseDecl));
+ members.push_back(MemberInfo(offset, MemberInfo::InfoKind::VBase, nullptr,
+ baseDecl));
continue;
}
// If we've got a vtordisp, add it as a storage type.
- if (astRecordLayout.getVBaseOffsetsMap()
- .find(baseDecl)
- ->second.hasVtorDisp())
+ if (astRecordLayout.getVBaseOffsetsMap().find(baseDecl)->second.hasVtorDisp())
members.push_back(makeStorageInfo(offset - CharUnits::fromQuantity(4),
getUIntNType(32)));
members.push_back(MemberInfo(offset, MemberInfo::InfoKind::VBase,
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index bae55aa1e1928..e9a92ae0f01cb 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -180,7 +180,11 @@ CharUnits CodeGenModule::computeNonVirtualBaseClassOffset(
// Get the layout.
const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD);
- const auto *BaseDecl = Base->getType()->castAsCXXRecordDecl();
+ const auto *BaseDecl =
+ cast<CXXRecordDecl>(
+ Base->getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
+
// Add the offset.
Offset += Layout.getBaseClassOffset(BaseDecl);
@@ -298,7 +302,9 @@ Address CodeGenFunction::GetAddressOfBaseClass(
// *start* with a step down to the correct virtual base subobject,
// and hence will not require any further steps.
if ((*Start)->isVirtual()) {
- VBase = (*Start)->getType()->castAsCXXRecordDecl();
+ VBase = cast<CXXRecordDecl>(
+ (*Start)->getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
++Start;
}
@@ -553,7 +559,10 @@ static void EmitBaseInitializer(CodeGenFunction &CGF,
Address ThisPtr = CGF.LoadCXXThisAddress();
- const auto *BaseClassDecl = BaseInit->getBaseClass()->castAsCXXRecordDecl();
+ const Type *BaseType = BaseInit->getBaseClass();
+ const auto *BaseClassDecl =
+ cast<CXXRecordDecl>(BaseType->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
bool isBaseVirtual = BaseInit->isBaseVirtual();
@@ -1258,7 +1267,10 @@ namespace {
static bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit) {
const Type *BaseType = BaseInit->getBaseClass();
- return BaseType->castAsCXXRecordDecl()->isDynamicClass();
+ const auto *BaseClassDecl =
+ cast<CXXRecordDecl>(BaseType->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
+ return BaseClassDecl->isDynamicClass();
}
/// EmitCtorPrologue - This routine generates necessary code to initialize
@@ -1365,7 +1377,10 @@ HasTrivialDestructorBody(ASTContext &Context,
if (I.isVirtual())
continue;
- const auto *NonVirtualBase = I.getType()->castAsCXXRecordDecl();
+ const CXXRecordDecl *NonVirtualBase =
+ cast<CXXRecordDecl>(
+ I.getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
if (!HasTrivialDestructorBody(Context, NonVirtualBase,
MostDerivedClassDecl))
return false;
@@ -1374,7 +1389,10 @@ HasTrivialDestructorBody(ASTContext &Context,
if (BaseClassDecl == MostDerivedClassDecl) {
// Check virtual bases.
for (const auto &I : BaseClassDecl->vbases()) {
- const auto *VirtualBase = I.getType()->castAsCXXRecordDecl();
+ const auto *VirtualBase =
+ cast<CXXRecordDecl>(
+ I.getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
if (!HasTrivialDestructorBody(Context, VirtualBase,
MostDerivedClassDecl))
return false;
@@ -1390,10 +1408,13 @@ FieldHasTrivialDestructorBody(ASTContext &Context,
{
QualType FieldBaseElementType = Context.getBaseElementType(Field->getType());
- auto *FieldClassDecl = FieldBaseElementType->getAsCXXRecordDecl();
- if (!FieldClassDecl)
+ const RecordType *RT = FieldBaseElementType->getAs<RecordType>();
+ if (!RT)
return true;
+ auto *FieldClassDecl =
+ cast<CXXRecordDecl>(RT->getOriginalDecl())->getDefinitionOrSelf();
+
// The destructor for an implicit anonymous union member is never invoked.
if (FieldClassDecl->isUnion() && FieldClassDecl->isAnonymousStructOrUnion())
return true;
@@ -1886,7 +1907,11 @@ void CodeGenFunction::EnterDtorCleanups(const CXXDestructorDecl *DD,
// We push them in the forward order so that they'll be popped in
// the reverse order.
for (const auto &Base : ClassDecl->vbases()) {
- auto *BaseClassDecl = Base.getType()->castAsCXXRecordDecl();
+ auto *BaseClassDecl =
+ cast<CXXRecordDecl>(
+ Base.getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
+
if (BaseClassDecl->hasTrivialDestructor()) {
// Under SanitizeMemoryUseAfterDtor, poison the trivial base class
// memory. For non-trival base classes the same is done in the class
@@ -2105,7 +2130,10 @@ void CodeGenFunction::EmitCXXAggrConstructorCall(const CXXConstructorDecl *ctor,
void CodeGenFunction::destroyCXXObject(CodeGenFunction &CGF,
Address addr,
QualType type) {
- const CXXDestructorDecl *dtor = type->castAsCXXRecordDecl()->getDestructor();
+ const RecordType *rtype = type->castAs<RecordType>();
+ const auto *record =
+ cast<CXXRecordDecl>(rtype->getOriginalDecl())->getDefinitionOrSelf();
+ const CXXDestructorDecl *dtor = record->getDestructor();
assert(!dtor->isTrivial());
CGF.EmitCXXDestructorCall(dtor, Dtor_Complete, /*for vbase*/ false,
/*Delegating=*/false, addr, type);
@@ -2624,7 +2652,10 @@ void CodeGenFunction::getVTablePointers(BaseSubobject Base,
// Traverse bases.
for (const auto &I : RD->bases()) {
- auto *BaseDecl = I.getType()->castAsCXXRecordDecl();
+ auto *BaseDecl = cast<CXXRecordDecl>(
+ I.getType()->castAs<RecordType>()->getOriginalDecl())
+ ->getDefinitionOrSelf();
+
// Ignore classes without a vtable.
if (!BaseDecl->isDynamicClass())
continue;
@@ -2819,10 +2850,13 @@ void CodeGenFunction::EmitVTablePtrCheckForCast(QualType T, Address Derived,
if (!getLangOpts().CPlusPlus)
return;
- const auto *ClassDecl = T->getAsCXXRecordDecl();
- if (!ClassDecl)
+ auto *ClassTy = T->getAs<RecordType>();
+ if (!ClassTy)
return;
+ const auto *ClassDecl =
+ cast<CXXRecordDecl>(ClassTy->getOriginalDecl())->getDefinitionOrSelf();
+
if (!ClassDecl->isCompleteDefinition() || !ClassDecl->isDynamicClass())
return;
More information about the cfe-commits
mailing list