From bc2fc8dc65eb0eaa483ba161199c445ea16b30b5 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Thu, 25 Sep 2025 18:25:13 -0700 Subject: [PATCH 1/3] [CIR] Implement initial LoweringPrepare support for global ctors This adds the initial support for lowering the 'ctor' region of cir.global operations to an init function which is called from a TU-specific static initialization function. This does not yet add an attribute to hold a list of global initializers. That will be added in a future change. --- clang/include/clang/CIR/Dialect/IR/CIRAttrs.h | 4 + clang/include/clang/CIR/MissingFeatures.h | 5 + .../Dialect/Transforms/LoweringPrepare.cpp | 146 +++++++++++++++++- clang/test/CIR/CodeGen/global-init.cpp | 26 +++- 4 files changed, 173 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h index 925a9a87e267f..d447354a1ad47 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h @@ -20,6 +20,10 @@ #include "clang/CIR/Interfaces/CIRTypeInterfaces.h" +namespace cir { +inline constexpr uint32_t DefaultGlobalCtorDtorPriority = 65535; +} // namespace cir + //===----------------------------------------------------------------------===// // CIR Dialect Attrs //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 7a6c084f51cd7..a4dfc051e9953 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -37,6 +37,11 @@ struct MissingFeatures { static bool opGlobalDLLImportExport() { return false; } static bool opGlobalPartition() { return false; } 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; } static bool setComdat() { return false; } diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index c15637d297cd1..d7645bebc25ed 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -8,18 +8,39 @@ #include "PassDetail.h" #include "clang/AST/ASTContext.h" +#include "clang/Basic/Module.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/IR/CIROpsEnums.h" #include "clang/CIR/Dialect/Passes.h" #include "clang/CIR/MissingFeatures.h" +#include "llvm/Support/Path.h" #include using namespace mlir; using namespace cir; +static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) { + SmallString<128> fileName; + + if (mlirModule.getSymName()) + fileName = llvm::sys::path::filename(mlirModule.getSymName()->str()); + + if (fileName.empty()) + fileName = ""; + + for (size_t i = 0; i < fileName.size(); ++i) { + // Replace everything that's not [a-zA-Z0-9._] with a _. This set happens + // to be the set of C preprocessing numbers. + if (!clang::isPreprocessingNumberBody(fileName[i])) + fileName[i] = '_'; + } + + return fileName; +} + namespace { struct LoweringPreparePass : public LoweringPrepareBase { LoweringPreparePass() = default; @@ -30,9 +51,16 @@ struct LoweringPreparePass : public LoweringPrepareBase { void lowerComplexDivOp(cir::ComplexDivOp op); void lowerComplexMulOp(cir::ComplexMulOp op); void lowerUnaryOp(cir::UnaryOp op); + void lowerGlobalOp(cir::GlobalOp op); void lowerArrayDtor(cir::ArrayDtor op); void lowerArrayCtor(cir::ArrayCtor op); + /// Build the function that initializes the specified global + cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op); + + /// Build a module init function that calls all the dynamic initializers. + void buildCXXGlobalInitFunc(); + cir::FuncOp buildRuntimeFunction( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, cir::FuncType type, @@ -47,6 +75,10 @@ struct LoweringPreparePass : public LoweringPrepareBase { /// Tracks current module. mlir::ModuleOp mlirModule; + /// Tracks existing dynamic initializers. + llvm::StringMap dynamicInitializerNames; + llvm::SmallVector dynamicInitializers; + void setASTContext(clang::ASTContext *c) { astCtx = c; } }; @@ -589,6 +621,113 @@ void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) { op.erase(); } +cir::FuncOp +LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { + // TODO(cir): Store this in the GlobalOp. + // This should come from the MangleContext, but for now I'm hardcoding it. + SmallString<256> fnName("__cxx_global_var_init"); + // Get a unique name + uint32_t cnt = dynamicInitializerNames[fnName]++; + if (cnt) + fnName += "." + llvm::Twine(cnt).str(); + + // Create a variable initialization function. + CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointAfter(op); + auto voidTy = cir::VoidType::get(builder.getContext()); + auto fnType = cir::FuncType::get({}, voidTy); + FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, + cir::GlobalLinkageKind::InternalLinkage); + + // Move over the initialzation code of the ctor region. + mlir::Block *entryBB = f.addEntryBlock(); + if (!op.getCtorRegion().empty()) { + mlir::Block &block = op.getCtorRegion().front(); + entryBB->getOperations().splice(entryBB->begin(), block.getOperations(), + block.begin(), std::prev(block.end())); + } + + // Register the destructor call with __cxa_atexit + auto &dtorRegion = op.getDtorRegion(); + if (!dtorRegion.empty()) { + assert(!cir::MissingFeatures::opGlobalDtorLowering()); + llvm_unreachable("dtor region lowering is NYI"); + } + + // Replace cir.yield with cir.return + builder.setInsertionPointToEnd(entryBB); + mlir::Operation *yieldOp = nullptr; + if (!op.getCtorRegion().empty()) { + mlir::Block &block = op.getCtorRegion().front(); + yieldOp = &block.getOperations().back(); + } else { + assert(!cir::MissingFeatures::opGlobalDtorLowering()); + llvm_unreachable("dtor region lowering is NYI"); + } + + assert(isa(*yieldOp)); + builder.create(yieldOp->getLoc()); + return f; +} + +void LoweringPreparePass::lowerGlobalOp(GlobalOp op) { + mlir::Region &ctorRegion = op.getCtorRegion(); + mlir::Region &dtorRegion = op.getDtorRegion(); + + if (!ctorRegion.empty() || !dtorRegion.empty()) { + // Build a variable initialization function and move the initialzation code + // in the ctor region over. + cir::FuncOp f = buildCXXGlobalVarDeclInitFunc(op); + + // Clear the ctor and dtor region + ctorRegion.getBlocks().clear(); + dtorRegion.getBlocks().clear(); + + assert(!cir::MissingFeatures::astVarDeclInterface()); + dynamicInitializers.push_back(f); + } + + assert(!cir::MissingFeatures::opGlobalAnnotations()); +} + +void LoweringPreparePass::buildCXXGlobalInitFunc() { + if (dynamicInitializers.empty()) + return; + + assert(!cir::MissingFeatures::opGlobalCtorList()); + + SmallString<256> fnName; + // Include the filename in the symbol name. Including "sub_" matches gcc + // and makes sure these symbols appear lexicographically behind the symbols + // with priority (TBD). Module implementation units behave the same + // way as a non-modular TU with imports. + // TODO: check CXX20ModuleInits + if (astCtx->getCurrentNamedModule() && + !astCtx->getCurrentNamedModule()->isModuleImplementation()) { + llvm::raw_svector_ostream out(fnName); + std::unique_ptr mangleCtx( + astCtx->createMangleContext()); + cast(*mangleCtx) + .mangleModuleInitializer(astCtx->getCurrentNamedModule(), out); + } else { + fnName += "_GLOBAL__sub_I_"; + fnName += getTransformedFileName(mlirModule); + } + + CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); + auto fnType = + cir::FuncType::get({}, cir::VoidType::get(builder.getContext())); + cir::FuncOp f = + buildRuntimeFunction(builder, fnName, mlirModule.getLoc(), fnType, + cir::GlobalLinkageKind::ExternalLinkage); + builder.setInsertionPointToStart(f.addEntryBlock()); + for (cir::FuncOp &f : dynamicInitializers) + builder.createCallOp(f.getLoc(), f, {}); + + builder.create(f.getLoc()); +} + static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, clang::ASTContext *astCtx, mlir::Operation *op, mlir::Type eltTy, @@ -691,6 +830,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) { lowerComplexDivOp(complexDiv); else if (auto complexMul = mlir::dyn_cast(op)) lowerComplexMulOp(complexMul); + else if (auto glob = mlir::dyn_cast(op)) + lowerGlobalOp(glob); else if (auto unary = mlir::dyn_cast(op)) lowerUnaryOp(unary); } @@ -704,12 +845,15 @@ void LoweringPreparePass::runOnOperation() { op->walk([&](mlir::Operation *op) { if (mlir::isa(op)) + cir::ComplexMulOp, cir::ComplexDivOp, cir::GlobalOp, + cir::UnaryOp>(op)) opsToTransform.push_back(op); }); for (mlir::Operation *o : opsToTransform) runOnOp(o); + + buildCXXGlobalInitFunc(); } std::unique_ptr mlir::createLoweringPreparePass() { diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp index 102affc5563ac..d70bad37f22b9 100644 --- a/clang/test/CIR/CodeGen/global-init.cpp +++ b/clang/test/CIR/CodeGen/global-init.cpp @@ -1,8 +1,9 @@ -// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// 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 CIR generated from this test isn't ready for lowering to LLVM yet. -// That will require changes to LoweringPrepare. +// Note: The LoweringPrepare work isn't yet complete. We still need to create +// the global ctor list attribute. struct NeedsCtor { NeedsCtor(); @@ -10,8 +11,19 @@ struct NeedsCtor { NeedsCtor needsCtor; -// CIR: cir.func private @_ZN9NeedsCtorC1Ev(!cir.ptr) -// CIR: cir.global external @needsCtor = ctor : !rec_NeedsCtor { -// CIR: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr -// CIR: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () +// CIR-BEFORE-LPP: cir.func private @_ZN9NeedsCtorC1Ev(!cir.ptr) +// CIR-BEFORE-LPP: cir.global external @needsCtor = ctor : !rec_NeedsCtor { +// CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr +// CIR-BEFORE-LPP: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () +// CIR-BEFORE-LPP: } + +// 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 +// CIR: cir.call @_ZN9NeedsCtorC1Ev(%0) : (!cir.ptr) -> () +// CIR: cir.return +// CIR: } +// CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() { +// CIR: cir.call @__cxx_global_var_init() : () -> () +// CIR: cir.return // CIR: } From b4330a950eac79b860f60ce19f583e2a48ff8439 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Wed, 1 Oct 2025 16:24:13 -0700 Subject: [PATCH 2/3] Remove some unnecessary checks from the test --- clang/test/CIR/CodeGen/global-init.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp index d70bad37f22b9..0c19e686b2493 100644 --- a/clang/test/CIR/CodeGen/global-init.cpp +++ b/clang/test/CIR/CodeGen/global-init.cpp @@ -11,18 +11,15 @@ struct NeedsCtor { NeedsCtor needsCtor; -// CIR-BEFORE-LPP: cir.func private @_ZN9NeedsCtorC1Ev(!cir.ptr) // CIR-BEFORE-LPP: cir.global external @needsCtor = ctor : !rec_NeedsCtor { // CIR-BEFORE-LPP: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr // CIR-BEFORE-LPP: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () -// CIR-BEFORE-LPP: } // 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 // CIR: cir.call @_ZN9NeedsCtorC1Ev(%0) : (!cir.ptr) -> () -// CIR: cir.return -// CIR: } + // CIR: cir.func private @_GLOBAL__sub_I_[[FILENAME:.*]]() { // CIR: cir.call @__cxx_global_var_init() : () -> () // CIR: cir.return From bc4826d0ff2b9b81dbf89c3a02c9510ec6e29022 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Fri, 3 Oct 2025 17:02:53 -0700 Subject: [PATCH 3/3] Apply review feedback --- .../clang/CIR/Dialect/Builder/CIRBaseBuilder.h | 1 + clang/include/clang/CIR/Dialect/IR/CIRAttrs.h | 4 ---- clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp | 12 +++++------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 3f83c302176c0..f5371e97bc661 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -124,6 +124,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { cir::ConstantOp getTrue(mlir::Location loc) { return getBool(true, loc); } cir::BoolType getBoolTy() { return cir::BoolType::get(getContext()); } + cir::VoidType getVoidTy() { return cir::VoidType::get(getContext()); } cir::PointerType getPointerTo(mlir::Type ty) { return cir::PointerType::get(ty); diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h index d447354a1ad47..925a9a87e267f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h @@ -20,10 +20,6 @@ #include "clang/CIR/Interfaces/CIRTypeInterfaces.h" -namespace cir { -inline constexpr uint32_t DefaultGlobalCtorDtorPriority = 65535; -} // namespace cir - //===----------------------------------------------------------------------===// // CIR Dialect Attrs //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index d7645bebc25ed..2eeef819dc3fc 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -634,8 +634,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { // Create a variable initialization function. CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); - auto voidTy = cir::VoidType::get(builder.getContext()); - auto fnType = cir::FuncType::get({}, voidTy); + auto fnType = cir::FuncType::get({}, builder.getVoidTy()); FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, cir::GlobalLinkageKind::InternalLinkage); @@ -648,7 +647,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { } // Register the destructor call with __cxa_atexit - auto &dtorRegion = op.getDtorRegion(); + mlir::Region &dtorRegion = op.getDtorRegion(); if (!dtorRegion.empty()) { assert(!cir::MissingFeatures::opGlobalDtorLowering()); llvm_unreachable("dtor region lowering is NYI"); @@ -666,7 +665,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { } assert(isa(*yieldOp)); - builder.create(yieldOp->getLoc()); + cir::ReturnOp::create(builder, yieldOp->getLoc()); return f; } @@ -716,8 +715,7 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); - auto fnType = - cir::FuncType::get({}, cir::VoidType::get(builder.getContext())); + auto fnType = cir::FuncType::get({}, builder.getVoidTy()); cir::FuncOp f = buildRuntimeFunction(builder, fnName, mlirModule.getLoc(), fnType, cir::GlobalLinkageKind::ExternalLinkage); @@ -725,7 +723,7 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { for (cir::FuncOp &f : dynamicInitializers) builder.createCallOp(f.getLoc(), f, {}); - builder.create(f.getLoc()); + cir::ReturnOp::create(builder, f.getLoc()); } static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,