[clang] [CIR] Inheriting Constructor/inheriting ctor inlining lowering (PR #191467)
Erich Keane via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 13 06:20:09 PDT 2026
https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/191467
>From 17e78b9189d51317646b71955d2d3fc45790ac65 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Thu, 2 Apr 2026 15:55:35 -0700
Subject: [PATCH 1/3] [CIR] Inheriting Constructor/inheriting ctor inlining
lowering
In cases with inheritance/vertual tables/etc, we need to generate a
series of constructors to delegate to. There are a handful slightly
different cases where we need to generate these/generate calls to these,
so this patch does that lowering.
The test check-lines are a bit confusing thanks to the ordering
differences between declarations. However the LLVM/OGCG lines are
copy pasted (plus minor attribute differences), with the exception of
the call to a delegated constructor.
One thing of note here: There is a difference in behavior with the
delegated constructor, which is called out in the test in a comment.
Classic codegen has a bug where it correctly creates the declaration
without arguments (since this constructor is only for initializing the
vtable pointers, arguments aren't necessary). However, when
classic-codegen creates the call, it doesn't omit them.
This isn't a problem there, however in CIR, this causes us to fail the
verifier, so this fixes that in CIR, but leaves it alone in OGCG.
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 14 +-
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 136 ++++++++++++-
clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp | 7 +-
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 18 +-
clang/lib/CIR/CodeGen/CIRGenFunction.h | 66 +++++++
clang/lib/CIR/CodeGen/CIRGenTypes.h | 6 +
clang/test/CIR/CodeGen/inherited-ctors.cpp | 182 ++++++++++++++++++
7 files changed, 412 insertions(+), 17 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/inherited-ctors.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 876fef687b477..4dc24113f71b9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -736,9 +736,8 @@ CIRGenTypes::arrangeCXXStructorDeclaration(GlobalDecl gd) {
if (auto *cd = dyn_cast<CXXConstructorDecl>(md)) {
// A base class inheriting constructor doesn't get forwarded arguments
// needed to construct a virtual base (or base class thereof)
- if (cd->getInheritedConstructor())
- cgm.errorNYI(cd->getSourceRange(),
- "arrangeCXXStructorDeclaration: inheriting constructor");
+ if (auto inherited = cd->getInheritedConstructor())
+ passParams = inheritingCtorHasParams(inherited, gd.getCtorType());
}
CanQual<FunctionProtoType> fpt = getFormalType(md);
@@ -1427,3 +1426,12 @@ void CIRGenFunction::emitCallArgs(
std::reverse(args.begin() + callArgsStart, args.end());
}
}
+
+bool CIRGenTypes::inheritingCtorHasParams(
+ const InheritedConstructor &inherited, CXXCtorType type) {
+ // Parameters are unnecessary if we're constructing a base class subobject
+ // and the inherited constructor lives in a virtual base.
+ return type == Ctor_Complete ||
+ !inherited.getShadowDecl()->constructsVirtualBase() ||
+ !getASTContext().getTargetInfo().getCXXABI().hasConstructorVariants();
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 748d450d6dc10..bf0e28ae30ba5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -1344,14 +1344,134 @@ void CIRGenFunction::emitCXXConstructorCall(const clang::CXXConstructorDecl *d,
assert(!cir::MissingFeatures::opCallArgEvaluationOrder());
- emitCallArgs(args, fpt, e->arguments(), e->getConstructor(),
- /*ParamsToSkip=*/0);
+ if (auto inherited = d->getInheritedConstructor();
+ !inherited || cgm.getTypes().inheritingCtorHasParams(inherited, type))
+ emitCallArgs(args, fpt, e->arguments(), e->getConstructor(),
+ /*ParamsToSkip=*/0);
assert(!cir::MissingFeatures::sanitizers());
emitCXXConstructorCall(d, type, forVirtualBase, delegating, thisAddr, args,
e->getExprLoc());
}
+static bool canEmitDelegateCallArgs(CIRGenModule &cgm, ASTContext &ctx,
+ const CXXConstructorDecl *d,
+ CXXCtorType type) {
+ // We can't forward a variadic call.
+ if (d->isVariadic())
+ return false;
+
+ if (ctx.getTargetInfo().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
+ // If the parameters are callee-cleanup, it's not safe to forward.
+ if (llvm::any_of(d->parameters(), [&ctx](const ParmVarDecl *param) {
+ return param->needsDestruction(ctx);
+ }))
+ return false;
+
+ // FIXME(CIR): It isn't clear to me that this is the right answer here,
+ // classic-codegen decides the answer is 'false' if there is an inalloca
+ // argument. What our default should be in this case, or what we want to do.
+ // When we get an understanding of what the the calling-convention code
+ // needs here, we should be able to replace this with either a 'return
+ // false' or 'return true'.
+ cgm.errorNYI(d->getSourceRange(),
+ "canEmitDelegateCallArgs: args-destroyed-L-to-R in callee");
+ }
+
+ return true;
+}
+
+void CIRGenFunction::emitInheritedCXXConstructorCall(
+ const CXXConstructorDecl *d, bool forVirtualBase, Address thisAddr,
+ bool inheritedFromVBase, const CXXInheritedCtorInitExpr *e) {
+
+ CallArgList ctorArgs;
+ CallArg thisArg(RValue::get(getAsNaturalPointerTo(
+ thisAddr, d->getThisType()->getPointeeType())),
+ d->getThisType());
+
+ if (inheritedFromVBase &&
+ cgm.getTarget().getCXXABI().hasConstructorVariants()) {
+ cgm.errorNYI(
+ e->getSourceRange(),
+ "emitInheritedCXXConstructorCall inheritedFromVBasewith ctor variants");
+ return;
+ } else if (!cxxInheritedCtorInitExprArgs.empty()) {
+ // The inheriting constructor was inlined; just inject its arguments.
+ assert(cxxInheritedCtorInitExprArgs.size() >= d->getNumParams() &&
+ "wrong number of parameters for inherited constructor call");
+ ctorArgs = cxxInheritedCtorInitExprArgs;
+ ctorArgs[0] = thisArg;
+ } else {
+ ctorArgs.push_back(thisArg);
+ const auto *outerCtor = cast<CXXConstructorDecl>(curCodeDecl);
+ assert(outerCtor->getNumParams() == d->getNumParams());
+ assert(!outerCtor->isVariadic() && "should have been inlined");
+
+ for (const ParmVarDecl *param : outerCtor->parameters()) {
+ assert(getContext().hasSameUnqualifiedType(
+ outerCtor->getParamDecl(param->getFunctionScopeIndex())->getType(),
+ param->getType()));
+ emitDelegateCallArg(ctorArgs, param, e->getLocation());
+
+ if (param->hasAttr<PassObjectSizeAttr>())
+ cgm.errorNYI(
+ e->getLocation(),
+ "emitInheritedCXXConstructorCall: pass object size attr argument");
+ }
+ }
+
+ emitCXXConstructorCall(d, Ctor_Base, forVirtualBase, /*delegating=*/false,
+ thisAddr, ctorArgs, e->getLocation());
+}
+
+void CIRGenFunction::emitInlinedInheritingCXXConstructorCall(
+ SourceLocation loc, const CXXConstructorDecl *d, CXXCtorType ctorType,
+ bool forVirtualBase, bool delegating, CallArgList &args) {
+ GlobalDecl gd(d, ctorType);
+ assert(!cir::MissingFeatures::generateDebugInfo());
+ assert(!cir::MissingFeatures::runCleanupsScope());
+ InlinedInheritingConstructorScope scope(*this, gd);
+
+ // Save the arguments to be passed to the inherited constructor.
+ cxxInheritedCtorInitExprArgs = args;
+
+ FunctionArgList params;
+ QualType retTy = buildFunctionArgList(gd, params);
+
+ cgm.getCXXABI().addImplicitConstructorArgs(*this, d, ctorType, forVirtualBase,
+ delegating, args);
+
+ // Emit a simplified prolog. We only need to emit the implicit params.
+ assert(args.size() >= params.size() && "too few arguments for call");
+ for (auto [idx, arg, parm] :
+ llvm::zip_longest(llvm::index_range{0, args.size()}, args, params)) {
+ if (idx < params.size() && isa<ImplicitParamDecl>(*parm)) {
+ mlir::Location parmLoc = getLoc((*parm)->getSourceRange());
+ RValue argVal = arg->getRValue(*this, parmLoc);
+
+ LValue allocaVal = makeAddrLValue(
+ createTempAlloca(convertType((*parm)->getType()),
+ getContext().getDeclAlign(*parm), parmLoc),
+ (*parm)->getType());
+
+ emitStoreThroughLValue(argVal, allocaVal, /*isInit=*/true);
+
+ setAddrOfLocalVar((*parm), allocaVal.getAddress());
+ }
+ }
+
+ // FIXME(cir): it isn't clear what it takes to get here with a constructor?
+ // Leave as an NYI until we come across a reproducer.
+ if (!retTy->isVoidType())
+ cgm.errorNYI(d->getSourceRange(),
+ "emitInlinedInheritingCXXConstructorCall: non-void return");
+
+ cgm.getCXXABI().emitInstanceFunctionProlog(loc, *this);
+ cxxThisValue = cxxabiThisValue;
+ emitCtorPrologue(d, ctorType, params);
+}
+
void CIRGenFunction::emitCXXConstructorCall(
const CXXConstructorDecl *d, CXXCtorType type, bool forVirtualBase,
bool delegating, Address thisAddr, CallArgList &args, SourceLocation loc) {
@@ -1370,10 +1490,14 @@ void CIRGenFunction::emitCXXConstructorCall(
bool passPrototypeArgs = true;
// Check whether we can actually emit the constructor before trying to do so.
- if (d->getInheritedConstructor()) {
- cgm.errorNYI(d->getSourceRange(),
- "emitCXXConstructorCall: inherited constructor");
- return;
+ if (auto inherited = d->getInheritedConstructor()) {
+ passPrototypeArgs = getTypes().inheritingCtorHasParams(inherited, type);
+ if (passPrototypeArgs &&
+ !canEmitDelegateCallArgs(cgm, cgm.getASTContext(), d, type)) {
+ emitInlinedInheritingCXXConstructorCall(loc, d, type, forVirtualBase,
+ delegating, args);
+ return;
+ }
}
// Insert any ABI-specific implicit constructor arguments.
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index ffce8a6bf86a7..21a6f9209bb17 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -501,8 +501,11 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
Visit(dae->getExpr());
}
void VisitCXXInheritedCtorInitExpr(const CXXInheritedCtorInitExpr *e) {
- cgf.cgm.errorNYI(e->getSourceRange(),
- "AggExprEmitter: VisitCXXInheritedCtorInitExpr");
+ AggValueSlot slot =
+ ensureSlot(cgf.getLoc(e->getSourceRange()), e->getType());
+ cgf.emitInheritedCXXConstructorCall(e->getConstructor(),
+ e->constructsVBase(), slot.getAddress(),
+ e->inheritedFromVBase(), e);
}
/// Emit the initializer for a std::initializer_list initialized with a
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index f7f9331060956..fcfbeb809371e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -990,13 +990,19 @@ clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
cgm.getCXXABI().buildThisParam(*this, args);
}
+ bool passedParams = true;
if (const auto *cd = dyn_cast<CXXConstructorDecl>(fd))
- if (cd->getInheritedConstructor())
- cgm.errorNYI(fd->getSourceRange(),
- "buildFunctionArgList: inherited constructor");
-
- for (auto *param : fd->parameters())
- args.push_back(param);
+ if (auto inherited = cd->getInheritedConstructor())
+ passedParams =
+ getTypes().inheritingCtorHasParams(inherited, gd.getCtorType());
+
+ if (passedParams) {
+ for (auto *param : fd->parameters()) {
+ args.push_back(param);
+ if (param->hasAttr<PassObjectSizeAttr>())
+ cgm.errorNYI(param->getSourceRange(), "pass-object-size attribute");
+ }
+ }
if (md && (isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md)))
cgm.getCXXABI().addImplicitStructorParams(*this, retTy, args);
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 88c7996eab569..612fee560f8ee 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -137,11 +137,16 @@ class CIRGenFunction : public CIRGenTypeCache {
/// expression.
Address cxxDefaultInitExprThis = Address::invalid();
+ /// The values of function arguments to use when evaluating
+ /// CXXInheritedCtorInitExprs within this context.
+ CallArgList cxxInheritedCtorInitExprArgs;
+
// Holds the Decl for the current outermost non-closure context
const clang::Decl *curFuncDecl = nullptr;
/// This is the inner-most code context, which includes blocks.
const clang::Decl *curCodeDecl = nullptr;
const CIRGenFunctionInfo *curFnInfo = nullptr;
+ QualType fnRetTy;
/// The current function or global initializer that is generated code for.
/// This is usually a cir::FuncOp, but it can also be a cir::GlobalOp for
@@ -1564,6 +1569,15 @@ class CIRGenFunction : public CIRGenTypeCache {
bool delegating, Address thisAddr,
CallArgList &args, clang::SourceLocation loc);
+ void emitInheritedCXXConstructorCall(const CXXConstructorDecl *d,
+ bool forVirtualBase, Address thisAddr,
+ bool inheritedFromVBase,
+ const CXXInheritedCtorInitExpr *e);
+
+ void emitInlinedInheritingCXXConstructorCall(
+ SourceLocation loc, const CXXConstructorDecl *d, CXXCtorType ctorType,
+ bool forVirtualBase, bool delegating, CallArgList &args);
+
void emitCXXDeleteExpr(const CXXDeleteExpr *e);
void emitCXXDestructorCall(const CXXDestructorDecl *dd, CXXDtorType type,
@@ -2395,6 +2409,58 @@ class CIRGenFunction : public CIRGenTypeCache {
private:
QualType getVarArgType(const Expr *arg);
+
+ class InlinedInheritingConstructorScope {
+ public:
+ InlinedInheritingConstructorScope(CIRGenFunction &cgf, GlobalDecl gd)
+ : cgf(cgf), oldCurGD(cgf.curGD), oldCurFuncDecl(cgf.curFuncDecl),
+ oldCurCodeDecl(cgf.curCodeDecl),
+ oldCxxabiThisDecl(cgf.cxxabiThisDecl),
+ oldCxxThisValue(cgf.cxxThisValue),
+ oldCxxThisAlignment(cgf.cxxThisAlignment),
+ oldReturnValue(cgf.returnValue), oldFnRetTy(cgf.fnRetTy),
+ oldCxxInheritedCtorInitExprArgs(
+ std::move(cgf.cxxInheritedCtorInitExprArgs)) {
+ cgf.curGD = gd;
+ cgf.curFuncDecl = cast<CXXConstructorDecl>(gd.getDecl());
+ cgf.curCodeDecl = cgf.curFuncDecl;
+ cgf.cxxabiThisDecl = nullptr;
+ cgf.cxxabiThisValue = nullptr;
+ cgf.cxxThisValue = nullptr;
+ cgf.cxxThisAlignment = CharUnits();
+ cgf.returnValue = Address::invalid();
+ cgf.fnRetTy = QualType();
+ cgf.cxxInheritedCtorInitExprArgs.clear();
+ // FIXME: at one point when we wnat to call one of these, we'll need
+ // CXXInheritedCtorInitExprArgs here too.
+ }
+ ~InlinedInheritingConstructorScope() {
+ cgf.curGD = oldCurGD;
+ cgf.curFuncDecl = oldCurFuncDecl;
+ cgf.curCodeDecl = oldCurCodeDecl;
+ cgf.cxxabiThisDecl = oldCxxabiThisDecl;
+ cgf.cxxabiThisValue = oldCxxabiThisValue;
+ cgf.cxxThisValue = oldCxxThisValue;
+ cgf.cxxThisAlignment = oldCxxThisAlignment;
+ cgf.returnValue = oldReturnValue;
+ cgf.fnRetTy = oldFnRetTy;
+ cgf.cxxInheritedCtorInitExprArgs =
+ std::move(oldCxxInheritedCtorInitExprArgs);
+ }
+
+ private:
+ CIRGenFunction &cgf;
+ GlobalDecl oldCurGD;
+ const Decl *oldCurFuncDecl;
+ const Decl *oldCurCodeDecl;
+ ImplicitParamDecl *oldCxxabiThisDecl;
+ mlir::Value oldCxxabiThisValue;
+ mlir::Value oldCxxThisValue;
+ clang::CharUnits oldCxxThisAlignment;
+ Address oldReturnValue;
+ QualType oldFnRetTy;
+ CallArgList oldCxxInheritedCtorInitExprArgs;
+ };
};
} // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index 172b348ac78cf..c881b1b6e69e4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -29,6 +29,7 @@ class ASTContext;
class FunctionType;
class GlobalDecl;
class QualType;
+class TargetInfo;
class Type;
} // namespace clang
@@ -132,6 +133,11 @@ class CIRGenTypes {
cir::FuncType getFunctionType(clang::GlobalDecl gd);
+ /// Determine if a C++ inheriting constructor should have parameters matching
+ /// those of its inherited constructor.
+ bool inheritingCtorHasParams(const InheritedConstructor &inherited,
+ CXXCtorType type);
+
// The arrangement methods are split into three families:
// - those meant to drive the signature and prologue/epilogue
// of a function declaration or definition,
diff --git a/clang/test/CIR/CodeGen/inherited-ctors.cpp b/clang/test/CIR/CodeGen/inherited-ctors.cpp
new file mode 100644
index 0000000000000..158bc9f11b94a
--- /dev/null
+++ b/clang/test/CIR/CodeGen/inherited-ctors.cpp
@@ -0,0 +1,182 @@
+// 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
+
+struct Base {
+ Base(int i);
+ Base(float, ...);
+};
+
+struct Derived : Base {
+ using Base::Base;
+};
+
+struct VirtDerived : virtual Base {
+ using Base::Base;
+};
+
+struct VirtualDelegatingCtor : VirtDerived {
+ VirtualDelegatingCtor(int x) : Base(x), VirtDerived(x){}
+};
+
+void emitDelegateCallArgs() {
+ // ONLY PassPrototypeArgs
+ Derived canEmitDelegateCallArgs{1};
+}
+
+void cannotEmitDelegateCallArgs() {
+ // Inside of the PassPrototypeArgs && !canEmitDelegateCallArgs
+ Derived cannotEmitDelgateCallArgs{1.1f,2,3.0};
+}
+void fallsthrough() {
+ // !PassPrototypeArgs
+ VirtualDelegatingCtor noInheritingCtorHasParams{1};
+}
+
+
+// LLVM and OGCG check labels are identical other than the 1 difference called out (and ordering).
+// CIR-LABEL: cir.func private @_ZN4BaseC2Ei(!cir.ptr<!rec_Base>{{.*}}, !s32i{{.*}}) special_member<#cir.cxx_ctor<!rec_Base, custom>>
+// LLVM-LABEL: declare void @_ZN4BaseC2Ei(ptr {{.*}}, i32 {{.*}})
+//
+//
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr @_ZN7DerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_Derived>{{.*}}, %{{.*}}: !s32i{{.*}}) special_member<#cir.cxx_ctor<!rec_Derived, custom>>
+// CIR: %[[THIS_ALLOCA:.*]] = cir.alloca !cir.ptr<!rec_Derived>, !cir.ptr<!cir.ptr<!rec_Derived>>, ["this", init]
+// CIR: %[[INT_ALLOCA:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["", init]
+// CIR: %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOCA]] : !cir.ptr<!cir.ptr<!rec_Derived>>, !cir.ptr<!rec_Derived>
+// CIR: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS_LOAD]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
+// CIR: %[[INT:.*]] = cir.load align(4) %[[INT_ALLOCA]] : !cir.ptr<!s32i>, !s32i
+// CIR: cir.call @_ZN4BaseC2Ei(%[[BASE_ADDR]], %[[INT]]) : (!cir.ptr<!rec_Base>{{.*}}, !s32i{{.*}}) -> ()
+//
+// LLVM-LABEL: define linkonce_odr void @_ZN7DerivedCI24BaseEi(ptr {{.*}}, i32 {{.*}})
+// LLVM: %[[THIS_ALLOCA:.*]] = alloca ptr
+// LLVM: %[[INT_ALLOCA:.*]] = alloca i32
+// LLVM: %[[BASE_ADDR:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// LLVM: %[[INT:.*]] = load i32, ptr %[[INT_ALLOCA]]
+// LLVM: call void @_ZN4BaseC2Ei(ptr {{.*}}%[[BASE_ADDR]], i32 {{.*}}[[INT]])
+//
+//
+// CIR-LABEL: cir.func no_inline dso_local @_Z20emitDelegateCallArgsv()
+// CIR: cir.call @_ZN7DerivedCI14BaseEi(%{{.*}}, %{{.*}}) : (!cir.ptr<!rec_Derived>{{.*}}, !s32i{{.*}}) -> ()
+// LLVM-LABEL: define dso_local void @_Z20emitDelegateCallArgsv()
+// LLVM: call void @_ZN7DerivedCI14BaseEi(ptr {{.*}}, i32 {{.*}}1)
+//
+// CIR-LABEL: cir.func private @_ZN4BaseC2Efz(!cir.ptr<!rec_Base>{{.*}}, !cir.float{{.*}}, ...) special_member<#cir.cxx_ctor<!rec_Base, custom>>
+// LLVM-LABEL: declare void @_ZN4BaseC2Efz(ptr {{.*}}, float {{.*}}, ...)
+//
+// CIR-LABEL: cir.func no_inline dso_local @_Z26cannotEmitDelegateCallArgsv()
+// CIR: %[[TMP_ALLOCA:.*]] = cir.alloca !cir.ptr<!rec_Derived>, !cir.ptr<!cir.ptr<!rec_Derived>>, ["tmp", init]
+// CIR: %[[FP_1_1:.*]] = cir.const #cir.fp<1.1{{.*}}> : !cir.float
+// CIR: %[[TWO:.*]] = cir.const #cir.int<2> : !s32i
+// CIR: %[[THREE:.*]] = cir.const #cir.fp<3.0{{.*}}> : !cir.double
+// CIR: %[[LOAD_DERIVED:.*]] = cir.load %[[TMP_ALLOCA]] : !cir.ptr<!cir.ptr<!rec_Derived>>, !cir.ptr<!rec_Derived>
+// CIR: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[LOAD_DERIVED]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
+// CIR: cir.call @_ZN4BaseC2Efz(%[[BASE_ADDR]], %[[FP_1_1]], %[[TWO]], %[[THREE]]) : (!cir.ptr<!rec_Base>{{.*}}, !cir.float{{.*}}, !s32i{{.*}}, !cir.double{{.*}}) -> ()
+//
+// LLVM-LABEL: define dso_local void @_Z26cannotEmitDelegateCallArgsv()
+// LLVM: %[[TMP_ALLOCA:.*]] = alloca ptr
+// LLVM: %[[TMP_LOAD:.*]] = load ptr, ptr %[[TMP_ALLOCA]]
+// LLVM: call void (ptr, float, ...) @_ZN4BaseC2Efz(ptr {{.*}}%[[TMP_LOAD]], float {{.*}}0x3FF19999A{{.*}}, i32 {{.*}}2, double {{.*}}3.000000e+00)
+//
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr @_ZN11VirtDerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_VirtDerived> {{.*}}, %{{.*}}: !cir.ptr<!cir.ptr<!void>>{{.*}}) special_member<#cir.cxx_ctor<!rec_VirtDerived, custom>>
+// CIR: %[[THIS_ALLOCA:.*]] = cir.alloca !cir.ptr<!rec_VirtDerived>, !cir.ptr<!cir.ptr<!rec_VirtDerived>>, ["this", init] {alignment = 8 : i64}
+// CIR: %[[VTT_ALLOCA:.]] = cir.alloca !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, ["vtt", init] {alignment = 8 : i64}
+// CIR: %[[THIS:.*]] = cir.load %[[THIS_ALLOCA]] : !cir.ptr<!cir.ptr<!rec_VirtDerived>>, !cir.ptr<!rec_VirtDerived>
+// CIR: %[[VTT:.*]] = cir.load align(8) %[[VTT_ALLOCA]] : !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, !cir.ptr<!cir.ptr<!void>>
+// CIR: %[[VTT_ADDR:.*]] = cir.vtt.address_point %[[VTT]] : !cir.ptr<!cir.ptr<!void>>, offset = 0 -> !cir.ptr<!cir.ptr<!void>>
+// CIR: %[[VTT_ADDR_CAST:.*]] = cir.cast bitcast %[[VTT_ADDR]] : !cir.ptr<!cir.ptr<!void>> -> !cir.ptr<!cir.vptr>
+// CIR: %[[VTT_ADDR_LOAD:.*]] = cir.load align(8) %[[VTT_ADDR_CAST]] : !cir.ptr<!cir.vptr>, !cir.vptr
+// CIR: %[[VPTR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_VirtDerived> -> !cir.ptr<!cir.vptr>
+// CIR: cir.store align(8) %[[VTT_ADDR_LOAD]], %[[VPTR]] : !cir.vptr, !cir.ptr<!cir.vptr>
+
+// LLVM-LABEL: define linkonce_odr void @_ZN11VirtDerivedCI24BaseEi(ptr {{.*}}, ptr {{.*}})
+// LLVM: %[[THIS_ALLOCA:.*]] = alloca ptr
+// LLVM: %[[VTT_ALLOCA:.*]] = alloca ptr
+// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// LLVM: %[[VTT:.*]] = load ptr, ptr %[[VTT_ALLOCA]]
+// LLVM: %[[VTT_ADDR_LOAD:.*]] = load ptr, ptr %[[VTT]]
+// LLVM: store ptr %[[VTT_ADDR_LOAD]], ptr %[[THIS]]
+//
+//
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr @_ZN21VirtualDelegatingCtorC1Ei(%{{.*}}: !cir.ptr<!rec_VirtualDelegatingCtor> {{.*}}, %{{.*}}: !s32i {{.*}}) special_member<#cir.cxx_ctor<!rec_VirtualDelegatingCtor, custom>>
+// CIR: %[[THIS_ALLOCA:.*]] = cir.alloca !cir.ptr<!rec_VirtualDelegatingCtor>, !cir.ptr<!cir.ptr<!rec_VirtualDelegatingCtor>>, ["this", init] {alignment = 8 : i64}
+// CIR: %[[X_ALLOCA:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
+// CIR: %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOCA]] : !cir.ptr<!cir.ptr<!rec_VirtualDelegatingCtor>>, !cir.ptr<!rec_VirtualDelegatingCtor>
+// CIR: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS_LOAD]] : !cir.ptr<!rec_VirtualDelegatingCtor> nonnull [0] -> !cir.ptr<!rec_Base>
+// CIR: %[[X_LOAD:.*]] = cir.load align(4) %[[X_ALLOCA]] : !cir.ptr<!s32i>, !s32i
+// CIR: cir.call @_ZN4BaseC2Ei(%[[BASE_ADDR]], %[[X_LOAD]]) : (!cir.ptr<!rec_Base> {{.*}}, !s32i {{{.*}}) -> ()
+//
+// LLVM-LABEL: define linkonce_odr void @_ZN21VirtualDelegatingCtorC1Ei(ptr {{.*}}, i32 {{.*}})
+// LLVM: %[[THIS_ALLOCA:.*]] = alloca ptr
+// LLVM: %[[X_ALLOCA:.*]] = alloca i32
+// LLVM: %[[THIS_LOAD:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// LLVM: %[[X_LOAD:.*]] = load i32, ptr %[[X_ALLOCA]]
+// LLVM: call void @_ZN4BaseC2Ei(ptr {{.*}}%[[THIS_LOAD]], i32 {{.*}}%[[X_LOAD]])
+
+// Note: Due to an innocuous bug in LLVM-IR codegen, this line is different.
+// LLVM-IR codegen emits this with 3 arguments, despite the 3rd not being used
+// in the body, and not being included in the declaration/definition of this
+// function. CIR cannot reproduce this, as we have a verifier that checks that
+// the arg counts match.
+// CIR: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS_LOAD]] : !cir.ptr<!rec_VirtualDelegatingCtor> nonnull [0] -> !cir.ptr<!rec_VirtDerived>
+// CIR: %[[ADDR_PT:.*]] = cir.vtt.address_point @_ZTT21VirtualDelegatingCtor, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+// CIR: cir.call @_ZN11VirtDerivedCI24BaseEi(%[[BASE_ADDR]], %[[ADDR_PT]]) : (!cir.ptr<!rec_VirtDerived>{{.*}}, !cir.ptr<!cir.ptr<!void>>{{.*}}) -> ()
+// CIR: %[[ADDR_PT:.*]] = cir.vtable.address_point(@_ZTV21VirtualDelegatingCtor, address_point = <index = 0, offset = 3>) : !cir.vptr
+// CIR: %[[VPTR:.*]] = cir.vtable.get_vptr %[[THIS_LOAD]] : !cir.ptr<!rec_VirtualDelegatingCtor> -> !cir.ptr<!cir.vptr>
+// CIR: cir.store align(8) %[[ADDR_PT]], %[[VPTR]] : !cir.vptr, !cir.ptr<!cir.vptr>
+
+// LLVM: call void @_ZN11VirtDerivedCI24BaseEi(ptr {{.*}}%[[THIS_LOAD]], ptr {{.*}}(i8, ptr @_ZTT21VirtualDelegatingCtor, i64 8))
+// LLVM: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV21VirtualDelegatingCtor, i64 24), ptr %[[THIS_LOAD]]
+//
+
+// CIR-LABEL: cir.func no_inline dso_local @_Z12fallsthroughv()
+// CIR: cir.call @_ZN21VirtualDelegatingCtorC1Ei(%{{.*}}, %{{.*}}) : (!cir.ptr<!rec_VirtualDelegatingCtor> {{.*}}, !s32i {{.*}}) -> ()
+// LLVM-LABEL: define dso_local void @_Z12fallsthroughv()
+// LLVM: call void @_ZN21VirtualDelegatingCtorC1Ei(ptr {{.*}}, i32 {{.*}}1)
+
+
+// OGCG-LABEL: define dso_local void @_Z20emitDelegateCallArgsv()
+// OGCG: call void @_ZN7DerivedCI14BaseEi(ptr {{.*}}, i32 {{.*}}1)
+//
+// OGCG-LABEL: define linkonce_odr void @_ZN7DerivedCI14BaseEi(ptr {{.*}}, i32 {{.*}})
+// OGCG: call void @_ZN7DerivedCI24BaseEi(ptr {{.*}}, i32 {{.*}})
+//
+// OGCG-LABEL: define dso_local void @_Z26cannotEmitDelegateCallArgsv()
+// OGCG: %[[TMP_ALLOCA:.*]] = alloca ptr
+// OGCG: %[[TMP_LOAD:.*]] = load ptr, ptr %[[TMP_ALLOCA]]
+// OGCG: call void (ptr, float, ...) @_ZN4BaseC2Efz(ptr {{.*}}%[[TMP_LOAD]], float {{.*}}0x3FF19999A{{.*}}, i32 {{.*}}2, double {{.*}}3.000000e+00)
+//
+// OGCG-LABEL: declare void @_ZN4BaseC2Efz(ptr {{.*}}, float {{.*}}, ...)
+//
+// OGCG-LABEL: define dso_local void @_Z12fallsthroughv()
+// OGCG: call void @_ZN21VirtualDelegatingCtorC1Ei(ptr {{.*}}, i32 {{.*}}1)
+//
+// OGCG-LABEL: define linkonce_odr void @_ZN21VirtualDelegatingCtorC1Ei(ptr {{.*}}, i32 {{.*}})
+// OGCG: %[[THIS_ALLOCA:.*]] = alloca ptr
+// OGCG: %[[X_ALLOCA:.*]] = alloca i32
+// OGCG: %[[THIS_LOAD:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// OGCG: %[[X_LOAD:.*]] = load i32, ptr %[[X_ALLOCA]]
+// OGCG: call void @_ZN4BaseC2Ei(ptr {{.*}}%[[THIS_LOAD]], i32 {{.*}}%[[X_LOAD]])
+// Note: see the note above for the CIR/LLVM-IR difference here.
+// OGCG: %[[X_LOAD:.*]] = load i32, ptr %[[X_ALLOCA]]
+// OGCG: call void @_ZN11VirtDerivedCI24BaseEi(ptr {{.*}}%[[THIS_LOAD]], ptr {{.*}}(i8, ptr @_ZTT21VirtualDelegatingCtor, i64 8), i32{{.*}}%[[X_LOAD]])
+// OGCG: store ptr getelementptr inbounds inrange(-24, 0) ({ [3 x ptr] }, ptr @_ZTV21VirtualDelegatingCtor, i32 0, i32 0, i32 3), ptr %[[THIS_LOAD]]
+//
+// OGCG-LABEL: define linkonce_odr void @_ZN7DerivedCI24BaseEi(ptr {{.*}}, i32 {{.*}})
+// OGCG: %[[THIS_ALLOCA:.*]] = alloca ptr
+// OGCG: %[[INT_ALLOCA:.*]] = alloca i32
+// OGCG: %[[BASE_ADDR:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// OGCG: %[[INT:.*]] = load i32, ptr %[[INT_ALLOCA]]
+// OGCG: call void @_ZN4BaseC2Ei(ptr {{.*}}%[[BASE_ADDR]], i32 {{.*}}[[INT]])
+//
+// OGCG-LABEL: declare void @_ZN4BaseC2Ei(ptr {{.*}}, i32 {{.*}})
+
+// OGCG-LABEL: define linkonce_odr void @_ZN11VirtDerivedCI24BaseEi(ptr {{.*}}, ptr {{.*}})
+// OGCG: %[[THIS_ALLOCA:.*]] = alloca ptr
+// OGCG: %[[VTT_ALLOCA:.*]] = alloca ptr
+// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ALLOCA]]
+// OGCG: %[[VTT:.*]] = load ptr, ptr %[[VTT_ALLOCA]]
+// OGCG: %[[VTT_ADDR_LOAD:.*]] = load ptr, ptr %[[VTT]]
+// OGCG: store ptr %[[VTT_ADDR_LOAD]], ptr %[[THIS]]
>From a9881193a1aacd7a776223d4c7a746ff96fd9d3f Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Fri, 10 Apr 2026 10:12:35 -0700
Subject: [PATCH 2/3] Clang-format fix
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 4dc24113f71b9..9341cd7d60323 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1427,8 +1427,8 @@ void CIRGenFunction::emitCallArgs(
}
}
-bool CIRGenTypes::inheritingCtorHasParams(
- const InheritedConstructor &inherited, CXXCtorType type) {
+bool CIRGenTypes::inheritingCtorHasParams(const InheritedConstructor &inherited,
+ CXXCtorType type) {
// Parameters are unnecessary if we're constructing a base class subobject
// and the inherited constructor lives in a virtual base.
return type == Ctor_Complete ||
>From 9e06bcc8a276d2f7ce3fccca50ee818c5b84dcfd Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Mon, 13 Apr 2026 06:12:45 -0700
Subject: [PATCH 3/3] fixups based on Andy's feedback
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 2 ++
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 11 +++--------
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 9341cd7d60323..39393d52172a7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1427,6 +1427,8 @@ void CIRGenFunction::emitCallArgs(
}
}
+// FIXME(cir): This is identical to the version from classic-codegen, we should
+// figure out how to move this to a common location.
bool CIRGenTypes::inheritingCtorHasParams(const InheritedConstructor &inherited,
CXXCtorType type) {
// Parameters are unnecessary if we're constructing a base class subobject
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index bf0e28ae30ba5..54935cd09819f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -1362,18 +1362,13 @@ static bool canEmitDelegateCallArgs(CIRGenModule &cgm, ASTContext &ctx,
return false;
if (ctx.getTargetInfo().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
- // If the parameters are callee-cleanup, it's not safe to forward.
- if (llvm::any_of(d->parameters(), [&ctx](const ParmVarDecl *param) {
- return param->needsDestruction(ctx);
- }))
- return false;
-
// FIXME(CIR): It isn't clear to me that this is the right answer here,
// classic-codegen decides the answer is 'false' if there is an inalloca
- // argument. What our default should be in this case, or what we want to do.
+ // argument or if there is a param that needs destruction.
// When we get an understanding of what the the calling-convention code
// needs here, we should be able to replace this with either a 'return
// false' or 'return true'.
+ // Perhaps we should be checking isParamDestroyedInCallee?
cgm.errorNYI(d->getSourceRange(),
"canEmitDelegateCallArgs: args-destroyed-L-to-R in callee");
}
@@ -1430,8 +1425,8 @@ void CIRGenFunction::emitInlinedInheritingCXXConstructorCall(
bool forVirtualBase, bool delegating, CallArgList &args) {
GlobalDecl gd(d, ctorType);
assert(!cir::MissingFeatures::generateDebugInfo());
- assert(!cir::MissingFeatures::runCleanupsScope());
InlinedInheritingConstructorScope scope(*this, gd);
+ RunCleanupsScope RunCleanups(*this);
// Save the arguments to be passed to the inherited constructor.
cxxInheritedCtorInitExprArgs = args;
More information about the cfe-commits
mailing list