Skip to content

[CIR] Add support for constructor aliases #145792

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 27, 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
3 changes: 2 additions & 1 deletion clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1772,7 +1772,8 @@ def FuncOp : CIR_Op<"func", [
OptionalAttr<StrAttr>:$sym_visibility,
UnitAttr:$comdat,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs);
OptionalAttr<DictArrayAttr>:$res_attrs,
OptionalAttr<FlatSymbolRefAttr>:$aliasee);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a cir to cir test to exercise the parser?


let regions = (region AnyRegion:$body);

Expand Down
1 change: 0 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ struct MissingFeatures {
static bool opFuncMultipleReturnVals() { return false; }
static bool opFuncAttributesForDefinition() { return false; }
static bool opFuncMaybeHandleStaticInExternC() { return false; }
static bool opFuncGlobalAliases() { return false; }
static bool setLLVMFunctionFEnvAttributes() { return false; }
static bool setFunctionAttributes() { return false; }

Expand Down
89 changes: 87 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,102 @@ void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc,
}
}

// Find out how to cirgen the complete destructor and constructor
namespace {
enum class StructorCIRGen { Emit, RAUW, Alias, COMDAT };
}

static StructorCIRGen getCIRGenToUse(CIRGenModule &cgm,
const CXXMethodDecl *md) {
if (!cgm.getCodeGenOpts().CXXCtorDtorAliases)
return StructorCIRGen::Emit;

// The complete and base structors are not equivalent if there are any virtual
// bases, so emit separate functions.
if (md->getParent()->getNumVBases()) {
// The return value is correct here, but other support for this is NYI.
cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: virtual bases");
return StructorCIRGen::Emit;
}

GlobalDecl aliasDecl;
if (const auto *dd = dyn_cast<CXXDestructorDecl>(md)) {
// The assignment is correct here, but other support for this is NYI.
cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: dtor");
aliasDecl = GlobalDecl(dd, Dtor_Complete);
} else {
const auto *cd = cast<CXXConstructorDecl>(md);
aliasDecl = GlobalDecl(cd, Ctor_Complete);
}

cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl);

if (cir::isDiscardableIfUnused(linkage))
return StructorCIRGen::RAUW;

// FIXME: Should we allow available_externally aliases?
if (!cir::isValidLinkage(linkage))
return StructorCIRGen::RAUW;

if (cir::isWeakForLinker(linkage)) {
// Only ELF and wasm support COMDATs with arbitrary names (C5/D5).
if (cgm.getTarget().getTriple().isOSBinFormatELF() ||
cgm.getTarget().getTriple().isOSBinFormatWasm())
return StructorCIRGen::COMDAT;
return StructorCIRGen::Emit;
}

return StructorCIRGen::Alias;
}

static void emitConstructorDestructorAlias(CIRGenModule &cgm,
GlobalDecl aliasDecl,
GlobalDecl targetDecl) {
cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl);

// Does this function alias already exists?
StringRef mangledName = cgm.getMangledName(aliasDecl);
auto globalValue = dyn_cast_or_null<cir::CIRGlobalValueInterface>(
cgm.getGlobalValue(mangledName));
if (globalValue && !globalValue.isDeclaration())
return;

auto entry = cast_or_null<cir::FuncOp>(cgm.getGlobalValue(mangledName));

// Retrieve aliasee info.
auto aliasee = cast<cir::FuncOp>(cgm.getAddrOfGlobal(targetDecl));

// Populate actual alias.
cgm.emitAliasForGlobal(mangledName, entry, aliasDecl, aliasee, linkage);
}

void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) {
auto *md = cast<CXXMethodDecl>(gd.getDecl());
auto *cd = dyn_cast<CXXConstructorDecl>(md);

StructorCIRGen cirGenType = getCIRGenToUse(cgm, md);

if (!cd) {
cgm.errorNYI(md->getSourceRange(), "CXCABI emit destructor");
return;
}

if (cgm.getCodeGenOpts().CXXCtorDtorAliases)
cgm.errorNYI(md->getSourceRange(), "Ctor/Dtor aliases");
if (gd.getCtorType() == Ctor_Complete) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destructor doesn't require special handling here? Seems a little odd to me...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an artifact of destructors not being implemented yet. See the errorNYI above.

GlobalDecl baseDecl = gd.getWithCtorType(Ctor_Base);

if (cirGenType == StructorCIRGen::Alias ||
cirGenType == StructorCIRGen::COMDAT) {
emitConstructorDestructorAlias(cgm, gd, baseDecl);
return;
}

if (cirGenType == StructorCIRGen::RAUW) {
StringRef mangledName = cgm.getMangledName(gd);
mlir::Operation *aliasee = cgm.getAddrOfGlobal(baseDecl);
cgm.addReplacement(mangledName, aliasee);
return;
}
}

