Skip to content

[CIR] Add support for delegating constructors #143932

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 12, 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
2 changes: 1 addition & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ struct MissingFeatures {
static bool astVarDeclInterface() { return false; }
static bool stackSaveOp() { return false; }
static bool aggValueSlot() { return false; }
static bool aggValueSlotMayOverlap() { return false; }
static bool generateDebugInfo() { return false; }
static bool pointerOverflowSanitizer() { return false; }
static bool fpConstraints() { return false; }
Expand Down Expand Up @@ -227,7 +228,6 @@ struct MissingFeatures {
static bool implicitConstructorArgs() { return false; }
static bool intrinsics() { return false; }
static bool attributeNoBuiltin() { return false; }
static bool emitCtorPrologue() { return false; }
static bool thunks() { return false; }
static bool runCleanupsScope() { return false; }

Expand Down
3 changes: 1 addition & 2 deletions clang/lib/CIR/CodeGen/CIRGenCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,7 @@ void CIRGenFunction::emitDelegateCallArg(CallArgList &args,
// For the most part, we just need to load the alloca, except that aggregate
// r-values are actually pointers to temporaries.
} else {
cgm.errorNYI(param->getSourceRange(),
"emitDelegateCallArg: convertTempToRValue");
args.add(convertTempToRValue(local, type, loc), type);
}

// Deactivate the cleanup for the callee-destructed param that was pushed.
Expand Down
38 changes: 38 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ bool CIRGenFunction::isConstructorDelegationValid(
return true;
}

/// This routine generates necessary code to initialize base classes and
/// non-static data members belonging to this constructor.
void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
CXXCtorType ctorType,
FunctionArgList &args) {
if (cd->isDelegatingConstructor())
return emitDelegatingCXXConstructorCall(cd, args);

if (cd->getNumCtorInitializers() != 0) {
// There's much more to do here.
cgm.errorNYI(cd->getSourceRange(), "emitCtorPrologue: any initializer");
return;
}
}

Address CIRGenFunction::loadCXXThisAddress() {
assert(curFuncDecl && "loading 'this' without a func declaration?");
assert(isa<CXXMethodDecl>(curFuncDecl));
Expand Down Expand Up @@ -102,6 +117,29 @@ void CIRGenFunction::emitDelegateCXXConstructorCall(
/*Delegating=*/true, thisAddr, delegateArgs, loc);
}

void CIRGenFunction::emitDelegatingCXXConstructorCall(
const CXXConstructorDecl *ctor, const FunctionArgList &args) {
assert(ctor->isDelegatingConstructor());

Address thisPtr = loadCXXThisAddress();

assert(!cir::MissingFeatures::objCGC());
assert(!cir::MissingFeatures::sanitizers());
AggValueSlot aggSlot = AggValueSlot::forAddr(
thisPtr, Qualifiers(), AggValueSlot::IsDestructed,
AggValueSlot::IsNotAliased, AggValueSlot::MayOverlap,
AggValueSlot::IsNotZeroed);

emitAggExpr(ctor->init_begin()[0]->getInit(), aggSlot);

const CXXRecordDecl *classDecl = ctor->getParent();
if (cgm.getLangOpts().Exceptions && !classDecl->hasTrivialDestructor()) {
cgm.errorNYI(ctor->getSourceRange(),
"emitDelegatingCXXConstructorCall: exception");
return;
}
}

Address CIRGenFunction::getAddressOfBaseClass(
Address value, const CXXRecordDecl *derived,
llvm::iterator_range<CastExpr::path_const_iterator> path,
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,12 @@ void CIRGenFunction::emitExprAsInit(const Expr *init, const ValueDecl *d,
return;
}
case cir::TEK_Aggregate:
emitAggExpr(init, AggValueSlot::forLValue(lvalue));
// The overlap flag here should be calculated.
assert(!cir::MissingFeatures::aggValueSlotMayOverlap());
emitAggExpr(init,
AggValueSlot::forLValue(lvalue, AggValueSlot::IsDestructed,
AggValueSlot::IsNotAliased,
AggValueSlot::MayOverlap));
return;
}
llvm_unreachable("bad evaluation kind");
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,23 @@ Address CIRGenFunction::emitArrayToPointerDecay(const Expr *e) {
return Address(ptr, addr.getAlignment());
}

/// Given the address of a temporary variable, produce an r-value of its type.
RValue CIRGenFunction::convertTempToRValue(Address addr, clang::QualType type,
clang::SourceLocation loc) {
LValue lvalue = makeAddrLValue(addr, type, AlignmentSource::Decl);
switch (getEvaluationKind(type)) {
case cir::TEK_Complex:
cgm.errorNYI(loc, "convertTempToRValue: complex type");
return RValue::get(nullptr);
case cir::TEK_Aggregate:
cgm.errorNYI(loc, "convertTempToRValue: aggregate type");
return RValue::get(nullptr);
case cir::TEK_Scalar:
return RValue::get(emitLoadOfScalar(lvalue, loc));
}
llvm_unreachable("bad evaluation kind");
}

/// Emit an `if` on a boolean condition, filling `then` and `else` into
/// appropriated regions.
mlir::LogicalResult CIRGenFunction::emitIfOnBoolExpr(const Expr *cond,
Expand Down Expand Up @@ -1473,6 +1490,10 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
type = Ctor_Complete;
break;
case CXXConstructionKind::Delegating:
// We should be emitting a constructor; GlobalDecl will assert this
type = curGD.getCtorType();
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
case CXXConstructionKind::NonVirtualBase:
cgm.errorNYI(e->getSourceRange(),
Expand Down
10 changes: 8 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
cgf.cgm.errorNYI("emitInitializationToLValue TEK_Complex");
break;
case cir::TEK_Aggregate:
cgf.emitAggExpr(e, AggValueSlot::forLValue(lv));
cgf.emitAggExpr(e, AggValueSlot::forLValue(lv, AggValueSlot::IsDestructed,
AggValueSlot::IsNotAliased,
AggValueSlot::MayOverlap,
dest.isZeroed()));

return;
case cir::TEK_Scalar:
if (lv.isSimple())
Expand Down Expand Up @@ -284,6 +288,8 @@ LValue CIRGenFunction::emitAggExprToLValue(const Expr *e) {
assert(hasAggregateEvaluationKind(e->getType()) && "Invalid argument!");
Address temp = createMemTemp(e->getType(), getLoc(e->getSourceRange()));
LValue lv = makeAddrLValue(temp, e->getType());
emitAggExpr(e, AggValueSlot::forLValue(lv));
emitAggExpr(e, AggValueSlot::forLValue(lv, AggValueSlot::IsNotDestructed,
AggValueSlot::IsNotAliased,
AggValueSlot::DoesNotOverlap));
return lv;
}
10 changes: 2 additions & 8 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,14 +526,8 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList &args) {
// TODO: in restricted cases, we can emit the vbase initializers of a
// complete ctor and then delegate to the base ctor.

assert(!cir::MissingFeatures::emitCtorPrologue());
if (ctor->isDelegatingConstructor()) {
// This will be handled in emitCtorPrologue, but we should emit a diagnostic
// rather than silently fail to delegate.
cgm.errorNYI(ctor->getSourceRange(),
"emitConstructorBody: delegating ctor");
return;
}
// Emit the constructor prologue, i.e. the base and member initializers.
emitCtorPrologue(ctor, ctorType, args);

// TODO(cir): propagate this result via mlir::logical result. Just unreachable
// now just to have it handled.
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ class CIRGenFunction : public CIRGenTypeCache {

bool shouldNullCheckClassCastValue(const CastExpr *ce);

RValue convertTempToRValue(Address addr, clang::QualType type,
clang::SourceLocation loc);

static bool
isConstructorDelegationValid(const clang::CXXConstructorDecl *ctor);

Expand Down Expand Up @@ -797,6 +800,16 @@ class CIRGenFunction : public CIRGenTypeCache {
const CXXMethodDecl *md,
ReturnValueSlot returnValue);

void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
clang::CXXCtorType ctorType, FunctionArgList &args);

// It's important not to confuse this and emitDelegateCXXConstructorCall.
// Delegating constructors are the C++11 feature. The constructor delegate
// optimization is used to reduce duplication in the base and complete
// constructors where they are substantially the same.
void emitDelegatingCXXConstructorCall(const CXXConstructorDecl *ctor,
const FunctionArgList &args);

mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);

/// Emit an expression as an initializer for an object (variable, field, etc.)
Expand Down
53 changes: 47 additions & 6 deletions clang/lib/CIR/CodeGen/CIRGenValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,23 +267,64 @@ class AggValueSlot {
Address addr;
clang::Qualifiers quals;

/// This is set to true if some external code is responsible for setting up a
/// destructor for the slot. Otherwise the code which constructs it should
/// push the appropriate cleanup.
LLVM_PREFERRED_TYPE(bool)
[[maybe_unused]] unsigned destructedFlag : 1;

/// This is set to true if the memory in the slot is known to be zero before
/// the assignment into it. This means that zero fields don't need to be set.
bool zeroedFlag : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned zeroedFlag : 1;

/// This is set to true if the slot might be aliased and it's not undefined
/// behavior to access it through such an alias. Note that it's always
/// undefined behavior to access a C++ object that's under construction
/// through an alias derived from outside the construction process.
///
/// This flag controls whether calls that produce the aggregate
/// value may be evaluated directly into the slot, or whether they
/// must be evaluated into an unaliased temporary and then memcpy'ed
/// over. Since it's invalid in general to memcpy a non-POD C++
/// object, it's important that this flag never be set when
/// evaluating an expression which constructs such an object.
LLVM_PREFERRED_TYPE(bool)
[[maybe_unused]] unsigned aliasedFlag : 1;

/// This is set to true if the tail padding of this slot might overlap
/// another object that may have already been initialized (and whose
/// value must be preserved by this initialization). If so, we may only
/// store up to the dsize of the type. Otherwise we can widen stores to
/// the size of the type.
LLVM_PREFERRED_TYPE(bool)
[[maybe_unused]] unsigned overlapFlag : 1;

public:
enum IsDestructed_t { IsNotDestructed, IsDestructed };
enum IsZeroed_t { IsNotZeroed, IsZeroed };
enum IsAliased_t { IsNotAliased, IsAliased };
enum Overlap_t { MayOverlap, DoesNotOverlap };

AggValueSlot(Address addr, clang::Qualifiers quals, bool zeroedFlag)
: addr(addr), quals(quals), zeroedFlag(zeroedFlag) {}
AggValueSlot(Address addr, clang::Qualifiers quals, bool destructedFlag,
bool zeroedFlag, bool aliasedFlag, bool overlapFlag)
: addr(addr), quals(quals), destructedFlag(destructedFlag),
zeroedFlag(zeroedFlag), aliasedFlag(aliasedFlag),
overlapFlag(overlapFlag) {}

static AggValueSlot forAddr(Address addr, clang::Qualifiers quals,
IsDestructed_t isDestructed,
IsAliased_t isAliased, Overlap_t mayOverlap,
IsZeroed_t isZeroed = IsNotZeroed) {
return AggValueSlot(addr, quals, isZeroed);
return AggValueSlot(addr, quals, isDestructed, isZeroed, isAliased,
mayOverlap);
}

static AggValueSlot forLValue(const LValue &lv) {
return forAddr(lv.getAddress(), lv.getQuals());
static AggValueSlot forLValue(const LValue &LV, IsDestructed_t isDestructed,
IsAliased_t isAliased, Overlap_t mayOverlap,
IsZeroed_t isZeroed = IsNotZeroed) {
return forAddr(LV.getAddress(), LV.getQuals(), isDestructed, isAliased,
mayOverlap, isZeroed);
}

clang::Qualifiers getQualifiers() const { return quals; }
Expand Down
46 changes: 46 additions & 0 deletions clang/test/CIR/CodeGen/ctor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,49 @@ void bar() {
// CHECK-NEXT: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
// CHECK-NEXT: cir.call @_ZN13VariadicStrukC1Eiz(%[[S_ADDR]], %[[ONE]], %[[TWO]], %[[THREE]])
// CHECK-NEXT: cir.return

struct DelegatingStruk {
int a;
DelegatingStruk(int n) { a = n; }
DelegatingStruk() : DelegatingStruk(0) {}
};

void bam() {
DelegatingStruk s;
}

// CHECK: cir.func @_ZN15DelegatingStrukC2Ei(%arg0: !cir.ptr<!rec_DelegatingStruk>
// CHECK-SAME: %arg1: !s32i
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
// CHECK-NEXT: %[[N_ADDR:.*]] = cir.alloca {{.*}} ["n", init]
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
// CHECK-NEXT: cir.store %arg1, %[[N_ADDR]]
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
// CHECK-NEXT: %[[N:.*]] = cir.load{{.*}} %[[N_ADDR]]
// CHECK-NEXT: %[[A_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "a"}
// CHECK-NEXT: cir.store{{.*}} %[[N]], %[[A_ADDR]]
// CHECK-NEXT: cir.return

// CHECK: cir.func @_ZN15DelegatingStrukC1Ei(%arg0: !cir.ptr<!rec_DelegatingStruk>
// CHECK-SAME: %arg1: !s32i
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
// CHECK-NEXT: %[[N_ADDR:.*]] = cir.alloca {{.*}} ["n", init]
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
// CHECK-NEXT: cir.store %arg1, %[[N_ADDR]]
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
// CHECK-NEXT: %[[N:.*]] = cir.load{{.*}} %[[N_ADDR]]
// CHECK-NEXT: cir.call @_ZN15DelegatingStrukC2Ei(%[[THIS]], %[[N]])
// CHECK-NEXT: cir.return

// CHECK: cir.func @_ZN15DelegatingStrukC1Ev(%arg0: !cir.ptr<!rec_DelegatingStruk>
// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]]
// CHECK-NEXT: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
// CHECK-NEXT: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
// CHECK-NEXT: cir.call @_ZN15DelegatingStrukC1Ei(%[[THIS]], %[[ZERO]])
// CHECK-NEXT: cir.return

// CHECK: cir.func @_Z3bamv
// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca {{.*}} ["s", init]
// CHECK-NEXT: cir.call @_ZN15DelegatingStrukC1Ev(%[[S_ADDR]])
// CHECK-NEXT: cir.return