[clang] [CIR] Add support for constructor aliases (PR #145792)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 25 14:47:55 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Andy Kaylor (andykaylor)
<details>
<summary>Changes</summary>
This change adds support for handling the -mconstructor-aliases option in CIR. Aliases are not yet correctly lowered to LLVM IR. That will be implemented in a future change.
---
Full diff: https://github.com/llvm/llvm-project/pull/145792.diff
7 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+2-1)
- (modified) clang/include/clang/CIR/MissingFeatures.h (-1)
- (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+87-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+104)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+17)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+15-5)
- (added) clang/test/CIR/CodeGen/ctor-alias.cpp (+75)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index ef77c46b011f7..7e45fa464f9d4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -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);
let regions = (region AnyRegion:$body);
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 9e8944d1114b8..7009d6d7d702a 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -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; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index cd9096a0188a7..1044dfe507471 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -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) {
+ 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);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index f24bee44f26a7..1271f290d6193 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -888,6 +888,69 @@ 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;
+
+ mlir::OperandRange argOps = call.getArgs();
+ mlir::ArrayRef<mlir::Type> funcArgTypes =
+ newF.getFunctionType().getInputs();
+ // In the case of variadic functions, the call may have more arguments that
+ // the function type, so we can't use llvm::enumerate here.
+ for (unsigned i = 0; i < funcArgTypes.size(); i++) {
+ if (argOps[i].getType() == funcArgTypes[i])
+ 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,
@@ -1797,11 +1860,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);
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 9f6a57c31d291..5a1eb9dea35c6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -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.
@@ -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);
@@ -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
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 17157561357f9..d14fb6af5a988 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1419,13 +1419,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() {
@@ -1460,6 +1464,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()) {
diff --git a/clang/test/CIR/CodeGen/ctor-alias.cpp b/clang/test/CIR/CodeGen/ctor-alias.cpp
new file mode 100644
index 0000000000000..a20e206c6d714
--- /dev/null
+++ b/clang/test/CIR/CodeGen/ctor-alias.cpp
@@ -0,0 +1,75 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+struct B {
+ B();
+};
+B::B() {
+}
+
+// OGCG: @_ZN1BC1Ev = unnamed_addr alias void (ptr), ptr @_ZN1BC2Ev
+
+// CHECK: cir.func{{.*}} @_ZN1BC2Ev(%arg0: !cir.ptr<!rec_B>
+// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!rec_B>>, ["this", init]
+// CHECK: cir.store %arg0, %[[THIS_ADDR]]
+// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+
+// CHECK: cir.func{{.*}} private dso_local @_ZN1BC1Ev(!cir.ptr<!rec_B>) alias(@_ZN1BC2Ev)
+
+// LLVM: define{{.*}} @_ZN1BC2Ev(ptr %[[THIS_ARG:.*]])
+// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
+// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+
+// This should be an alias, like the similar OGCG alias above, but that's not
+// implemented yet.
+// LLVM: declare dso_local void @_ZN1BC1Ev(ptr)
+
+// OGCG: define{{.*}} @_ZN1BC2Ev(ptr{{.*}} %[[THIS_ARG:.*]])
+// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
+// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+
+// The constructor in this cases is handled by RAUW rather than aliasing.
+struct Struk {
+ Struk() {}
+};
+
+void baz() {
+ Struk s;
+}
+
+// CHECK: cir.func{{.*}} @_ZN5StrukC2Ev(%arg0: !cir.ptr<!rec_Struk>
+// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["this", init]
+// CHECK: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>
+// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_Struk>>, !cir.ptr<!rec_Struk>
+// CHECK: cir.return
+
+// CHECK-NOT: cir.func{{.*}} @_ZN5StrukC1Ev
+
+// CHECK: cir.func{{.*}} @_Z3bazv()
+// CHECK: %[[S_ADDR:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["s", init]
+// CHECK: cir.call @_ZN5StrukC2Ev(%[[S_ADDR]]) : (!cir.ptr<!rec_Struk>) -> ()
+
+// LLVM: define linkonce_odr void @_ZN5StrukC2Ev(ptr %[[THIS_ARG]])
+// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
+// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
+
+// LLVM: define{{.*}} void @_Z3bazv()
+// LLVM: %[[S_ADDR:.*]] = alloca %struct.Struk
+// LLVM: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]])
+
+// This function gets emitted before the constructor in OGCG.
+// OGCG: define{{.*}} void @_Z3bazv()
+// OGCG: %[[S_ADDR:.*]] = alloca %struct.Struk
+// OGCG: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]])
+
+// OGCG: define linkonce_odr void @_ZN5StrukC2Ev(ptr{{.*}} %[[THIS_ARG]])
+// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
+// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
+// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
``````````
</details>
https://github.com/llvm/llvm-project/pull/145792
More information about the cfe-commits
mailing list