-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[CIR] Add support for global ctor/dtor attributes #163247
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
Conversation
This adds support for adding the `global_ctor` or `global_dtor` attribute to the CIR representation of functions defined with `__attribute__((constructor))` or `__attribute__((destructor))` and adding them to the `@llvm.global_ctors` or `@llvm.global_dtors` list during lowering to LLVM IR.
@llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) ChangesThis adds support for adding the Full diff: https://github.com/llvm/llvm-project/pull/163247.diff 11 Files Affected:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index bb62223d9e152..610e349717e12 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -814,6 +814,14 @@ def CIR_GlobalCtorAttr : CIR_GlobalCtorDtor<"Ctor", "ctor"> {
}];
}
+def CIR_GlobalDtorAttr : CIR_GlobalCtorDtor<"Dtor", "dtor"> {
+ let summary = "Marks a function as a global destructor";
+ let description = [{
+ Marks a function as a global destructor in the module dtors list.
+ The function will be executed before the module unloading.
+ }];
+}
+
//===----------------------------------------------------------------------===//
// BitfieldInfoAttr
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index feb08d6088125..e91537186df59 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -43,6 +43,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getSideEffectAttrName() { return "side_effect"; }
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
+ static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
void registerAttributes();
void registerTypes();
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 27fe0cc46d7cf..4c15d9ed0f834 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -88,6 +88,19 @@ class CIR_Op<string mnemonic, list<Trait> traits = []> :
code extraLLVMLoweringPatternDecl = "";
}
+//===----------------------------------------------------------------------===//
+// CIR Operation Traits
+//===----------------------------------------------------------------------===//
+
+class HasAtMostOneOfAttrsPred<list<string> names> :
+ CPred<!foldl("0", names, acc, name, acc # " + (" # name # " ? 1 : 0)")
+ # " <= 1">;
+
+class HasAtMostOneOfAttrs<list<string> names> : PredOpTrait<
+ "has only one of the optional attributes: " # !interleave(names, ", "),
+ HasAtMostOneOfAttrsPred<!foreach(name, names, "$" # name)>
+>;
+
//===----------------------------------------------------------------------===//
// CastOp
//===----------------------------------------------------------------------===//
@@ -2422,9 +2435,17 @@ def CIR_GetMemberOp : CIR_Op<"get_member"> {
// TODO(CIR): FuncOp is still a tiny shell of what it will become. Many more
// properties and attributes will be added as upstreaming continues.
+def CIR_OptionalPriorityAttr : OptionalAttr<
+ DefaultValuedAttr<
+ ConfinedAttr<I32Attr, [IntMinValue<101>, IntMaxValue<65535>]>,
+ "65535"
+ >
+>;
+
def CIR_FuncOp : CIR_Op<"func", [
AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
DeclareOpInterfaceMethods<CIRGlobalValueInterface>,
+ HasAtMostOneOfAttrs<["global_ctor_priority", "global_dtor_priority"]>,
IsolatedFromAbove
]> {
let summary = "Declare or define a function";
@@ -2449,6 +2470,12 @@ def CIR_FuncOp : CIR_Op<"func", [
without a prototype and, consequently, may contain calls with invalid
arguments and undefined behavior.
+ The `global_ctor` keyword indicates whether a function should execute before
+ `main()` function, as specified by `__attribute__((constructor))`. An
+ execution priority can also be specified `global_ctor(<priority>)`.
+ Similarly, for global destructors both `global_dtor` and
+ `global_dtor(<priority>)` are available.
+
Example:
```mlir
@@ -2487,7 +2514,9 @@ def CIR_FuncOp : CIR_Op<"func", [
UnitAttr:$comdat,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs,
- OptionalAttr<FlatSymbolRefAttr>:$aliasee);
+ OptionalAttr<FlatSymbolRefAttr>:$aliasee,
+ CIR_OptionalPriorityAttr:$global_ctor_priority,
+ CIR_OptionalPriorityAttr:$global_dtor_priority);
let regions = (region AnyRegion:$body);
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index df82ca138d4b1..4fbae150b587e 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -39,7 +39,6 @@ struct MissingFeatures {
static bool opGlobalUsedOrCompilerUsed() { return false; }
static bool opGlobalAnnotations() { return false; }
static bool opGlobalCtorPriority() { return false; }
- static bool opGlobalDtorList() { return false; }
static bool setDSOLocal() { return false; }
static bool setComdat() { return false; }
@@ -175,6 +174,10 @@ struct MissingFeatures {
static bool atomicScope() { return false; }
static bool atomicSyncScopeID() { return false; }
+ // Global ctor handling
+ static bool globalCtorLexOrder() { return false; }
+ static bool globalCtorAssociatedData() { return false; }
+
// Misc
static bool abiArgInfo() { return false; }
static bool addHeapAllocSiteMetadata() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index fe1ea5617b8cd..82b10515a412f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -451,15 +451,47 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
setNonAliasAttributes(gd, funcOp);
assert(!cir::MissingFeatures::opFuncAttributesForDefinition());
- if (funcDecl->getAttr<ConstructorAttr>())
- errorNYI(funcDecl->getSourceRange(), "constructor attribute");
- if (funcDecl->getAttr<DestructorAttr>())
- errorNYI(funcDecl->getSourceRange(), "destructor attribute");
+ auto getPriority = [this](const auto *attr) -> int {
+ Expr *e = attr->getPriority();
+ if (e)
+ return e->EvaluateKnownConstInt(this->getASTContext()).getExtValue();
+ return attr->DefaultPriority;
+ };
+
+ if (const ConstructorAttr *ca = funcDecl->getAttr<ConstructorAttr>())
+ addGlobalCtor(funcOp, getPriority(ca));
+ if (const DestructorAttr *da = funcDecl->getAttr<DestructorAttr>())
+ addGlobalDtor(funcOp, getPriority(da));
if (funcDecl->getAttr<AnnotateAttr>())
errorNYI(funcDecl->getSourceRange(), "deferredAnnotations");
}
+/// Track functions to be called before main() runs.
+void CIRGenModule::addGlobalCtor(cir::FuncOp ctor,
+ std::optional<int> priority) {
+ assert(!cir::MissingFeatures::globalCtorLexOrder());
+ assert(!cir::MissingFeatures::globalCtorAssociatedData());
+
+ // Traditional LLVM codegen directly adds the function to the list of global
+ // ctors. In CIR we just add a global_ctor attribute to the function. The
+ // global list is created in LoweringPrepare.
+ //
+ // FIXME(from traditional LLVM): Type coercion of void()* types.
+ ctor.setGlobalCtorPriority(priority);
+}
+
+/// Add a function to the list that will be called when the module is unloaded.
+void CIRGenModule::addGlobalDtor(cir::FuncOp dtor,
+ std::optional<int> priority) {
+ if (codeGenOpts.RegisterGlobalDtorsWithAtExit &&
+ (!getASTContext().getTargetInfo().getTriple().isOSAIX()))
+ errorNYI(dtor.getLoc(), "registerGlobalDtorsWithAtExit");
+
+ // FIXME(from traditional LLVM): Type coercion of void()* types.
+ dtor.setGlobalDtorPriority(priority);
+}
+
void CIRGenModule::handleCXXStaticMemberVarInstantiation(VarDecl *vd) {
VarDecl::DefinitionKind dk = vd->isThisDeclarationADefinition();
if (dk == VarDecl::Definition && vd->hasAttr<DLLImportAttr>())
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index f627bae9f87f9..690f0ed0e9bde 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -159,6 +159,13 @@ class CIRGenModule : public CIRGenTypeCache {
bool isConstant = false,
mlir::Operation *insertPoint = nullptr);
+ /// Add a global constructor or destructor to the module.
+ /// The priority is optional, if not specified, the default priority is used.
+ void addGlobalCtor(cir::FuncOp ctor,
+ std::optional<int> priority = std::nullopt);
+ void addGlobalDtor(cir::FuncOp dtor,
+ std::optional<int> priority = std::nullopt);
+
bool shouldZeroInitPadding() const {
// In C23 (N3096) $6.7.10:
// """
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 5f88590c48d30..12837d953d677 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -15,6 +15,7 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "mlir/IR/DialectImplementation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/FunctionImplementation.h"
#include "mlir/Support/LLVM.h"
@@ -1720,6 +1721,43 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
hasAlias = true;
}
+ auto parseGlobalDtorCtor =
+ [&](StringRef keyword,
+ llvm::function_ref<void(std::optional<int> prio)> createAttr)
+ -> mlir::LogicalResult {
+ if (mlir::succeeded(parser.parseOptionalKeyword(keyword))) {
+ std::optional<int> priority;
+ if (mlir::succeeded(parser.parseOptionalLParen())) {
+ auto parsedPriority = mlir::FieldParser<int>::parse(parser);
+ if (mlir::failed(parsedPriority))
+ return parser.emitError(parser.getCurrentLocation(),
+ "failed to parse 'priority', of type 'int'");
+ priority = parsedPriority.value_or(int());
+ // Parse literal ')'
+ if (parser.parseRParen())
+ return failure();
+ }
+ createAttr(priority);
+ }
+ return success();
+ };
+
+ if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
+ mlir::IntegerAttr globalCtorPriorityAttr =
+ builder.getI32IntegerAttr(priority.value_or(65535));
+ state.addAttribute(getGlobalCtorPriorityAttrName(state.name),
+ globalCtorPriorityAttr);
+ }).failed())
+ return failure();
+
+ if (parseGlobalDtorCtor("global_dtor", [&](std::optional<int> priority) {
+ mlir::IntegerAttr globalDtorPriorityAttr =
+ builder.getI32IntegerAttr(priority.value_or(65535));
+ state.addAttribute(getGlobalDtorPriorityAttrName(state.name),
+ globalDtorPriorityAttr);
+ }).failed())
+ return failure();
+
// Parse the optional function body.
auto *body = state.addRegion();
OptionalParseResult parseResult = parser.parseOptionalRegion(
@@ -1801,6 +1839,18 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
p << ")";
}
+ if (auto globalCtorPriority = getGlobalCtorPriority()) {
+ p << " global_ctor";
+ if (globalCtorPriority.value() != 65535)
+ p << "(" << globalCtorPriority.value() << ")";
+ }
+
+ if (auto globalDtorPriority = getGlobalDtorPriority()) {
+ p << " global_dtor";
+ if (globalDtorPriority.value() != 65535)
+ p << "(" << globalDtorPriority.value() << ")";
+ }
+
// Print the body if this is not an external function.
Region &body = getOperation()->getRegion(0);
if (!body.empty()) {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index dbff0b98fbe42..d99c36241ab68 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -105,6 +105,8 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
/// List of ctors and their priorities to be called before main()
llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
+ /// List of dtors and their priorities to be called when unloading module.
+ llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList;
void setASTContext(clang::ASTContext *c) {
astCtx = c;
@@ -823,10 +825,13 @@ void LoweringPreparePass::buildGlobalCtorDtorList() {
mlir::ArrayAttr::get(&getContext(), globalCtors));
}
- // We will eventual need to populate a global_dtor list, but that's not
- // needed for globals with destructors. It will only be needed for functions
- // that are marked as global destructors with an attribute.
- assert(!cir::MissingFeatures::opGlobalDtorList());
+ if (!globalDtorList.empty()) {
+ llvm::SmallVector<mlir::Attribute> globalDtors =
+ prepareCtorDtorAttrList<cir::GlobalDtorAttr>(&getContext(),
+ globalDtorList);
+ mlirModule->setAttr(cir::CIRDialect::getGlobalDtorsAttrName(),
+ mlir::ArrayAttr::get(&getContext(), globalDtors));
+ }
}
void LoweringPreparePass::buildCXXGlobalInitFunc() {
@@ -975,22 +980,28 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
}
void LoweringPreparePass::runOnOp(mlir::Operation *op) {
- if (auto arrayCtor = dyn_cast<ArrayCtor>(op))
+ if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
lowerArrayCtor(arrayCtor);
- else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op))
+ } else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) {
lowerArrayDtor(arrayDtor);
- else if (auto cast = mlir::dyn_cast<cir::CastOp>(op))
+ } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
lowerCastOp(cast);
- else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op))
+ } else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op)) {
lowerComplexDivOp(complexDiv);
- else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op))
+ } else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op)) {
lowerComplexMulOp(complexMul);
- else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op))
+ } else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) {
lowerGlobalOp(glob);
- else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op))
+ } else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op)) {
lowerDynamicCastOp(dynamicCast);
- else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op))
+ } else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op)) {
lowerUnaryOp(unary);
+ } else if (auto fnOp = dyn_cast<cir::FuncOp>(op)) {
+ if (auto globalCtor = fnOp.getGlobalCtorPriority())
+ globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
+ else if (auto globalDtor = fnOp.getGlobalDtorPriority())
+ globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
+ }
}
void LoweringPreparePass::runOnOperation() {
@@ -1003,7 +1014,7 @@ void LoweringPreparePass::runOnOperation() {
op->walk([&](mlir::Operation *op) {
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp,
- cir::GlobalOp, cir::UnaryOp>(op))
+ cir::FuncOp, cir::GlobalOp, cir::UnaryOp>(op))
opsToTransform.push_back(op);
});
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 26e0ba9b4e45a..f0d73ac872386 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2598,7 +2598,13 @@ void ConvertCIRToLLVMPass::runOnOperation() {
return std::make_pair(ctorAttr.getName(),
ctorAttr.getPriority());
});
- assert(!cir::MissingFeatures::opGlobalDtorList());
+ // Emit the llvm.global_dtors array.
+ buildCtorDtorList(module, cir::CIRDialect::getGlobalDtorsAttrName(),
+ "llvm.global_dtors", [](mlir::Attribute attr) {
+ auto dtorAttr = mlir::cast<cir::GlobalDtorAttr>(attr);
+ return std::make_pair(dtorAttr.getName(),
+ dtorAttr.getPriority());
+ });
}
mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite(
diff --git a/clang/test/CIR/CodeGen/global-ctor-dtor.cpp b/clang/test/CIR/CodeGen/global-ctor-dtor.cpp
new file mode 100644
index 0000000000000..2e03ff3e88c7d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-ctor-dtor.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before.cir
+// RUN: FileCheck --input-file=%t-before.cir %s --check-prefix=CIR-BEFORE-LPP
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR-AFTER
+// RUN: FileCheck --check-prefix=CIR-AFTER --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+extern int bar();
+void foo(void) __attribute__((constructor));
+void foo(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z3foov() global_ctor
+
+void foo2(void) __attribute__((constructor(777)));
+void foo2(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo2v() global_ctor(777)
+
+void foo3(void) __attribute__((destructor));
+void foo3(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo3v() global_dtor
+
+void foo4(void) __attribute__((destructor(789)));
+void foo4(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo4v() global_dtor(789)
+
+// CIR-AFTER: module @{{.*}} attributes {cir.global_ctors = [#cir.global_ctor<"_Z3foov", 65535>, #cir.global_ctor<"_Z4foo2v", 777>], cir.global_dtors = [#cir.global_dtor<"_Z4foo3v", 65535>, #cir.global_dtor<"_Z4foo4v", 789>]
+
+// LLVM: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z3foov, ptr null }, { i32, ptr, ptr } { i32 777, ptr @_Z4foo2v, ptr null }]
+// LLVM: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z4foo3v, ptr null }, { i32, ptr, ptr } { i32 789, ptr @_Z4foo4v, ptr null }]
+
+// OGCG: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z3foov, ptr null }, { i32, ptr, ptr } { i32 777, ptr @_Z4foo2v, ptr null }]
+// OGCG: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z4foo3v, ptr null }, { i32, ptr, ptr } { i32 789, ptr @_Z4foo4v, ptr null }]
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index d7e81849ab74e..6e91898a3b452 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -110,4 +110,36 @@ cir.func builtin @builtin() {
}
// CHECK: cir.func{{.*}} builtin @builtin()
+cir.func @global_ctor_func() global_ctor {
+ cir.return
+}
+
+// CHECK: cir.func @global_ctor_func() global_ctor {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_ctor_with_priority() global_ctor(200) {
+ cir.return
+}
+
+// CHECK: cir.func @global_ctor_with_priority() global_ctor(200) {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_dtor_func() global_dtor {
+ cir.return
+}
+
+// CHECK: cir.func @global_dtor_func() global_dtor {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_dtor_with_priority() global_dtor(201) {
+ cir.return
+}
+
+// CHECK: cir.func @global_dtor_with_priority() global_dtor(201) {
+// CHECK: cir.return
+// CHECK: }
+
}
|
@llvm/pr-subscribers-clang Author: Andy Kaylor (andykaylor) ChangesThis adds support for adding the Full diff: https://github.com/llvm/llvm-project/pull/163247.diff 11 Files Affected:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index bb62223d9e152..610e349717e12 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -814,6 +814,14 @@ def CIR_GlobalCtorAttr : CIR_GlobalCtorDtor<"Ctor", "ctor"> {
}];
}
+def CIR_GlobalDtorAttr : CIR_GlobalCtorDtor<"Dtor", "dtor"> {
+ let summary = "Marks a function as a global destructor";
+ let description = [{
+ Marks a function as a global destructor in the module dtors list.
+ The function will be executed before the module unloading.
+ }];
+}
+
//===----------------------------------------------------------------------===//
// BitfieldInfoAttr
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index feb08d6088125..e91537186df59 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -43,6 +43,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getSideEffectAttrName() { return "side_effect"; }
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
+ static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
void registerAttributes();
void registerTypes();
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 27fe0cc46d7cf..4c15d9ed0f834 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -88,6 +88,19 @@ class CIR_Op<string mnemonic, list<Trait> traits = []> :
code extraLLVMLoweringPatternDecl = "";
}
+//===----------------------------------------------------------------------===//
+// CIR Operation Traits
+//===----------------------------------------------------------------------===//
+
+class HasAtMostOneOfAttrsPred<list<string> names> :
+ CPred<!foldl("0", names, acc, name, acc # " + (" # name # " ? 1 : 0)")
+ # " <= 1">;
+
+class HasAtMostOneOfAttrs<list<string> names> : PredOpTrait<
+ "has only one of the optional attributes: " # !interleave(names, ", "),
+ HasAtMostOneOfAttrsPred<!foreach(name, names, "$" # name)>
+>;
+
//===----------------------------------------------------------------------===//
// CastOp
//===----------------------------------------------------------------------===//
@@ -2422,9 +2435,17 @@ def CIR_GetMemberOp : CIR_Op<"get_member"> {
// TODO(CIR): FuncOp is still a tiny shell of what it will become. Many more
// properties and attributes will be added as upstreaming continues.
+def CIR_OptionalPriorityAttr : OptionalAttr<
+ DefaultValuedAttr<
+ ConfinedAttr<I32Attr, [IntMinValue<101>, IntMaxValue<65535>]>,
+ "65535"
+ >
+>;
+
def CIR_FuncOp : CIR_Op<"func", [
AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
DeclareOpInterfaceMethods<CIRGlobalValueInterface>,
+ HasAtMostOneOfAttrs<["global_ctor_priority", "global_dtor_priority"]>,
IsolatedFromAbove
]> {
let summary = "Declare or define a function";
@@ -2449,6 +2470,12 @@ def CIR_FuncOp : CIR_Op<"func", [
without a prototype and, consequently, may contain calls with invalid
arguments and undefined behavior.
+ The `global_ctor` keyword indicates whether a function should execute before
+ `main()` function, as specified by `__attribute__((constructor))`. An
+ execution priority can also be specified `global_ctor(<priority>)`.
+ Similarly, for global destructors both `global_dtor` and
+ `global_dtor(<priority>)` are available.
+
Example:
```mlir
@@ -2487,7 +2514,9 @@ def CIR_FuncOp : CIR_Op<"func", [
UnitAttr:$comdat,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs,
- OptionalAttr<FlatSymbolRefAttr>:$aliasee);
+ OptionalAttr<FlatSymbolRefAttr>:$aliasee,
+ CIR_OptionalPriorityAttr:$global_ctor_priority,
+ CIR_OptionalPriorityAttr:$global_dtor_priority);
let regions = (region AnyRegion:$body);
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index df82ca138d4b1..4fbae150b587e 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -39,7 +39,6 @@ struct MissingFeatures {
static bool opGlobalUsedOrCompilerUsed() { return false; }
static bool opGlobalAnnotations() { return false; }
static bool opGlobalCtorPriority() { return false; }
- static bool opGlobalDtorList() { return false; }
static bool setDSOLocal() { return false; }
static bool setComdat() { return false; }
@@ -175,6 +174,10 @@ struct MissingFeatures {
static bool atomicScope() { return false; }
static bool atomicSyncScopeID() { return false; }
+ // Global ctor handling
+ static bool globalCtorLexOrder() { return false; }
+ static bool globalCtorAssociatedData() { return false; }
+
// Misc
static bool abiArgInfo() { return false; }
static bool addHeapAllocSiteMetadata() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index fe1ea5617b8cd..82b10515a412f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -451,15 +451,47 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
setNonAliasAttributes(gd, funcOp);
assert(!cir::MissingFeatures::opFuncAttributesForDefinition());
- if (funcDecl->getAttr<ConstructorAttr>())
- errorNYI(funcDecl->getSourceRange(), "constructor attribute");
- if (funcDecl->getAttr<DestructorAttr>())
- errorNYI(funcDecl->getSourceRange(), "destructor attribute");
+ auto getPriority = [this](const auto *attr) -> int {
+ Expr *e = attr->getPriority();
+ if (e)
+ return e->EvaluateKnownConstInt(this->getASTContext()).getExtValue();
+ return attr->DefaultPriority;
+ };
+
+ if (const ConstructorAttr *ca = funcDecl->getAttr<ConstructorAttr>())
+ addGlobalCtor(funcOp, getPriority(ca));
+ if (const DestructorAttr *da = funcDecl->getAttr<DestructorAttr>())
+ addGlobalDtor(funcOp, getPriority(da));
if (funcDecl->getAttr<AnnotateAttr>())
errorNYI(funcDecl->getSourceRange(), "deferredAnnotations");
}
+/// Track functions to be called before main() runs.
+void CIRGenModule::addGlobalCtor(cir::FuncOp ctor,
+ std::optional<int> priority) {
+ assert(!cir::MissingFeatures::globalCtorLexOrder());
+ assert(!cir::MissingFeatures::globalCtorAssociatedData());
+
+ // Traditional LLVM codegen directly adds the function to the list of global
+ // ctors. In CIR we just add a global_ctor attribute to the function. The
+ // global list is created in LoweringPrepare.
+ //
+ // FIXME(from traditional LLVM): Type coercion of void()* types.
+ ctor.setGlobalCtorPriority(priority);
+}
+
+/// Add a function to the list that will be called when the module is unloaded.
+void CIRGenModule::addGlobalDtor(cir::FuncOp dtor,
+ std::optional<int> priority) {
+ if (codeGenOpts.RegisterGlobalDtorsWithAtExit &&
+ (!getASTContext().getTargetInfo().getTriple().isOSAIX()))
+ errorNYI(dtor.getLoc(), "registerGlobalDtorsWithAtExit");
+
+ // FIXME(from traditional LLVM): Type coercion of void()* types.
+ dtor.setGlobalDtorPriority(priority);
+}
+
void CIRGenModule::handleCXXStaticMemberVarInstantiation(VarDecl *vd) {
VarDecl::DefinitionKind dk = vd->isThisDeclarationADefinition();
if (dk == VarDecl::Definition && vd->hasAttr<DLLImportAttr>())
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index f627bae9f87f9..690f0ed0e9bde 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -159,6 +159,13 @@ class CIRGenModule : public CIRGenTypeCache {
bool isConstant = false,
mlir::Operation *insertPoint = nullptr);
+ /// Add a global constructor or destructor to the module.
+ /// The priority is optional, if not specified, the default priority is used.
+ void addGlobalCtor(cir::FuncOp ctor,
+ std::optional<int> priority = std::nullopt);
+ void addGlobalDtor(cir::FuncOp dtor,
+ std::optional<int> priority = std::nullopt);
+
bool shouldZeroInitPadding() const {
// In C23 (N3096) $6.7.10:
// """
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 5f88590c48d30..12837d953d677 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -15,6 +15,7 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "mlir/IR/DialectImplementation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/FunctionImplementation.h"
#include "mlir/Support/LLVM.h"
@@ -1720,6 +1721,43 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
hasAlias = true;
}
+ auto parseGlobalDtorCtor =
+ [&](StringRef keyword,
+ llvm::function_ref<void(std::optional<int> prio)> createAttr)
+ -> mlir::LogicalResult {
+ if (mlir::succeeded(parser.parseOptionalKeyword(keyword))) {
+ std::optional<int> priority;
+ if (mlir::succeeded(parser.parseOptionalLParen())) {
+ auto parsedPriority = mlir::FieldParser<int>::parse(parser);
+ if (mlir::failed(parsedPriority))
+ return parser.emitError(parser.getCurrentLocation(),
+ "failed to parse 'priority', of type 'int'");
+ priority = parsedPriority.value_or(int());
+ // Parse literal ')'
+ if (parser.parseRParen())
+ return failure();
+ }
+ createAttr(priority);
+ }
+ return success();
+ };
+
+ if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
+ mlir::IntegerAttr globalCtorPriorityAttr =
+ builder.getI32IntegerAttr(priority.value_or(65535));
+ state.addAttribute(getGlobalCtorPriorityAttrName(state.name),
+ globalCtorPriorityAttr);
+ }).failed())
+ return failure();
+
+ if (parseGlobalDtorCtor("global_dtor", [&](std::optional<int> priority) {
+ mlir::IntegerAttr globalDtorPriorityAttr =
+ builder.getI32IntegerAttr(priority.value_or(65535));
+ state.addAttribute(getGlobalDtorPriorityAttrName(state.name),
+ globalDtorPriorityAttr);
+ }).failed())
+ return failure();
+
// Parse the optional function body.
auto *body = state.addRegion();
OptionalParseResult parseResult = parser.parseOptionalRegion(
@@ -1801,6 +1839,18 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
p << ")";
}
+ if (auto globalCtorPriority = getGlobalCtorPriority()) {
+ p << " global_ctor";
+ if (globalCtorPriority.value() != 65535)
+ p << "(" << globalCtorPriority.value() << ")";
+ }
+
+ if (auto globalDtorPriority = getGlobalDtorPriority()) {
+ p << " global_dtor";
+ if (globalDtorPriority.value() != 65535)
+ p << "(" << globalDtorPriority.value() << ")";
+ }
+
// Print the body if this is not an external function.
Region &body = getOperation()->getRegion(0);
if (!body.empty()) {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index dbff0b98fbe42..d99c36241ab68 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -105,6 +105,8 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
/// List of ctors and their priorities to be called before main()
llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
+ /// List of dtors and their priorities to be called when unloading module.
+ llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList;
void setASTContext(clang::ASTContext *c) {
astCtx = c;
@@ -823,10 +825,13 @@ void LoweringPreparePass::buildGlobalCtorDtorList() {
mlir::ArrayAttr::get(&getContext(), globalCtors));
}
- // We will eventual need to populate a global_dtor list, but that's not
- // needed for globals with destructors. It will only be needed for functions
- // that are marked as global destructors with an attribute.
- assert(!cir::MissingFeatures::opGlobalDtorList());
+ if (!globalDtorList.empty()) {
+ llvm::SmallVector<mlir::Attribute> globalDtors =
+ prepareCtorDtorAttrList<cir::GlobalDtorAttr>(&getContext(),
+ globalDtorList);
+ mlirModule->setAttr(cir::CIRDialect::getGlobalDtorsAttrName(),
+ mlir::ArrayAttr::get(&getContext(), globalDtors));
+ }
}
void LoweringPreparePass::buildCXXGlobalInitFunc() {
@@ -975,22 +980,28 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
}
void LoweringPreparePass::runOnOp(mlir::Operation *op) {
- if (auto arrayCtor = dyn_cast<ArrayCtor>(op))
+ if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
lowerArrayCtor(arrayCtor);
- else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op))
+ } else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) {
lowerArrayDtor(arrayDtor);
- else if (auto cast = mlir::dyn_cast<cir::CastOp>(op))
+ } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
lowerCastOp(cast);
- else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op))
+ } else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op)) {
lowerComplexDivOp(complexDiv);
- else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op))
+ } else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op)) {
lowerComplexMulOp(complexMul);
- else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op))
+ } else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) {
lowerGlobalOp(glob);
- else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op))
+ } else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op)) {
lowerDynamicCastOp(dynamicCast);
- else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op))
+ } else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op)) {
lowerUnaryOp(unary);
+ } else if (auto fnOp = dyn_cast<cir::FuncOp>(op)) {
+ if (auto globalCtor = fnOp.getGlobalCtorPriority())
+ globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
+ else if (auto globalDtor = fnOp.getGlobalDtorPriority())
+ globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
+ }
}
void LoweringPreparePass::runOnOperation() {
@@ -1003,7 +1014,7 @@ void LoweringPreparePass::runOnOperation() {
op->walk([&](mlir::Operation *op) {
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp,
- cir::GlobalOp, cir::UnaryOp>(op))
+ cir::FuncOp, cir::GlobalOp, cir::UnaryOp>(op))
opsToTransform.push_back(op);
});
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 26e0ba9b4e45a..f0d73ac872386 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2598,7 +2598,13 @@ void ConvertCIRToLLVMPass::runOnOperation() {
return std::make_pair(ctorAttr.getName(),
ctorAttr.getPriority());
});
- assert(!cir::MissingFeatures::opGlobalDtorList());
+ // Emit the llvm.global_dtors array.
+ buildCtorDtorList(module, cir::CIRDialect::getGlobalDtorsAttrName(),
+ "llvm.global_dtors", [](mlir::Attribute attr) {
+ auto dtorAttr = mlir::cast<cir::GlobalDtorAttr>(attr);
+ return std::make_pair(dtorAttr.getName(),
+ dtorAttr.getPriority());
+ });
}
mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite(
diff --git a/clang/test/CIR/CodeGen/global-ctor-dtor.cpp b/clang/test/CIR/CodeGen/global-ctor-dtor.cpp
new file mode 100644
index 0000000000000..2e03ff3e88c7d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-ctor-dtor.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before.cir
+// RUN: FileCheck --input-file=%t-before.cir %s --check-prefix=CIR-BEFORE-LPP
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR-AFTER
+// RUN: FileCheck --check-prefix=CIR-AFTER --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+extern int bar();
+void foo(void) __attribute__((constructor));
+void foo(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z3foov() global_ctor
+
+void foo2(void) __attribute__((constructor(777)));
+void foo2(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo2v() global_ctor(777)
+
+void foo3(void) __attribute__((destructor));
+void foo3(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo3v() global_dtor
+
+void foo4(void) __attribute__((destructor(789)));
+void foo4(void) {
+ bar();
+}
+
+// CIR-BEFORE-LPP: cir.func dso_local @_Z4foo4v() global_dtor(789)
+
+// CIR-AFTER: module @{{.*}} attributes {cir.global_ctors = [#cir.global_ctor<"_Z3foov", 65535>, #cir.global_ctor<"_Z4foo2v", 777>], cir.global_dtors = [#cir.global_dtor<"_Z4foo3v", 65535>, #cir.global_dtor<"_Z4foo4v", 789>]
+
+// LLVM: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z3foov, ptr null }, { i32, ptr, ptr } { i32 777, ptr @_Z4foo2v, ptr null }]
+// LLVM: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z4foo3v, ptr null }, { i32, ptr, ptr } { i32 789, ptr @_Z4foo4v, ptr null }]
+
+// OGCG: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z3foov, ptr null }, { i32, ptr, ptr } { i32 777, ptr @_Z4foo2v, ptr null }]
+// OGCG: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z4foo3v, ptr null }, { i32, ptr, ptr } { i32 789, ptr @_Z4foo4v, ptr null }]
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index d7e81849ab74e..6e91898a3b452 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -110,4 +110,36 @@ cir.func builtin @builtin() {
}
// CHECK: cir.func{{.*}} builtin @builtin()
+cir.func @global_ctor_func() global_ctor {
+ cir.return
+}
+
+// CHECK: cir.func @global_ctor_func() global_ctor {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_ctor_with_priority() global_ctor(200) {
+ cir.return
+}
+
+// CHECK: cir.func @global_ctor_with_priority() global_ctor(200) {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_dtor_func() global_dtor {
+ cir.return
+}
+
+// CHECK: cir.func @global_dtor_func() global_dtor {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @global_dtor_with_priority() global_dtor(201) {
+ cir.return
+}
+
+// CHECK: cir.func @global_dtor_with_priority() global_dtor(201) {
+// CHECK: cir.return
+// CHECK: }
+
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
This adds support for adding the `global_ctor` or `global_dtor` attribute to the CIR representation of functions defined with `__attribute__((constructor))` or `__attribute__((destructor))` and adding them to the `@llvm.global_ctors` or `@llvm.global_dtors` list during lowering to LLVM IR.
This adds support for adding the
global_ctor
orglobal_dtor
attribute to the CIR representation of functions defined with__attribute__((constructor))
or__attribute__((destructor))
and adding them to the@llvm.global_ctors
or@llvm.global_dtors
list during lowering to LLVM IR.