Skip to content

Commit b9fa4c7

Browse files
committed
[CIR] Add support for constructor aliases
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.
1 parent 79da5fe commit b9fa4c7

File tree

7 files changed

+300
-9
lines changed

7 files changed

+300
-9
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1772,7 +1772,8 @@ def FuncOp : CIR_Op<"func", [
17721772
OptionalAttr<StrAttr>:$sym_visibility,
17731773
UnitAttr:$comdat,
17741774
OptionalAttr<DictArrayAttr>:$arg_attrs,
1775-
OptionalAttr<DictArrayAttr>:$res_attrs);
1775+
OptionalAttr<DictArrayAttr>:$res_attrs,
1776+
OptionalAttr<FlatSymbolRefAttr>:$aliasee);
17761777

17771778
let regions = (region AnyRegion:$body);
17781779

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ struct MissingFeatures {
8181
static bool opFuncMultipleReturnVals() { return false; }
8282
static bool opFuncAttributesForDefinition() { return false; }
8383
static bool opFuncMaybeHandleStaticInExternC() { return false; }
84-
static bool opFuncGlobalAliases() { return false; }
8584
static bool setLLVMFunctionFEnvAttributes() { return false; }
8685
static bool setFunctionAttributes() { return false; }
8786

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,102 @@ void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc,
7979
}
8080
}
8181