auto fn = cgm.codegenCXXStructor(gd);

Expand Down
100 changes: 100 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,65 @@ void CIRGenModule::updateCompletedType(const TagDecl *td) {
genTypes.updateCompletedType(td);
}

void CIRGenModule::addReplacement(StringRef name, mlir::Operation *op) {
replacements[name] = op;
}

void CIRGenModule::replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF) {
std::optional<mlir::SymbolTable::UseRange> optionalUseRange =
oldF.getSymbolUses(theModule);
if (!optionalUseRange)
return;

for (const mlir::SymbolTable::SymbolUse &u : *optionalUseRange) {
// CallTryOp only shows up after FlattenCFG.
auto call = mlir::dyn_cast<cir::CallOp>(u.getUser());
if (!call)
continue;

for (const auto [argOp, fnArgType] :
llvm::zip(call.getArgs(), newF.getFunctionType().getInputs())) {
if (argOp.getType() == fnArgType)
continue;

// The purpose of this entire function is to insert bitcasts in the case
// where these types don't match, but I haven't seen a case where that
// happens.
errorNYI(call.getLoc(), "replace call with mismatched types");
}
}
}

void CIRGenModule::applyReplacements() {
for (auto &i : replacements) {
StringRef mangledName = i.first();
mlir::Operation *replacement = i.second;
mlir::Operation *entry = getGlobalValue(mangledName);
if (!entry)
continue;
assert(isa<cir::FuncOp>(entry) && "expected function");
auto oldF = cast<cir::FuncOp>(entry);
auto newF = dyn_cast<cir::FuncOp>(replacement);
if (!newF) {
// In classic codegen, this can be a global alias, a bitcast, or a GEP.
errorNYI(replacement->getLoc(), "replacement is not a function");
continue;
}

// LLVM has opaque pointer but CIR not. So we may have to handle these
// different pointer types when performing replacement.
replacePointerTypeArgs(oldF, newF);

// Replace old with new, but keep the old order.
if (oldF.replaceAllSymbolUses(newF.getSymNameAttr(), theModule).failed())
llvm_unreachable("internal error, cannot RAUW symbol");
if (newF) {
newF->moveBefore(oldF);
oldF->erase();
}
}
}

