Skip to content

[CIR] Upstream support for emitting constructors #143639

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
4 changes: 4 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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; }
Expand Down
40 changes: 40 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXX.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class CIRGenCXXABI {

void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);

/// Emit a single constructor/destructor with the gen type from a C++
/// constructor/destructor Decl.
virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;

public:
clang::ImplicitParamDecl *getThisDecl(CIRGenFunction &cgf) {
return cgf.cxxabiThisDecl;
Expand All @@ -55,12 +59,19 @@ class CIRGenCXXABI {
return md->getParent();
}

/// 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'.
void buildThisParam(CIRGenFunction &cgf, FunctionArgList &params);

/// 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;

/// 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).
Expand Down
41 changes: 41 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
81 changes: 81 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,87 @@
using namespace clang;
using namespace clang::CIRGen;

/// Checks whether the given constructor is a valid subject for 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
// 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,
Expand Down
60 changes: 54 additions & 6 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -496,6 +496,54 @@ 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());
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.
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,
Expand All @@ -522,16 +570,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");
assert(!cir::MissingFeatures::cxxabiStructorImplicitParam());

return retTy;
}
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
Loading