82+
// Find out how to cirgen the complete destructor and constructor
83+
namespace {
84+
enum class StructorCIRGen { Emit, RAUW, Alias, COMDAT };
85+
}
86+
87+
static StructorCIRGen getCIRGenToUse(CIRGenModule &cgm,
88+
const CXXMethodDecl *md) {
89+
if (!cgm.getCodeGenOpts().CXXCtorDtorAliases)
90+
return StructorCIRGen::Emit;
91+
92+
// The complete and base structors are not equivalent if there are any virtual
93+
// bases, so emit separate functions.
94+
if (md->getParent()->getNumVBases()) {
95+
// The return value is correct here, but other support for this is NYI.
96+
cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: virtual bases");
97+
return StructorCIRGen::Emit;
98+
}
99+
100+
GlobalDecl aliasDecl;
101+
if (const auto *dd = dyn_cast<CXXDestructorDecl>(md)) {
102+
// The assignment is correct here, but other support for this is NYI.
103+
cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: dtor");
104+
aliasDecl = GlobalDecl(dd, Dtor_Complete);
105+
} else {
106+
const auto *cd = cast<CXXConstructorDecl>(md);
107+
aliasDecl = GlobalDecl(cd, Ctor_Complete);
108+
}
109+
110+
cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl);
111+
112+
if (cir::isDiscardableIfUnused(linkage))
113+
return StructorCIRGen::RAUW;
114+
115+
// FIXME: Should we allow available_externally aliases?
116+
if (!cir::isValidLinkage(linkage))
117+
return StructorCIRGen::RAUW;
118+
119+
if (cir::isWeakForLinker(linkage)) {
120+
// Only ELF and wasm support COMDATs with arbitrary names (C5/D5).
121+
if (cgm.getTarget().getTriple().isOSBinFormatELF() ||
122+
cgm.getTarget().getTriple().isOSBinFormatWasm())
123+
return StructorCIRGen::COMDAT;
124+
return StructorCIRGen::Emit;
125+
}
126+
127+
return StructorCIRGen::Alias;
128+
}
129+
130+
static void emitConstructorDestructorAlias(CIRGenModule &cgm,
131+
GlobalDecl aliasDecl,
132+
GlobalDecl targetDecl) {
133+
cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl);
134+
135+
// Does this function alias already exists?
136+
StringRef mangledName = cgm.getMangledName(aliasDecl);
137+
auto globalValue = dyn_cast_or_null<cir::CIRGlobalValueInterface>(
138+
cgm.getGlobalValue(mangledName));
139+
if (globalValue && !globalValue.isDeclaration())
140+
return;
141+
142+
auto entry = cast_or_null<cir::FuncOp>(cgm.getGlobalValue(mangledName));
143+
144+
// Retrieve aliasee info.
145+
auto aliasee = cast<cir::FuncOp>(cgm.getAddrOfGlobal(targetDecl));
146+
147+
// Populate actual alias.
148+
cgm.emitAliasForGlobal(mangledName, entry, aliasDecl, aliasee, linkage);
149+
}
150+
82151
void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) {
83152
auto *md = cast<CXXMethodDecl>(gd.getDecl());
84153
auto *cd = dyn_cast<CXXConstructorDecl>(md);
85154

155+
StructorCIRGen cirGenType = getCIRGenToUse(cgm, md);
156+
86157
if (!cd) {
87158
cgm.errorNYI(md->getSourceRange(), "CXCABI emit destructor");
88159
return;
89160
}
90161

91-
if (cgm.getCodeGenOpts().CXXCtorDtorAliases)
92-
cgm.errorNYI(md->getSourceRange(), "Ctor/Dtor aliases");
162+
if (gd.getCtorType() == Ctor_Complete) {
163+
GlobalDecl baseDecl = gd.getWithCtorType(Ctor_Base);
164+
165+
if (cirGenType == StructorCIRGen::Alias ||
166+
cirGenType == StructorCIRGen::COMDAT) {
167+
emitConstructorDestructorAlias(cgm, gd, baseDecl);
168+
return;
169+
}
170+
171+
if (cirGenType == StructorCIRGen::RAUW) {
172+
StringRef mangledName = cgm.getMangledName(gd);
173+
mlir::Operation *aliasee = cgm.getAddrOfGlobal(baseDecl);
174+
cgm.addReplacement(mangledName, aliasee);
175+
return;
176+
}
177+
}
93178

94179
auto fn = cgm.codegenCXXStructor(gd);
95180

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,69 @@ void CIRGenModule::updateCompletedType(const TagDecl *td) {
888888
genTypes.updateCompletedType(td);
889889
}
890890

891+
void CIRGenModule::addReplacement(StringRef name, mlir::Operation *op) {
892+
replacements[name] = op;
893+
}
894+
895+
void CIRGenModule::replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF) {
896+
std::optional<mlir::SymbolTable::UseRange> optionalUseRange =
897+
oldF.getSymbolUses(theModule);
898+
if (!optionalUseRange)
899+
return;
900+
901+
for (const mlir::SymbolTable::SymbolUse &u : *optionalUseRange) {
902+
// CallTryOp only shows up after FlattenCFG.
903+
auto call = mlir::dyn_cast<cir::CallOp>(u.getUser());
904+
if (!call)
905+
continue;
906+
907+
mlir::OperandRange argOps = call.getArgs();
908+
mlir::ArrayRef<mlir::Type> funcArgTypes =
909+
newF.getFunctionType().getInputs();
910+
// In the case of variadic functions, the call may have more arguments that
911+
// the function type, so we can't use llvm::enumerate here.
912+
for (unsigned i = 0; i < funcArgTypes.size(); i++) {
913+
if (argOps[i].getType() == funcArgTypes[i])
914+
continue;
915+
916+
// The purpose of this entire function is to insert bitcasts in the case
917+
// where these types don't match, but I haven't seen a case where that
918+
// happens.
919+
errorNYI(call.getLoc(), "replace call with mismatched types");
920+
}
921+
}
922+
}
923+
924+
void CIRGenModule::applyReplacements() {
925+
for (auto &i : replacements) {
926+
StringRef mangledName = i.first();
927+
mlir::Operation *replacement = i.second;
928+
mlir::Operation *entry = getGlobalValue(mangledName);
929+
if (!entry)
930+
continue;
931+
assert(isa<cir::FuncOp>(entry) && "expected function");
932+
auto oldF = cast<cir::FuncOp>(entry);
933+
auto newF = dyn_cast<cir::FuncOp>(replacement);
934+
if (!newF) {
935+
// In classic codegen, this can be a global alias, a bitcast, or a GEP.
936+
errorNYI(replacement->getLoc(), "replacement is not a function");
937+
continue;
938+
}
939+
940+
// LLVM has opaque pointer but CIR not. So we may have to handle these
941+
// different pointer types when performing replacement.
942+
replacePointerTypeArgs(oldF, newF);
943+
944+
// Replace old with new, but keep the old order.
945+
if (oldF.replaceAllSymbolUses(newF.getSymNameAttr(), theModule).failed())
946+
llvm_unreachable("internal error, cannot RAUW symbol");
947+
if (newF) {
948+
newF->moveBefore(oldF);
949+
oldF->erase();
950+
}
951+
}
952+
}
953+
891954
// TODO(CIR): this could be a common method between LLVM codegen.
892955
static bool isVarDeclStrongDefinition(const ASTContext &astContext,
893956
CIRGenModule &cgm, const VarDecl *vd,
@@ -1797,11 +1860,52 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
17971860

17981861
void CIRGenModule::release() {
17991862
emitDeferred();
1863+
applyReplacements();
18001864

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

1869+
void CIRGenModule::emitAliasForGlobal(StringRef mangledName,
1870+
mlir::Operation *op, GlobalDecl aliasGD,
1871+
cir::FuncOp aliasee,
1872+
cir::GlobalLinkageKind linkage) {
1873+
1874+
auto *aliasFD = dyn_cast<FunctionDecl>(aliasGD.getDecl());
1875+
assert(aliasFD && "expected FunctionDecl");
1876+
1877+
// The aliasee function type is different from the alias one, this difference
1878+
// is specific to CIR because in LLVM the ptr types are already erased at this
1879+
// point.
1880+
const CIRGenFunctionInfo &fnInfo =
1881+
getTypes().arrangeCXXStructorDeclaration(aliasGD);
1882+
cir::FuncType fnType = getTypes().getFunctionType(fnInfo);
1883+
1884+
cir::FuncOp alias =
1885+
createCIRFunction(getLoc(aliasGD.getDecl()->getSourceRange()),
1886+
mangledName, fnType, aliasFD);
1887+
alias.setAliasee(aliasee.getName());
1888+
alias.setLinkage(linkage);
1889+
// Declarations cannot have public MLIR visibility, just mark them private
1890+
// but this really should have no meaning since CIR should not be using
1891+
// this information to derive linkage information.
1892+
mlir::SymbolTable::setSymbolVisibility(
1893+
alias, mlir::SymbolTable::Visibility::Private);
1894+
1895+
// Alias constructors and destructors are always unnamed_addr.
1896+
assert(!cir::MissingFeatures::opGlobalUnnamedAddr());
1897+
1898+
// Switch any previous uses to the alias.
1899+
if (op) {
1900+
errorNYI(aliasFD->getSourceRange(), "emitAliasForGlobal: previous uses");
1901+
} else {
1902+
// Name already set by createCIRFunction
1903+
}
1904+
1905+
// Finally, set up the alias with its proper name and attributes.
1906+
setCommonAttributes(aliasGD, alias);
1907+
}
1908+
18051909
mlir::Type CIRGenModule::convertType(QualType type) {
18061910
return genTypes.convertType(type);
18071911
}

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ class CIRGenModule : public CIRGenTypeCache {
256256
/// declarations are emitted lazily.
257257
void emitGlobal(clang::GlobalDecl gd);
258258

259+
void emitAliasForGlobal(llvm::StringRef mangledName, mlir::Operation *op,
260+
GlobalDecl aliasGD, cir::FuncOp aliasee,
261+
cir::GlobalLinkageKind linkage);
262+
259263
mlir::Type convertType(clang::QualType type);
260264

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

365+
void addReplacement(llvm::StringRef name, mlir::Operation *op);
366+
361367
/// Helpers to emit "not yet implemented" error diagnostics
362368
DiagnosticBuilder errorNYI(SourceLocation, llvm::StringRef);
363369

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

406+
// FIXME: should we use llvm::TrackingVH<mlir::Operation> here?
407+
typedef llvm::StringMap<mlir::Operation *> ReplacementsTy;
408+
ReplacementsTy replacements;
409+
/// Call replaceAllUsesWith on all pairs in replacements.
410+
void applyReplacements();
411+
412+
/// A helper function to replace all uses of OldF to NewF that replace
413+
/// the type of pointer arguments. This is not needed to tradtional
414+
/// pipeline since LLVM has opaque pointers but CIR not.
415+
void replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF);
416+
400417
void setNonAliasAttributes(GlobalDecl gd, mlir::Operation *op);
401418
};
402419
} // namespace CIRGen

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,13 +1419,17 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
14191419
}
14201420