// TODO(CIR): this could be a common method between LLVM codegen.
static bool isVarDeclStrongDefinition(const ASTContext &astContext,
CIRGenModule &cgm, const VarDecl *vd,
Expand Down Expand Up @@ -1797,11 +1856,52 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {

void CIRGenModule::release() {
emitDeferred();
applyReplacements();

// There's a lot of code that is not implemented yet.
assert(!cir::MissingFeatures::cgmRelease());
}

void CIRGenModule::emitAliasForGlobal(StringRef mangledName,
mlir::Operation *op, GlobalDecl aliasGD,
cir::FuncOp aliasee,
cir::GlobalLinkageKind linkage) {

auto *aliasFD = dyn_cast<FunctionDecl>(aliasGD.getDecl());
assert(aliasFD && "expected FunctionDecl");

// The aliasee function type is different from the alias one, this difference
// is specific to CIR because in LLVM the ptr types are already erased at this
// point.
const CIRGenFunctionInfo &fnInfo =
getTypes().arrangeCXXStructorDeclaration(aliasGD);
cir::FuncType fnType = getTypes().getFunctionType(fnInfo);

cir::FuncOp alias =
createCIRFunction(getLoc(aliasGD.getDecl()->getSourceRange()),
mangledName, fnType, aliasFD);
alias.setAliasee(aliasee.getName());
alias.setLinkage(linkage);
// Declarations cannot have public MLIR visibility, just mark them private
// but this really should have no meaning since CIR should not be using
// this information to derive linkage information.
mlir::SymbolTable::setSymbolVisibility(
alias, mlir::SymbolTable::Visibility::Private);

// Alias constructors and destructors are always unnamed_addr.
assert(!cir::MissingFeatures::opGlobalUnnamedAddr());

// Switch any previous uses to the alias.
if (op) {
errorNYI(aliasFD->getSourceRange(), "emitAliasForGlobal: previous uses");
} else {
// Name already set by createCIRFunction
}

// Finally, set up the alias with its proper name and attributes.
setCommonAttributes(aliasGD, alias);
}

mlir::Type CIRGenModule::convertType(QualType type) {
return genTypes.convertType(type);
}
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ class CIRGenModule : public CIRGenTypeCache {
/// declarations are emitted lazily.
void emitGlobal(clang::GlobalDecl gd);

void emitAliasForGlobal(llvm::StringRef mangledName, mlir::Operation *op,
GlobalDecl aliasGD, cir::FuncOp aliasee,
cir::GlobalLinkageKind linkage);

mlir::Type convertType(clang::QualType type);

/// Set the visibility for the given global.
Expand Down Expand Up @@ -358,6 +362,8 @@ class CIRGenModule : public CIRGenTypeCache {
cir::GlobalLinkageKind getCIRLinkageVarDefinition(const VarDecl *vd,
bool isConstant);

void addReplacement(llvm::StringRef name, mlir::Operation *op);

/// Helpers to emit "not yet implemented" error diagnostics
DiagnosticBuilder errorNYI(SourceLocation, llvm::StringRef);

Expand Down Expand Up @@ -397,6 +403,17 @@ class CIRGenModule : public CIRGenTypeCache {
llvm::MapVector<clang::GlobalDecl, llvm::StringRef> mangledDeclNames;
llvm::StringMap<clang::GlobalDecl, llvm::BumpPtrAllocator> manglings;

// FIXME: should we use llvm::TrackingVH<mlir::Operation> here?
typedef llvm::StringMap<mlir::Operation *> ReplacementsTy;
ReplacementsTy replacements;
/// Call replaceAllUsesWith on all pairs in replacements.
void applyReplacements();

/// A helper function to replace all uses of OldF to NewF that replace
/// the type of pointer arguments. This is not needed to tradtional
/// pipeline since LLVM has opaque pointers but CIR not.
void replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF);

void setNonAliasAttributes(GlobalDecl gd, mlir::Operation *op);
};
} // namespace CIRGen
Expand Down
36 changes: 31 additions & 5 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1403,11 +1403,27 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
state.addAttribute(getFunctionTypeAttrName(state.name),
TypeAttr::get(fnType));

bool hasAlias = false;
mlir::StringAttr aliaseeNameAttr = getAliaseeAttrName(state.name);
if (parser.parseOptionalKeyword("alias").succeeded()) {
if (parser.parseLParen().failed())
return failure();
mlir::StringAttr aliaseeAttr;
if (parser.parseOptionalSymbolName(aliaseeAttr).failed())
return failure();
state.addAttribute(aliaseeNameAttr, FlatSymbolRefAttr::get(aliaseeAttr));
if (parser.parseRParen().failed())
return failure();
hasAlias = true;
}

// Parse the optional function body.
auto *body = state.addRegion();
OptionalParseResult parseResult = parser.parseOptionalRegion(
*body, arguments, /*enableNameShadowing=*/false);
if (parseResult.has_value()) {
if (hasAlias)
return parser.emitError(loc, "function alias shall not have a body");
if (failed(*parseResult))
return failure();
// Function body was parsed, make sure its not empty.
Expand All @@ -1419,13 +1435,17 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
}

// This function corresponds to `llvm::GlobalValue::isDeclaration` and should
// have a similar implementation. We don't currently support aliases, ifuncs,
// or materializable functions, but those should be handled here as they are
// implemented.
// have a similar implementation. We don't currently ifuncs or materializable
// functions, but those should be handled here as they are implemented.
bool cir::FuncOp::isDeclaration() {
assert(!cir::MissingFeatures::opFuncGlobalAliases());
assert(!cir::MissingFeatures::supportIFuncAttr());
return getFunctionBody().empty();

std::optional<StringRef> aliasee = getAliasee();
if (!aliasee)
return getFunctionBody().empty();

// Aliases are always definitions.
return false;
}

mlir::Region *cir::FuncOp::getCallableRegion() {
Expand Down Expand Up @@ -1460,6 +1480,12 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
function_interface_impl::printFunctionSignature(
p, *this, fnType.getInputs(), fnType.isVarArg(), fnType.getReturnTypes());

if (std::optional<StringRef> aliaseeName = getAliasee()) {
p << " alias(";
p.printSymbolName(*aliaseeName);
p << ")";
}

// Print the body if this is not an external function.
Region &body = getOperation()->getRegion(0);
if (!body.empty()) {
Expand Down
Loading