[clang] eb1ce38 - [CIR] Finish global ctor init lowering (#162130)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 8 10:42:58 PDT 2025
Author: Andy Kaylor
Date: 2025-10-08T10:42:53-07:00
New Revision: eb1ce38fd1fc750080a0ee5fe1e16dd457d9c437
URL: https://github.com/llvm/llvm-project/commit/eb1ce38fd1fc750080a0ee5fe1e16dd457d9c437
DIFF: https://github.com/llvm/llvm-project/commit/eb1ce38fd1fc750080a0ee5fe1e16dd457d9c437.diff
LOG: [CIR] Finish global ctor init lowering (#162130)
Implement support for creating a global ctor list during the
cir::LoweringPrepare pass, and add tests for lowering global
constructor-based initialization to LLVM IR.
Added:
Modified:
clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
clang/include/clang/CIR/Dialect/IR/CIRDialect.td
clang/include/clang/CIR/MissingFeatures.h
clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
clang/test/CIR/CodeGen/global-init.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 43832b7c88063..bb62223d9e152 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -769,6 +769,51 @@ def CIR_VisibilityAttr : CIR_EnumAttr<CIR_VisibilityKind, "visibility"> {
}];
}
+//===----------------------------------------------------------------------===//
+// GloblCtorAttr
+//===----------------------------------------------------------------------===//
+
+class CIR_GlobalCtorDtor<string name, string attrMnemonic>
+ : CIR_Attr<"Global" # name, "global_" # attrMnemonic> {
+ let parameters = (ins "mlir::StringAttr":$name, "int":$priority);
+
+ let skipDefaultBuilders = 1;
+ let builders = [
+ AttrBuilder<(ins
+ "llvm::StringRef":$name,
+ CArg<"int", "65535">:$priority), [{
+ return $_get($_ctxt, mlir::StringAttr::get($_ctxt, name), priority);
+ }]>,
+ AttrBuilderWithInferredContext<(ins
+ "mlir::StringAttr":$name,
+ CArg<"int", "65535">:$priority), [{
+ return $_get(name.getContext(), name, priority);
+ }]>
+ ];
+
+ let assemblyFormat = [{
+ `<` $name `,` $priority `>`
+ }];
+
+ let extraClassDeclaration = [{
+ bool isDefaultPriority() const {
+ return getPriority() == getDefaultPriority();
+ };
+
+ static int getDefaultPriority() {
+ return 65535;
+ }
+ }];
+}
+
+def CIR_GlobalCtorAttr : CIR_GlobalCtorDtor<"Ctor", "ctor"> {
+ let summary = "Marks a function as a global constructor";
+ let description = [{
+ Marks the function as a global constructor in the module's constructor list.
+ It will be executed before main() is called.
+ }];
+}
+
//===----------------------------------------------------------------------===//
// BitfieldInfoAttr
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 15d5fa0b4753e..feb08d6088125 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -42,6 +42,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
static llvm::StringRef getSideEffectAttrName() { return "side_effect"; }
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
+ static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
void registerAttributes();
void registerTypes();
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index f79580083cf9f..3b7b130ebc973 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 opGlobalDtorLowering() { return false; }
- static bool opGlobalCtorAttr() { return false; }
static bool opGlobalCtorPriority() { return false; }
static bool opGlobalCtorList() { return false; }
static bool setDSOLocal() { return false; }
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 2eeef819dc3fc..bc917d0604668 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -61,6 +61,9 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
/// Build a module init function that calls all the dynamic initializers.
void buildCXXGlobalInitFunc();
+ /// Materialize global ctor/dtor list
+ void buildGlobalCtorDtorList();
+
cir::FuncOp buildRuntimeFunction(
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
cir::FuncType type,
@@ -79,6 +82,9 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
llvm::StringMap<uint32_t> dynamicInitializerNames;
llvm::SmallVector<cir::FuncOp> dynamicInitializers;
+ /// List of ctors and their priorities to be called before main()
+ llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
+
void setASTContext(clang::ASTContext *c) { astCtx = c; }
};
@@ -689,11 +695,36 @@ void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
assert(!cir::MissingFeatures::opGlobalAnnotations());
}
+template <typename AttributeTy>
+static llvm::SmallVector<mlir::Attribute>
+prepareCtorDtorAttrList(mlir::MLIRContext *context,
+ llvm::ArrayRef<std::pair<std::string, uint32_t>> list) {
+ llvm::SmallVector<mlir::Attribute> attrs;
+ for (const auto &[name, priority] : list)
+ attrs.push_back(AttributeTy::get(context, name, priority));
+ return attrs;
+}
+
+void LoweringPreparePass::buildGlobalCtorDtorList() {
+ if (!globalCtorList.empty()) {
+ llvm::SmallVector<mlir::Attribute> globalCtors =
+ prepareCtorDtorAttrList<cir::GlobalCtorAttr>(&getContext(),
+ globalCtorList);
+
+ mlirModule->setAttr(cir::CIRDialect::getGlobalCtorsAttrName(),
+ mlir::ArrayAttr::get(&getContext(), globalCtors));
+ }
+
+ assert(!cir::MissingFeatures::opGlobalDtorLowering());
+}
+
void LoweringPreparePass::buildCXXGlobalInitFunc() {
if (dynamicInitializers.empty())
return;
- assert(!cir::MissingFeatures::opGlobalCtorList());
+ // TODO: handle globals with a user-specified initialzation priority.
+ // TODO: handle default priority more nicely.
+ assert(!cir::MissingFeatures::opGlobalCtorPriority());
SmallString<256> fnName;
// Include the filename in the symbol name. Including "sub_" matches gcc
@@ -722,6 +753,10 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() {
builder.setInsertionPointToStart(f.addEntryBlock());
for (cir::FuncOp &f : dynamicInitializers)
builder.createCallOp(f.getLoc(), f, {});
+ // Add the global init function (not the individual ctor functions) to the
+ // global ctor list.
+ globalCtorList.emplace_back(fnName,
+ cir::GlobalCtorAttr::getDefaultPriority());
cir::ReturnOp::create(builder, f.getLoc());
}
@@ -852,6 +887,7 @@ void LoweringPreparePass::runOnOperation() {
runOnOp(o);
buildCXXGlobalInitFunc();
+ buildGlobalCtorDtorList();
}
std::unique_ptr<Pass> mlir::createLoweringPreparePass() {
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index e9649afaa6a8d..a80a2959deb8d 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2413,6 +2413,73 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
});
}
+static void buildCtorDtorList(
+ mlir::ModuleOp module, StringRef globalXtorName, StringRef llvmXtorName,
+ llvm::function_ref<std::pair<StringRef, int>(mlir::Attribute)> createXtor) {
+ llvm::SmallVector<std::pair<StringRef, int>> globalXtors;
+ for (const mlir::NamedAttribute namedAttr : module->getAttrs()) {
+ if (namedAttr.getName() == globalXtorName) {
+ for (auto attr : mlir::cast<mlir::ArrayAttr>(namedAttr.getValue()))
+ globalXtors.emplace_back(createXtor(attr));
+ break;
+ }
+ }
+
+ if (globalXtors.empty())
+ return;
+
+ mlir::OpBuilder builder(module.getContext());
+ builder.setInsertionPointToEnd(&module.getBodyRegion().back());
+
+ // Create a global array llvm.global_ctors with element type of
+ // struct { i32, ptr, ptr }
+ auto ctorPFTy = mlir::LLVM::LLVMPointerType::get(builder.getContext());
+ llvm::SmallVector<mlir::Type> ctorStructFields;
+ ctorStructFields.push_back(builder.getI32Type());
+ ctorStructFields.push_back(ctorPFTy);
+ ctorStructFields.push_back(ctorPFTy);
+
+ auto ctorStructTy = mlir::LLVM::LLVMStructType::getLiteral(
+ builder.getContext(), ctorStructFields);
+ auto ctorStructArrayTy =
+ mlir::LLVM::LLVMArrayType::get(ctorStructTy, globalXtors.size());
+
+ mlir::Location loc = module.getLoc();
+ auto newGlobalOp = mlir::LLVM::GlobalOp::create(
+ builder, loc, ctorStructArrayTy, /*constant=*/false,
+ mlir::LLVM::Linkage::Appending, llvmXtorName, mlir::Attribute());
+
+ builder.createBlock(&newGlobalOp.getRegion());
+ builder.setInsertionPointToEnd(newGlobalOp.getInitializerBlock());
+
+ mlir::Value result =
+ mlir::LLVM::UndefOp::create(builder, loc, ctorStructArrayTy);
+
+ for (auto [index, fn] : llvm::enumerate(globalXtors)) {
+ mlir::Value structInit =
+ mlir::LLVM::UndefOp::create(builder, loc, ctorStructTy);
+ mlir::Value initPriority = mlir::LLVM::ConstantOp::create(
+ builder, loc, ctorStructFields[0], fn.second);
+ mlir::Value initFuncAddr = mlir::LLVM::AddressOfOp::create(
+ builder, loc, ctorStructFields[1], fn.first);
+ mlir::Value initAssociate =
+ mlir::LLVM::ZeroOp::create(builder, loc, ctorStructFields[2]);
+ // Literal zero makes the InsertValueOp::create ambiguous.
+ llvm::SmallVector<int64_t> zero{0};
+ structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
+ initPriority, zero);
+ structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
+ initFuncAddr, 1);
+ // TODO: handle associated data for initializers.
+ structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
+ initAssociate, 2);
+ result = mlir::LLVM::InsertValueOp::create(builder, loc, result, structInit,
+ index);
+ }
+
+ builder.create<mlir::LLVM::ReturnOp>(loc, result);
+}
+
// The applyPartialConversion function traverses blocks in the dominance order,
// so it does not lower and operations that are not reachachable from the
// operations passed in as arguments. Since we do need to lower such code in
@@ -2519,6 +2586,14 @@ void ConvertCIRToLLVMPass::runOnOperation() {
if (failed(applyPartialConversion(ops, target, std::move(patterns))))
signalPassFailure();
+
+ // Emit the llvm.global_ctors array.
+ buildCtorDtorList(module, cir::CIRDialect::getGlobalCtorsAttrName(),
+ "llvm.global_ctors", [](mlir::Attribute attr) {
+ auto ctorAttr = mlir::cast<cir::GlobalCtorAttr>(attr);
+ return std::make_pair(ctorAttr.getName(),
+ ctorAttr.getPriority());
+ });
}
mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite(
diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp
index 0c19e686b2493..2afb5a5af01a0 100644
--- a/clang/test/CIR/CodeGen/global-init.cpp
+++ b/clang/test/CIR/CodeGen/global-init.cpp
@@ -1,9 +1,10 @@
// RUN: %clang_cc1 -std=c++17 -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
-
-// Note: The LoweringPrepare work isn't yet complete. We still need to create
-// the global ctor list attribute.
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
struct NeedsCtor {
NeedsCtor();
@@ -15,6 +16,9 @@ NeedsCtor needsCtor;
// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr<!rec_NeedsCtor>
// CIR-BEFORE-LPP: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr<!rec_NeedsCtor>) -> ()
+// CIR: module @{{.*}} attributes {
+// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"_GLOBAL__sub_I_[[FILENAME:.*]]", 65535>]
+
// CIR: cir.global external @needsCtor = #cir.zero : !rec_NeedsCtor
// CIR: cir.func internal private @__cxx_global_var_init() {
// CIR: %0 = cir.get_global @needsCtor : !cir.ptr<!rec_NeedsCtor>
@@ -24,3 +28,22 @@ NeedsCtor needsCtor;
// CIR: cir.call @__cxx_global_var_init() : () -> ()
// CIR: cir.return
// CIR: }
+
+// LLVM: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
+// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
+// LLVM: declare void @_ZN9NeedsCtorC1Ev(ptr)
+
+// LLVM: define internal void @__cxx_global_var_init()
+// LLVM: call void @_ZN9NeedsCtorC1Ev(ptr @needsCtor)
+
+// LLVM: define void @_GLOBAL__sub_I_[[FILENAME]]()
+// LLVM: call void @__cxx_global_var_init()
+
+// OGCG: @needsCtor = global %struct.NeedsCtor zeroinitializer, align 1
+// OGCG: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_[[FILENAME:.*]], ptr null }]
+
+// OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" {
+// OGCG: call void @_ZN9NeedsCtorC1Ev(ptr noundef nonnull align 1 dereferenceable(1) @needsCtor)
+
+// OGCG: define internal void @_GLOBAL__sub_I_[[FILENAME]]() {{.*}} section ".text.startup" {
+// OGCG: call void @__cxx_global_var_init()
More information about the cfe-commits
mailing list