14211421
// This function corresponds to `llvm::GlobalValue::isDeclaration` and should
1422-
// have a similar implementation. We don't currently support aliases, ifuncs,
1423-
// or materializable functions, but those should be handled here as they are
1424-
// implemented.
1422+
// have a similar implementation. We don't currently ifuncs or materializable
1423+
// functions, but those should be handled here as they are implemented.
14251424
bool cir::FuncOp::isDeclaration() {
1426-
assert(!cir::MissingFeatures::opFuncGlobalAliases());
14271425
assert(!cir::MissingFeatures::supportIFuncAttr());
1428-
return getFunctionBody().empty();
1426+
1427+
std::optional<StringRef> aliasee = getAliasee();
1428+
if (!aliasee)
1429+
return getFunctionBody().empty();
1430+
1431+
// Aliases are always definitions.
1432+
return false;
14291433
}
14301434

14311435
mlir::Region *cir::FuncOp::getCallableRegion() {
@@ -1460,6 +1464,12 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
14601464
function_interface_impl::printFunctionSignature(
14611465
p, *this, fnType.getInputs(), fnType.isVarArg(), fnType.getReturnTypes());
14621466

1467+
if (std::optional<StringRef> aliaseeName = getAliasee()) {
1468+
p << " alias(";
1469+
p.printSymbolName(*aliaseeName);
1470+
p << ")";
1471+
}
1472+
14631473
// Print the body if this is not an external function.
14641474
Region &body = getOperation()->getRegion(0);
14651475
if (!body.empty()) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
5+
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
7+
8+
struct B {
9+
B();
10+
};
11+
B::B() {
12+
}
13+
14+
// OGCG: @_ZN1BC1Ev = unnamed_addr alias void (ptr), ptr @_ZN1BC2Ev
15+
16+
// CHECK: cir.func{{.*}} @_ZN1BC2Ev(%arg0: !cir.ptr<!rec_B>
17+
// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!rec_B>>, ["this", init]
18+
// CHECK: cir.store %arg0, %[[THIS_ADDR]]
19+
// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
20+
21+
// CHECK: cir.func{{.*}} private dso_local @_ZN1BC1Ev(!cir.ptr<!rec_B>) alias(@_ZN1BC2Ev)
22+
23+
// LLVM: define{{.*}} @_ZN1BC2Ev(ptr %[[THIS_ARG:.*]])
24+
// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
25+
// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
26+
// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
27+
28+
// This should be an alias, like the similar OGCG alias above, but that's not
29+
// implemented yet.
30+
// LLVM: declare dso_local void @_ZN1BC1Ev(ptr)
31+
32+
// OGCG: define{{.*}} @_ZN1BC2Ev(ptr{{.*}} %[[THIS_ARG:.*]])
33+
// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
34+
// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
35+
// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
36+
37+
// The constructor in this cases is handled by RAUW rather than aliasing.
38+
struct Struk {
39+
Struk() {}
40+
};
41+
42+
void baz() {
43+
Struk s;
44+
}
45+
46+
// CHECK: cir.func{{.*}} @_ZN5StrukC2Ev(%arg0: !cir.ptr<!rec_Struk>
47+
// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["this", init]
48+
// CHECK: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>
49+
// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_Struk>>, !cir.ptr<!rec_Struk>
50+
// CHECK: cir.return
51+
52+
// CHECK-NOT: cir.func{{.*}} @_ZN5StrukC1Ev
53+
54+
// CHECK: cir.func{{.*}} @_Z3bazv()
55+
// CHECK: %[[S_ADDR:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["s", init]
56+
// CHECK: cir.call @_ZN5StrukC2Ev(%[[S_ADDR]]) : (!cir.ptr<!rec_Struk>) -> ()
57+
58+
// LLVM: define linkonce_odr void @_ZN5StrukC2Ev(ptr %[[THIS_ARG]])
59+
// LLVM: %[[THIS_ADDR:.*]] = alloca ptr
60+
// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
61+
// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]
62+
63+
// LLVM: define{{.*}} void @_Z3bazv()
64+
// LLVM: %[[S_ADDR:.*]] = alloca %struct.Struk
65+
// LLVM: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]])
66+
67+
// This function gets emitted before the constructor in OGCG.
68+
// OGCG: define{{.*}} void @_Z3bazv()
69+
// OGCG: %[[S_ADDR:.*]] = alloca %struct.Struk
70+
// OGCG: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]])
71+
72+
// OGCG: define linkonce_odr void @_ZN5StrukC2Ev(ptr{{.*}} %[[THIS_ARG]])
73+
// OGCG: %[[THIS_ADDR:.*]] = alloca ptr
74+
// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]]
75+
// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]]

0 commit comments

Comments
 (0)