[clang] 13c8970 - [CIR] Add support for non-virtual base class initialization (#148080)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 11 09:57:19 PDT 2025
Author: Andy Kaylor
Date: 2025-07-11T09:57:15-07:00
New Revision: 13c897093fd8d40ee3a5b13ff9c0b38c89e72bf1
URL: https://github.com/llvm/llvm-project/commit/13c897093fd8d40ee3a5b13ff9c0b38c89e72bf1
DIFF: https://github.com/llvm/llvm-project/commit/13c897093fd8d40ee3a5b13ff9c0b38c89e72bf1.diff
LOG: [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.
Added:
Modified:
clang/lib/CIR/CodeGen/CIRGenClass.cpp
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
clang/lib/CIR/CodeGen/CIRGenFunction.h
clang/test/CIR/CodeGen/ctor.cpp
Removed:
################################################################################
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index da8166a596d42..cc4a615dc392e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -117,6 +117,75 @@ static void emitMemberInitializer(CIRGenFunction &cgf,
cgf.emitInitializerForField(field, lhs, memberInit->getInit());
}
+static bool isInitializerOfDynamicClass(const CXXCtorInitializer *baseInit) {
+ const Type *baseType = baseInit->getBaseClass();
+ const auto *baseClassDecl =
+ cast<CXXRecordDecl>(baseType->castAs<RecordType>()->getDecl());
+ return baseClassDecl->isDynamicClass();
+}
+
+/// Gets the address of a direct base class within a complete object.
+/// This should only be used for (1) non-virtual bases or (2) virtual bases
+/// when the type is known to be complete (e.g. in complete destructors).
+///
+/// The object pointed to by 'thisAddr' is assumed to be non-null.
+Address CIRGenFunction::getAddressOfDirectBaseInCompleteClass(
+ mlir::Location loc, Address thisAddr, const CXXRecordDecl *derived,
+ const CXXRecordDecl *base, bool baseIsVirtual) {
+ // 'thisAddr' must be a pointer (in some address space) to Derived.
+ assert(thisAddr.getElementType() == convertType(derived));
+
+ // Compute the offset of the virtual base.
+ CharUnits offset;
+ const ASTRecordLayout &layout = getContext().getASTRecordLayout(derived);
+ if (baseIsVirtual)
+ offset = layout.getVBaseClassOffset(base);
+ else
+ offset = layout.getBaseClassOffset(base);
+
+ return builder.createBaseClassAddr(loc, thisAddr, convertType(base),
+ offset.getQuantity(),
+ /*assumeNotNull=*/true);
+}
+
+void CIRGenFunction::emitBaseInitializer(mlir::Location loc,
+ const CXXRecordDecl *classDecl,
+ CXXCtorInitializer *baseInit) {
+ assert(curFuncDecl && "loading 'this' without a func declaration?");
+ assert(isa<CXXMethodDecl>(curFuncDecl));
+
+ assert(baseInit->isBaseInitializer() && "Must have base initializer!");
+
+ Address thisPtr = loadCXXThisAddress();
+
+ const Type *baseType = baseInit->getBaseClass();
+ const auto *baseClassDecl =
+ cast<CXXRecordDecl>(baseType->castAs<RecordType>()->getDecl());
+
+ bool isBaseVirtual = baseInit->isBaseVirtual();
+
+ // If the initializer for the base (other than the constructor
+ // itself) accesses 'this' in any way, we need to initialize the
+ // vtables.
+ if (classDecl->isDynamicClass()) {
+ cgm.errorNYI(loc, "emitBaseInitializer: dynamic class");
+ return;
+ }
+
+ // We can pretend to be a complete class because it only matters for
+ // virtual bases, and we only do virtual bases for complete ctors.
+ Address v = getAddressOfDirectBaseInCompleteClass(
+ loc, thisPtr, classDecl, baseClassDecl, isBaseVirtual);
+ assert(!cir::MissingFeatures::aggValueSlotGC());
+ AggValueSlot aggSlot = AggValueSlot::forAddr(
+ v, Qualifiers(), AggValueSlot::IsDestructed, AggValueSlot::IsNotAliased,
+ getOverlapForBaseInit(classDecl, baseClassDecl, isBaseVirtual));
+
+ emitAggExpr(baseInit->getInit(), aggSlot);
+
+ assert(!cir::MissingFeatures::requiresCleanups());
+}
+
/// This routine generates necessary code to initialize base classes and
/// non-static data members belonging to this constructor.
void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
@@ -154,12 +223,29 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
return;
}
- if ((*b)->isBaseInitializer()) {
+ const mlir::Value oldThisValue = cxxThisValue;
+ if (!constructVBases && (*b)->isBaseInitializer() && (*b)->isBaseVirtual()) {
cgm.errorNYI(cd->getSourceRange(),
- "emitCtorPrologue: non-virtual base initializer");
+ "emitCtorPrologue: virtual base initializer");
return;
}
+ // Handle non-virtual base initializers.
+ for (; b != e && (*b)->isBaseInitializer(); b++) {
+ assert(!(*b)->isBaseVirtual());
+
+ if (cgm.getCodeGenOpts().StrictVTablePointers &&
+ cgm.getCodeGenOpts().OptimizationLevel > 0 &&
+ isInitializerOfDynamicClass(*b)) {
+ cgm.errorNYI(cd->getSourceRange(),
+ "emitCtorPrologue: strict vtable pointers");
+ return;
+ }
+ emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, *b);
+ }
+
+ cxxThisValue = oldThisValue;
+
if (classDecl->isDynamicClass()) {
cgm.errorNYI(cd->getSourceRange(),
"emitCtorPrologue: initialize vtable pointers");
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 715a37a5f3736..6d2dfb9269b5e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1593,10 +1593,15 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
- case CXXConstructionKind::NonVirtualBase:
+ // 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: other construction kind");
+ "emitCXXConstructExpr: virtual base constructor");
return;
+ case CXXConstructionKind::NonVirtualBase:
+ type = Ctor_Base;
+ break;
}
emitCXXConstructorCall(cd, type, forVirtualBase, delegating, dest, e);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index ffe1b701b244e..0d12c5c3edded 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -16,6 +16,7 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
#include <cstdint>
@@ -362,6 +363,28 @@ void AggExprEmitter::visitCXXParenListOrInitListExpr(
"visitCXXParenListOrInitListExpr Record or VariableSizeArray type");
}
+// TODO(cir): This could be shared with classic codegen.
+AggValueSlot::Overlap_t CIRGenFunction::getOverlapForBaseInit(
+ const CXXRecordDecl *rd, const CXXRecordDecl *baseRD, bool isVirtual) {
+ // If the most-derived object is a field declared with [[no_unique_address]],
+ // the tail padding of any virtual base could be reused for other subobjects
+ // of that field's class.
+ if (isVirtual)
+ return AggValueSlot::MayOverlap;
+
+ // If the base class is laid out entirely within the nvsize of the derived
+ // class, its tail padding cannot yet be initialized, so we can issue
+ // stores at the full width of the base class.
+ const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
+ if (layout.getBaseClassOffset(baseRD) +
+ getContext().getASTRecordLayout(baseRD).getSize() <=
+ layout.getNonVirtualSize())
+ return AggValueSlot::DoesNotOverlap;
+
+ // The tail padding may contain values we need to preserve.
+ return AggValueSlot::MayOverlap;
+}
+
void CIRGenFunction::emitAggExpr(const Expr *e, AggValueSlot slot) {
AggExprEmitter(*this, slot).Visit(const_cast<Expr *>(e));
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index f9a4c3f7bbab5..5feb5fc94d983 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -562,6 +562,19 @@ class CIRGenFunction : public CIRGenTypeCache {
}
Address loadCXXThisAddress();
+ /// Convert the given pointer to a complete class to the given direct base.
+ Address getAddressOfDirectBaseInCompleteClass(mlir::Location loc,
+ Address value,
+ const CXXRecordDecl *derived,
+ const CXXRecordDecl *base,
+ bool baseIsVirtual);
+
+ /// Determine whether a base class initialization may overlap some other
+ /// object.
+ AggValueSlot::Overlap_t getOverlapForBaseInit(const CXXRecordDecl *rd,
+ const CXXRecordDecl *baseRD,
+ bool isVirtual);
+
/// Get an appropriate 'undef' rvalue for the given type.
/// TODO: What's the equivalent for MLIR? Currently we're only using this for
/// void types so it just returns RValue::get(nullptr) but it'll need
@@ -762,6 +775,9 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitAutoVarCleanups(const AutoVarEmission &emission);
void emitAutoVarInit(const AutoVarEmission &emission);
+ void emitBaseInitializer(mlir::Location loc, const CXXRecordDecl *classDecl,
+ CXXCtorInitializer *baseInit);
+
LValue emitBinaryOperatorLValue(const BinaryOperator *e);
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
diff --git a/clang/test/CIR/CodeGen/ctor.cpp b/clang/test/CIR/CodeGen/ctor.cpp
index 4c2877f8460d0..2b06bb0f7cb08 100644
--- a/clang/test/CIR/CodeGen/ctor.cpp
+++ b/clang/test/CIR/CodeGen/ctor.cpp
@@ -219,3 +219,130 @@ void init_union() {
// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca {{.*}} ["s", init]
// CHECK-NEXT: cir.call @_ZN14UnionInitStrukC1Ev(%[[S_ADDR]])
// CHECK-NEXT: cir.return
+
+struct Base {
+ int a;
+ Base(int val) : a(val) {}
+};
+
+struct Derived : Base {
+ Derived(int val) : Base(val) {}
+};
+
+void test_derived() {
+ Derived d(1);
+}
+
+// CHECK: cir.func{{.*}} @_ZN4BaseC2Ei(%arg0: !cir.ptr<!rec_Base> {{.*}}, %arg1: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[A_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "a"}
+// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
+// CHECK-NEXT: cir.store{{.*}} %[[VAL]], %[[A_ADDR]]
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_ZN7DerivedC2Ei(%arg0: !cir.ptr<!rec_Derived> {{.*}}, %arg1: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
+// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
+// CHECK-NEXT: cir.call @_ZN4BaseC2Ei(%[[BASE]], %[[VAL]])
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_ZN7DerivedC1Ei(%arg0: !cir.ptr<!rec_Derived> {{.*}}, %arg1: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
+// CHECK-NEXT: cir.call @_ZN7DerivedC2Ei(%[[THIS]], %[[VAL]])
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_Z12test_derivedv
+// CHECK-NEXT: %[[D_ADDR:.*]] = cir.alloca {{.*}} ["d", init]
+// CHECK-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CHECK-NEXT: cir.call @_ZN7DerivedC1Ei(%[[D_ADDR]], %[[ONE]])
+// CHECK-NEXT: cir.return
+
+struct Base2 {
+ int b;
+ Base2(int val) : b(val) {}
+};
+
+struct Derived2 : Base, Base2 {
+ int c;
+ Derived2(int val1, int val2, int val3) : Base(val1), Base2(val2), c(val3) {}
+};
+
+void test_derived2() {
+ Derived2 d(1, 2, 3);
+}
+
+// CHECK: cir.func{{.*}} @_ZN5Base2C2Ei(%arg0: !cir.ptr<!rec_Base2> {{.*}}, %arg1: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL_ADDR:.*]] = cir.alloca {{.*}} ["val", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[B_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "b"}
+// CHECK-NEXT: %[[VAL:.*]] = cir.load{{.*}} %[[VAL_ADDR]]
+// CHECK-NEXT: cir.store{{.*}} %[[VAL]], %[[B_ADDR]]
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_ZN8Derived2C2Eiii(%arg0: !cir.ptr<!rec_Derived2>
+// CHECK-SAME: %arg1: !s32i
+// CHECK-SAME: %arg2: !s32i
+// CHECK-SAME: %arg3: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL1_ADDR:.*]] = cir.alloca {{.*}} ["val1", init]
+// CHECK-NEXT: %[[VAL2_ADDR:.*]] = cir.alloca {{.*}} ["val2", init]
+// CHECK-NEXT: %[[VAL3_ADDR:.*]] = cir.alloca {{.*}} ["val3", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL1_ADDR]]
+// CHECK-NEXT: cir.store %arg2, %[[VAL2_ADDR]]
+// CHECK-NEXT: cir.store %arg3, %[[VAL3_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[BASE:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived2> nonnull [0] -> !cir.ptr<!rec_Base>
+// CHECK-NEXT: %[[VAL1:.*]] = cir.load{{.*}} %[[VAL1_ADDR]]
+// CHECK-NEXT: cir.call @_ZN4BaseC2Ei(%[[BASE]], %[[VAL1]])
+// CHECK-NEXT: %[[BASE2:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived2> nonnull [4] -> !cir.ptr<!rec_Base2>
+// CHECK-NEXT: %[[VAL2:.*]] = cir.load{{.*}} %[[VAL2_ADDR]]
+// CHECK-NEXT: cir.call @_ZN5Base2C2Ei(%[[BASE2]], %[[VAL2]])
+// CHECK-NEXT: %[[C_ADDR:.*]] = cir.get_member %[[THIS]][2] {name = "c"}
+// CHECK-NEXT: %[[VAL3:.*]] = cir.load{{.*}} %[[VAL3_ADDR]]
+// CHECK-NEXT: cir.store{{.*}} %[[VAL3]], %[[C_ADDR]]
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_ZN8Derived2C1Eiii(%arg0: !cir.ptr<!rec_Derived2>
+// CHECK-SAME: %arg1: !s32i
+// CHECK-SAME: %arg2: !s32i
+// CHECK-SAME: %arg3: !s32i
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
+// CHECK-NEXT: %[[VAL1_ADDR:.*]] = cir.alloca {{.*}} ["val1", init]
+// CHECK-NEXT: %[[VAL2_ADDR:.*]] = cir.alloca {{.*}} ["val2", init]
+// CHECK-NEXT: %[[VAL3_ADDR:.*]] = cir.alloca {{.*}} ["val3", init]
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK-NEXT: cir.store %arg1, %[[VAL1_ADDR]]
+// CHECK-NEXT: cir.store %arg2, %[[VAL2_ADDR]]
+// CHECK-NEXT: cir.store %arg3, %[[VAL3_ADDR]]
+// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
+// CHECK-NEXT: %[[VAL1:.*]] = cir.load{{.*}} %[[VAL1_ADDR]]
+// CHECK-NEXT: %[[VAL2:.*]] = cir.load{{.*}} %[[VAL2_ADDR]]
+// CHECK-NEXT: %[[VAL3:.*]] = cir.load{{.*}} %[[VAL3_ADDR]]
+// CHECK-NEXT: cir.call @_ZN8Derived2C2Eiii(%[[THIS]], %[[VAL1]], %[[VAL2]], %[[VAL3]])
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func{{.*}} @_Z13test_derived2v
+// CHECK-NEXT: %[[D_ADDR:.*]] = cir.alloca {{.*}} ["d", init]
+// CHECK-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CHECK-NEXT: %[[TWO:.*]] = cir.const #cir.int<2> : !s32i
+// CHECK-NEXT: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
+// CHECK-NEXT: cir.call @_ZN8Derived2C1Eiii(%[[D_ADDR]], %[[ONE]], %[[TWO]], %[[THREE]])
+// CHECK-NEXT: cir.return
More information about the cfe-commits
mailing list