[clang] [CIR] Upstream support for emitting constructors (PR #143639)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 11 14:55:57 PDT 2025
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/143639
>From 7e08ebf8887cb4ef90a06b853ebacfff1e57f510 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 9 Jun 2025 09:38:55 -0700
Subject: [PATCH 1/2] [CIR] Upstream support for emitting constructors
This change upstreams the code to emit simple constructor defintions.
---
clang/include/clang/CIR/MissingFeatures.h | 4 +
clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 40 +++++++++
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 21 +++++
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 41 ++++++++++
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 82 +++++++++++++++++++
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 53 ++++++++++--
clang/lib/CIR/CodeGen/CIRGenFunction.h | 18 ++++
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 80 +++++++++++++++++-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 15 ++--
clang/lib/CIR/CodeGen/CIRGenModule.h | 5 ++
clang/lib/CIR/CodeGen/CMakeLists.txt | 1 +
clang/test/CIR/CodeGen/ctor.cpp | 20 ++++-
12 files changed, 364 insertions(+), 16 deletions(-)
create mode 100644 clang/lib/CIR/CodeGen/CIRGenCXX.cpp
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 87908e2ec08ac..fbd15d5c886d2 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -81,6 +81,7 @@ struct MissingFeatures {
static bool opFuncCPUAndFeaturesAttributes() { return false; }
static bool opFuncSection() { return false; }
static bool opFuncSetComdat() { return false; }
+ static bool opFuncAttributesForDefinition() { return false; }
// CallOp handling
static bool opCallPseudoDtor() { return false; }
@@ -226,6 +227,9 @@ 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; }
// Missing types
static bool dataMemberType() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
new file mode 100644
index 0000000000000..51751483d34e9
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This contains code dealing with C++ code generation.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenFunction.h"
+#include "CIRGenModule.h"
+
+#include "clang/AST/GlobalDecl.h"
+#include "clang/CIR/MissingFeatures.h"
+
+using namespace clang;
+using namespace clang::CIRGen;
+
+cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) {
+ const CIRGenFunctionInfo &fnInfo =
+ getTypes().arrangeCXXStructorDeclaration(gd);
+ cir::FuncType funcType = getTypes().getFunctionType(fnInfo);
+ cir::FuncOp fn = getAddrOfCXXStructor(gd, &fnInfo, /*FnType=*/nullptr,
+ /*DontDefer=*/true, ForDefinition);
+ assert(!cir::MissingFeatures::opFuncLinkage());
+ CIRGenFunction cgf{*this, builder};
+ curCGF = &cgf;
+ {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ cgf.generateCode(gd, fn, funcType);
+ }
+ curCGF = nullptr;
+
+ setNonAliasAttributes(gd, fn);
+ assert(!cir::MissingFeatures::opFuncAttributesForDefinition());
+ return fn;
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 107535ebc7275..7d2b46d50d82e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -37,6 +37,10 @@ class CIRGenCXXABI {
void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
+ /// Emit a single constructor/destructor with the gien type from a C++
+ /// constructor Decl.
+ virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;
+
public:
clang::ImplicitParamDecl *getThisDecl(CIRGenFunction &cgf) {
return cgf.cxxabiThisDecl;
@@ -55,12 +59,29 @@ class CIRGenCXXABI {
return md->getParent();
}
+ /// Return whether the given global decl needs a VTT parameter.
+ virtual bool needsVTTParameter(clang::GlobalDecl gd) { return false; }
+
/// Build a parameter variable suitable for 'this'.
void buildThisParam(CIRGenFunction &cgf, FunctionArgList ¶ms);
/// Loads the incoming C++ this pointer as it was passed by the caller.
mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
+ /// Emit constructor variants required by this ABI.
+ virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
+
+ /// Insert any ABI-specific implicit parameters into the parameter list for a
+ /// function. This generally involves extra data for constructors and
+ /// destructors.
+ ///
+ /// ABIs may also choose to override the return type, which has been
+ /// initialized with the type of 'this' if HasThisReturn(CGF.CurGD) is true or
+ /// the formal return type of the function otherwise.
+ virtual void addImplicitStructorParams(CIRGenFunction &cgf,
+ clang::QualType &resTy,
+ FunctionArgList ¶ms) = 0;
+
/// Returns true if the given constructor or destructor is one of the kinds
/// that the ABI says returns 'this' (only applies when called non-virtually
/// for destructors).
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 9d25eea9e413d..da754e0806b2d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -162,6 +162,47 @@ arrangeCIRFunctionInfo(CIRGenTypes &cgt, SmallVectorImpl<CanQualType> &prefix,
return cgt.arrangeCIRFunctionInfo(resultType, prefix, required);
}
+void CIRGenFunction::emitDelegateCallArg(CallArgList &args,
+ const VarDecl *param,
+ SourceLocation loc) {
+ // StartFunction converted the ABI-lowered parameter(s) into a local alloca.
+ // We need to turn that into an r-value suitable for emitCall
+ Address local = getAddrOfLocalVar(param);
+
+ QualType type = param->getType();
+
+ if (const auto *rd = type->getAsCXXRecordDecl()) {
+ cgm.errorNYI(param->getSourceRange(),
+ "emitDelegateCallArg: record argument");
+ return;
+ }
+
+ // GetAddrOfLocalVar returns a pointer-to-pointer for references, but the
+ // argument needs to be the original pointer.
+ if (type->isReferenceType()) {
+ args.add(
+ RValue::get(builder.createLoad(getLoc(param->getSourceRange()), local)),
+ type);
+ } else if (getLangOpts().ObjCAutoRefCount) {
+ cgm.errorNYI(param->getSourceRange(),
+ "emitDelegateCallArg: ObjCAutoRefCount");
+ // 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");
+ }
+
+ // Deactivate the cleanup for the callee-destructed param that was pushed.
+ assert(!cir::MissingFeatures::thunks());
+ if (type->isRecordType() &&
+ type->castAs<RecordType>()->getDecl()->isParamDestroyedInCallee() &&
+ param->needsDestruction(getContext())) {
+ cgm.errorNYI(param->getSourceRange(),
+ "emitDelegateCallArg: callee-destructed param");
+ }
+}
+
static const CIRGenFunctionInfo &
arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
const CallArgList &args,
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 8491a66ea6cb4..4590775462009 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -21,6 +21,88 @@
using namespace clang;
using namespace clang::CIRGen;
+/// Checks whether the given constructor is a valid subject for the
+/// complete-to-base constructor delgation optimization, i.e. emitting the
+/// complete constructor as a simple call to the base constructor.
+bool CIRGenFunction::isConstructorDelegationValid(
+ const CXXConstructorDecl *ctor) {
+
+ // Currently we disable the optimization for classes with virtual bases
+ // because (1) the address of parameter variables need to be consistent across
+ // all initializers but (2) the delegate function call necessarily creates a
+ // second copy of the parameter variable.
+ //
+ // The limiting example (purely theoretical AFAIK):
+ // struct A { A(int &c) { c++; } };
+ // struct A : virtual A {
+ // B(int count) : A(count) { printf("%d\n", count); }
+ // };
+ // ...although even this example could in principle be emitted as a delegation
+ // since the address of the parameter doesn't escape.
+ if (ctor->getParent()->getNumVBases())
+ return false;
+
+ // We also disable the optimization for variadic functions because it's
+ // impossible to "re-pass" varargs.
+ if (ctor->getType()->castAs<FunctionProtoType>()->isVariadic())
+ return false;
+
+ // FIXME: Decide if we can do a delegation of a delegating constructor.
+ if (ctor->isDelegatingConstructor())
+ return false;
+
+ return true;
+}
+
+Address CIRGenFunction::loadCXXThisAddress() {
+ assert(curFuncDecl && "loading 'this' without a func declaration?");
+ assert(isa<CXXMethodDecl>(curFuncDecl));
+
+ // Lazily compute CXXThisAlignment.
+ if (cxxThisAlignment.isZero()) {
+ // Just use the best known alignment for the parent.
+ // TODO: if we're currently emitting a complete-object ctor/dtor, we can
+ // always use the complete-object alignment.
+ auto rd = cast<CXXMethodDecl>(curFuncDecl)->getParent();
+ cxxThisAlignment = cgm.getClassPointerAlignment(rd);
+ }
+
+ return Address(loadCXXThis(), cxxThisAlignment);
+}
+
+void CIRGenFunction::emitDelegateCXXConstructorCall(
+ const CXXConstructorDecl *ctor, CXXCtorType ctorType,
+ const FunctionArgList &args, SourceLocation loc) {
+ CallArgList delegateArgs;
+
+ FunctionArgList::const_iterator i = args.begin(), e = args.end();
+ assert(i != e && "no parameters to constructor");
+
+ // this
+ Address thisAddr = loadCXXThisAddress();
+ delegateArgs.add(RValue::get(thisAddr.getPointer()), (*i)->getType());
+ ++i;
+
+ // FIXME: The location of the VTT parameter in the parameter list is specific
+ // to the Itanium ABI and shouldn't be hardcoded here.
+ if (cgm.getCXXABI().needsVTTParameter(curGD)) {
+ cgm.errorNYI(loc, "emitDelegateCXXConstructorCall: VTT parameter");
+ return;
+ }
+
+ // Explicit arguments.
+ for (; i != e; ++i) {
+ const VarDecl *param = *i;
+ // FIXME: per-argument source location
+ emitDelegateCallArg(delegateArgs, param, loc);
+ }
+
+ assert(!cir::MissingFeatures::sanitizers());
+
+ emitCXXConstructorCall(ctor, ctorType, /*ForVirtualBase=*/false,
+ /*Delegating=*/true, thisAddr, delegateArgs, loc);
+}
+
Address CIRGenFunction::getAddressOfBaseClass(
Address value, const CXXRecordDecl *derived,
llvm::iterator_range<CastExpr::path_const_iterator> path,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index e32a5c836be02..5b2094ff7ceb3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -465,7 +465,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
if (isa<CXXDestructorDecl>(funcDecl))
getCIRGenModule().errorNYI(bodyRange, "C++ destructor definition");
else if (isa<CXXConstructorDecl>(funcDecl))
- getCIRGenModule().errorNYI(bodyRange, "C++ constructor definition");
+ emitConstructorBody(args);
else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
funcDecl->hasAttr<CUDAGlobalAttr>())
getCIRGenModule().errorNYI(bodyRange, "CUDA kernel");
@@ -496,6 +496,47 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
return fn;
}
+void CIRGenFunction::emitConstructorBody(FunctionArgList &args) {
+ assert(!cir::MissingFeatures::sanitizers());
+ const auto *ctor = cast<CXXConstructorDecl>(curGD.getDecl());
+ CXXCtorType ctorType = curGD.getCtorType();
+
+ assert((cgm.getTarget().getCXXABI().hasConstructorVariants() ||
+ ctorType == Ctor_Complete) &&
+ "can only generate complete ctor for this ABI");
+
+ if (ctorType == Ctor_Complete && isConstructorDelegationValid(ctor) &&
+ cgm.getTarget().getCXXABI().hasConstructorVariants()) {
+ emitDelegateCXXConstructorCall(ctor, Ctor_Base, args, ctor->getEndLoc());
+ return;
+ }
+
+ const FunctionDecl *definition = nullptr;
+ Stmt *body = ctor->getBody(definition);
+ assert(definition == ctor && "emitting wrong constructor body");
+
+ if (isa_and_nonnull<CXXTryStmt>(body)) {
+ cgm.errorNYI(ctor->getSourceRange(), "emitConstructorBody: try body");
+ return;
+ }
+
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+ assert(!cir::MissingFeatures::runCleanupsScope());
+
+ // 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());
+
+ // TODO(cir): propagate this result via mlir::logical result. Just unreachable
+ // now just to have it handled.
+ if (mlir::failed(emitStmt(body, true))) {
+ cgm.errorNYI(ctor->getSourceRange(),
+ "emitConstructorBody: emit body statement failed.");
+ return;
+ }
+}
+
/// Given a value of type T* that may not be to a complete object, construct
/// an l-vlaue withi the natural pointee alignment of T.
LValue CIRGenFunction::makeNaturalAlignPointeeAddrLValue(mlir::Value val,
@@ -522,16 +563,16 @@ clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
cgm.getCXXABI().buildThisParam(*this, args);
}
- if (isa<CXXConstructorDecl>(fd))
- cgm.errorNYI(fd->getSourceRange(),
- "buildFunctionArgList: CXXConstructorDecl");
+ 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 (md && (isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md)))
- cgm.errorNYI(fd->getSourceRange(),
- "buildFunctionArgList: implicit structor params");
+ cgm.getCXXABI().addImplicitStructorParams(*this, retTy, args);
return retTy;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 682d59d63faa8..361dcd5ef1c31 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -66,6 +66,7 @@ class CIRGenFunction : public CIRGenTypeCache {
ImplicitParamDecl *cxxabiThisDecl = nullptr;
mlir::Value cxxabiThisValue = nullptr;
mlir::Value cxxThisValue = nullptr;
+ clang::CharUnits cxxThisAlignment;
// Holds the Decl for the current outermost non-closure context
const clang::Decl *curFuncDecl = nullptr;
@@ -473,6 +474,9 @@ class CIRGenFunction : public CIRGenTypeCache {
bool shouldNullCheckClassCastValue(const CastExpr *ce);
+ static bool
+ isConstructorDelegationValid(const clang::CXXConstructorDecl *ctor);
+
LValue makeNaturalAlignPointeeAddrLValue(mlir::Value v, clang::QualType t);
/// Construct an address with the natural alignment of T. If a pointer to T
@@ -517,6 +521,7 @@ class CIRGenFunction : public CIRGenTypeCache {
assert(cxxThisValue && "no 'this' value for this function");
return cxxThisValue;
}
+ Address loadCXXThisAddress();
/// Get an appropriate 'undef' rvalue for the given type.
/// TODO: What's the equivalent for MLIR? Currently we're only using this for
@@ -753,6 +758,8 @@ class CIRGenFunction : public CIRGenTypeCache {
LValue emitCompoundAssignmentLValue(const clang::CompoundAssignOperator *e);
+ void emitConstructorBody(FunctionArgList &args);
+
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
void emitCXXConstructExpr(const clang::CXXConstructExpr *e,
@@ -841,6 +848,17 @@ class CIRGenFunction : public CIRGenTypeCache {
mlir::Type condType,
bool buildingTopLevelCase);
+ void emitDelegateCXXConstructorCall(const clang::CXXConstructorDecl *ctor,
+ clang::CXXCtorType ctorType,
+ const FunctionArgList &args,
+ clang::SourceLocation loc);
+
+ /// We are performing a delegate call; that is, the current function is
+ /// delegating to another one. Produce a r-value suitable for passing the
+ /// given parameter.
+ void emitDelegateCallArg(CallArgList &args, const clang::VarDecl *param,
+ clang::SourceLocation loc);
+
/// Emit an `if` on a boolean condition to the specified blocks.
/// FIXME: Based on the condition, this might try to simplify the codegen of
/// the conditional based on the branch.
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index fdd8b63fb6da0..39b03a12ede9a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -20,7 +20,9 @@
#include "CIRGenCXXABI.h"
#include "CIRGenFunction.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/GlobalDecl.h"
+#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/ErrorHandling.h"
using namespace clang;
@@ -35,8 +37,16 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
assert(!cir::MissingFeatures::cxxabiUseARMGuardVarABI());
}
- void emitInstanceFunctionProlog(SourceLocation Loc,
- CIRGenFunction &CGF) override;
+ bool needsVTTParameter(clang::GlobalDecl gd) override;
+
+ void emitInstanceFunctionProlog(SourceLocation loc,
+ CIRGenFunction &cgf) override;
+
+ void addImplicitStructorParams(CIRGenFunction &cgf, QualType &retTy,
+ FunctionArgList ¶ms) override;
+
+ void emitCXXConstructors(const clang::CXXConstructorDecl *d) override;
+ void emitCXXStructor(clang::GlobalDecl gd) override;
};
} // namespace
@@ -72,6 +82,72 @@ void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc,
}
}
+void CIRGenItaniumCXXABI::addImplicitStructorParams(CIRGenFunction &cgf,
+ QualType &retTY,
+ FunctionArgList ¶ms) {
+ const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());
+ assert(isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md));
+
+ // Check if we need a VTT parameter as well.
+ if (needsVTTParameter(cgf.curGD)) {
+ cgf.cgm.errorNYI(cgf.curFuncDecl->getLocation(),
+ "addImplicitStructorParams: VTT parameter");
+ }
+}
+
+void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) {
+ auto *md = cast<CXXMethodDecl>(gd.getDecl());
+ auto *cd = dyn_cast<CXXConstructorDecl>(md);
+
+ if (!cd) {
+ cgm.errorNYI(md->getSourceRange(), "CXCABI emit destructor");
+ return;
+ }
+
+ if (cgm.getCodeGenOpts().CXXCtorDtorAliases)
+ cgm.errorNYI(md->getSourceRange(), "Ctor/Dtor aliases");
+
+ auto fn = cgm.codegenCXXStructor(gd);
+
+ cgm.maybeSetTrivialComdat(*md, fn);
+}
+
+void CIRGenItaniumCXXABI::emitCXXConstructors(const CXXConstructorDecl *d) {
+ // Just make sure we're in sync with TargetCXXABI.
+ assert(cgm.getTarget().getCXXABI().hasConstructorVariants());
+
+ // The constructor used for constructing this as a base class;
+ // ignores virtual bases.
+ cgm.emitGlobal(GlobalDecl(d, Ctor_Base));
+
+ // The constructor used for constructing this as a complete class;
+ // constructs the virtual bases, then calls the base constructor.
+ if (!d->getParent()->isAbstract()) {
+ // We don't need to emit the complete ctro if the class is abstract.
+ cgm.emitGlobal(GlobalDecl(d, Ctor_Complete));
+ }
+}
+
+/// Return whether the given global decl needs a VTT parameter, which it does if
+/// it's a base constructor or destructor with virtual bases.
+bool CIRGenItaniumCXXABI::needsVTTParameter(GlobalDecl gd) {
+ auto *md = cast<CXXMethodDecl>(gd.getDecl());
+
+ // We don't have any virtual bases, just return early.
+ if (!md->getParent()->getNumVBases())
+ return false;
+
+ // Check if we have a base constructor.
+ if (isa<CXXConstructorDecl>(md) && gd.getCtorType() == Ctor_Base)
+ return true;
+
+ // Check if we have a base destructor.
+ if (isa<CXXDestructorDecl>(md) && gd.getDtorType() == Dtor_Base)
+ return true;
+
+ return false;
+}
+
CIRGenCXXABI *clang::CIRGen::CreateCIRGenItaniumCXXABI(CIRGenModule &cgm) {
switch (cgm.getASTContext().getCXXABIKind()) {
case TargetCXXABI::GenericItanium:
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 8407f8fad06ba..434dd376208e1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -226,11 +226,9 @@ mlir::Operation *
CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
const Decl *d = gd.getDecl();
- if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d)) {
- errorNYI(d->getSourceRange(),
- "getAddrOfGlobal: C++ constructor/destructor");
- return nullptr;
- }
+ if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d))
+ return getAddrOfCXXStructor(gd, /*FnInfo=*/nullptr, /*FnType=*/nullptr,
+ /*DontDefer=*/false, isForDefinition);
if (isa<CXXMethodDecl>(d)) {
const CIRGenFunctionInfo &fi =
@@ -411,6 +409,7 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
cgf.generateCode(gd, funcOp, funcType);
}
curCGF = nullptr;
+ assert(!cir::MissingFeatures::opFuncAttributesForDefinition());
}
mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) {
@@ -771,7 +770,7 @@ void CIRGenModule::emitGlobalDefinition(clang::GlobalDecl gd,
// Make sure to emit the definition(s) before we emit the thunks. This is
// necessary for the generation of certain thunks.
if (isa<CXXConstructorDecl>(method) || isa<CXXDestructorDecl>(method))
- errorNYI(method->getSourceRange(), "C++ ctor/dtor");
+ abi->emitCXXStructor(gd);
else if (fd->isMultiVersion())
errorNYI(method->getSourceRange(), "multiversion functions");
else
@@ -1173,6 +1172,10 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
case Decl::Empty:
break;
+ case Decl::CXXConstructor:
+ getCXXABI().emitCXXConstructors(cast<CXXConstructorDecl>(decl));
+ break;
+
// C++ Decls
case Decl::LinkageSpec:
case Decl::Namespace:
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 9748c0b3ed43a..f76fd8e733642 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -267,6 +267,11 @@ class CIRGenModule : public CIRGenTypeCache {
// Make sure that this type is translated.
void updateCompletedType(const clang::TagDecl *td);
+ // Produce code for this constructor/destructor. This method doesn't try to
+ // apply any ABI rules about which other constructors/destructors are needed
+ // or if they are alias to each other.
+ cir::FuncOp codegenCXXStructor(clang::GlobalDecl gd);
+
bool supportsCOMDAT() const;
void maybeSetTrivialComdat(const clang::Decl &d, mlir::Operation *op);
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index beaa9afb31f93..217609687eabc 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -11,6 +11,7 @@ add_clang_library(clangCIR
CIRGenBuilder.cpp
CIRGenCall.cpp
CIRGenClass.cpp
+ CIRGenCXX.cpp
CIRGenCXXABI.cpp
CIRGenCXXExpr.cpp
CIRGenBuiltin.cpp
diff --git a/clang/test/CIR/CodeGen/ctor.cpp b/clang/test/CIR/CodeGen/ctor.cpp
index 3a1e82e338c1c..1248b2d104161 100644
--- a/clang/test/CIR/CodeGen/ctor.cpp
+++ b/clang/test/CIR/CodeGen/ctor.cpp
@@ -3,7 +3,7 @@
struct Struk {
int a;
- Struk();
+ Struk() {}
};
void baz() {
@@ -12,7 +12,23 @@ void baz() {
// CHECK: !rec_Struk = !cir.record<struct "Struk" {!s32i}>
-// CHECK: cir.func @_ZN5StrukC1Ev(!cir.ptr<!rec_Struk>)
+// Note: In the absence of the '-mconstructor-aliases' option, we emit two
+// constructors here. The handling of constructor aliases is currently
+// NYI, but when it is added this test should be updated to add a RUN
+// line that passes '-mconstructor-aliases' to clang_cc1.
+// CHECK: cir.func @_ZN5StrukC2Ev(%arg0: !cir.ptr<!rec_Struk>
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["this", init] {alignment = 8 : i64}
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>
+// CHECK-NEXT: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_Struk>>, !cir.ptr<!rec_Struk>
+// CHECK-NEXT: cir.return
+
+// CHECK: cir.func @_ZN5StrukC1Ev(%arg0: !cir.ptr<!rec_Struk>
+// CHECK-NEXT: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["this", init] {alignment = 8 : i64}
+// CHECK-NEXT: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>
+// CHECK-NEXT: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_Struk>>, !cir.ptr<!rec_Struk>
+// CHECK-NEXT: cir.call @_ZN5StrukC2Ev(%[[THIS]]) : (!cir.ptr<!rec_Struk>) -> ()
+// CHECK-NEXT: cir.return
+
// CHECK: cir.func @_Z3bazv()
// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["s", init] {alignment = 4 : i64}
// CHECK-NEXT: cir.call @_ZN5StrukC1Ev(%[[S_ADDR]]) : (!cir.ptr<!rec_Struk>) -> ()
>From 55e109a5b68afc5f788a9a33754745fed11b2dd8 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 11 Jun 2025 13:20:25 -0700
Subject: [PATCH 2/2] Address review feedback
---
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 18 +++-------
clang/lib/CIR/CodeGen/CIRGenClass.cpp | 3 +-
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 9 ++++-
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 21 ++----------
clang/test/CIR/CodeGen/ctor.cpp | 34 +++++++++++++++++++
5 files changed, 50 insertions(+), 35 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 7d2b46d50d82e..2d967fd307e01 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -37,8 +37,8 @@ class CIRGenCXXABI {
void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
- /// Emit a single constructor/destructor with the gien type from a C++
- /// constructor Decl.
+ /// Emit a single constructor/destructor with the gen type from a C++
+ /// constructor/destructor Decl.
virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;
public:
@@ -59,7 +59,8 @@ class CIRGenCXXABI {
return md->getParent();
}
- /// Return whether the given global decl needs a VTT parameter.
+ /// Return whether the given global decl needs a VTT (virtual table table)
+ /// parameter.
virtual bool needsVTTParameter(clang::GlobalDecl gd) { return false; }
/// Build a parameter variable suitable for 'this'.
@@ -71,17 +72,6 @@ class CIRGenCXXABI {
/// Emit constructor variants required by this ABI.
virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
- /// Insert any ABI-specific implicit parameters into the parameter list for a
- /// function. This generally involves extra data for constructors and
- /// destructors.
- ///
- /// ABIs may also choose to override the return type, which has been
- /// initialized with the type of 'this' if HasThisReturn(CGF.CurGD) is true or
- /// the formal return type of the function otherwise.
- virtual void addImplicitStructorParams(CIRGenFunction &cgf,
- clang::QualType &resTy,
- FunctionArgList ¶ms) = 0;
-
/// Returns true if the given constructor or destructor is one of the kinds
/// that the ABI says returns 'this' (only applies when called non-virtually
/// for destructors).
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 4590775462009..bb4b451c99247 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -22,11 +22,10 @@ using namespace clang;
using namespace clang::CIRGen;
/// Checks whether the given constructor is a valid subject for the
-/// complete-to-base constructor delgation optimization, i.e. emitting the
+/// complete-to-base constructor delegation optimization, i.e. emitting the
/// complete constructor as a simple call to the base constructor.
bool CIRGenFunction::isConstructorDelegationValid(
const CXXConstructorDecl *ctor) {
-
// Currently we disable the optimization for classes with virtual bases
// because (1) the address of parameter variables need to be consistent across
// all initializers but (2) the delegate function call necessarily creates a
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 5b2094ff7ceb3..53c44c6cc7680 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -527,6 +527,13 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList &args) {
// 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;
+ }
// TODO(cir): propagate this result via mlir::logical result. Just unreachable
// now just to have it handled.
@@ -572,7 +579,7 @@ clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
args.push_back(param);
if (md && (isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md)))
- cgm.getCXXABI().addImplicitStructorParams(*this, retTy, args);
+ assert(!cir::MissingFeatures::cxxabiStructorImplicitParam());
return retTy;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 39b03a12ede9a..cd9096a0188a7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -42,9 +42,6 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
void emitInstanceFunctionProlog(SourceLocation loc,
CIRGenFunction &cgf) override;
- void addImplicitStructorParams(CIRGenFunction &cgf, QualType &retTy,
- FunctionArgList ¶ms) override;
-
void emitCXXConstructors(const clang::CXXConstructorDecl *d) override;
void emitCXXStructor(clang::GlobalDecl gd) override;
};
@@ -82,19 +79,6 @@ void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc,
}
}
-void CIRGenItaniumCXXABI::addImplicitStructorParams(CIRGenFunction &cgf,
- QualType &retTY,
- FunctionArgList ¶ms) {
- const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());
- assert(isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md));
-
- // Check if we need a VTT parameter as well.
- if (needsVTTParameter(cgf.curGD)) {
- cgf.cgm.errorNYI(cgf.curFuncDecl->getLocation(),
- "addImplicitStructorParams: VTT parameter");
- }
-}
-
void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) {
auto *md = cast<CXXMethodDecl>(gd.getDecl());
auto *cd = dyn_cast<CXXConstructorDecl>(md);
@@ -128,8 +112,9 @@ void CIRGenItaniumCXXABI::emitCXXConstructors(const CXXConstructorDecl *d) {
}
}
-/// Return whether the given global decl needs a VTT parameter, which it does if
-/// it's a base constructor or destructor with virtual bases.
+/// Return whether the given global decl needs a VTT (virtual table table)
+/// parameter, which it does if it's a base constructor or destructor with
+/// virtual bases.
bool CIRGenItaniumCXXABI::needsVTTParameter(GlobalDecl gd) {
auto *md = cast<CXXMethodDecl>(gd.getDecl());
diff --git a/clang/test/CIR/CodeGen/ctor.cpp b/clang/test/CIR/CodeGen/ctor.cpp
index 1248b2d104161..3b4191fd74c97 100644
--- a/clang/test/CIR/CodeGen/ctor.cpp
+++ b/clang/test/CIR/CodeGen/ctor.cpp
@@ -33,3 +33,37 @@ void baz() {
// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["s", init] {alignment = 4 : i64}
// CHECK-NEXT: cir.call @_ZN5StrukC1Ev(%[[S_ADDR]]) : (!cir.ptr<!rec_Struk>) -> ()
// CHECK-NEXT: cir.return
+
+struct VariadicStruk {
+ int a;
+ VariadicStruk(int n, ...) { a = n;}
+};
+
+void bar() {
+ VariadicStruk s(1, 2, 3);
+}
+
+// When a variadic constructor is present, we call the C2 constructor directly.
+
+// CHECK-NOT: cir.func @_ZN13VariadicStrukC2Eiz
+
+// CHECK: cir.func @_ZN13VariadicStrukC1Eiz(%arg0: !cir.ptr<!rec_VariadicStruk>
+// CHECK-SAME: %arg1: !s32i
+// CHECK-SAME: ...) {
+// 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 @_Z3barv
+// CHECK-NEXT: %[[S_ADDR:.*]] = cir.alloca !rec_VariadicStruk, !cir.ptr<!rec_VariadicStruk>, ["s", 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 @_ZN13VariadicStrukC1Eiz(%[[S_ADDR]], %[[ONE]], %[[TWO]], %[[THREE]])
+// CHECK-NEXT: cir.return
More information about the cfe-commits
mailing list