https://github.com/bcardosolopes updated 
https://github.com/llvm/llvm-project/pull/179828

>From 1324b35c85d9d4cc5425fa6eda7183e0cdb2a30b Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <[email protected]>
Date: Fri, 30 Jan 2026 22:49:28 -0800
Subject: [PATCH] [CIR][LoweringPrepare] Emit guard variables for static local
 initialization

This implements the lowering of static local variables with the Itanium C++ ABI
guard variable pattern in LoweringPrepare.

When a GlobalOp has the static_local attribute and a ctor region, this pass:
1. Creates a guard variable global (mangled name from AST)
2. Inserts the guard check pattern at each GetGlobalOp use site:
   - Load guard byte with acquire ordering
   - If zero, call __cxa_guard_acquire
   - If acquire returns non-zero, inline the ctor region code
   - Call __cxa_guard_release
3. Clears the static_local attribute and ctor region from the GlobalOp
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |  20 +
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   7 +-
 .../clang/CIR/Interfaces/ASTAttrInterfaces.h  |   2 -
 .../clang/CIR/Interfaces/ASTAttrInterfaces.td |  12 -
 clang/include/clang/CIR/MissingFeatures.h     |   1 +
 clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp       |  23 +-
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   7 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  10 +-
 .../Dialect/Transforms/LoweringPrepare.cpp    | 407 +++++++++++++++++-
 clang/test/CIR/CodeGen/static-local.cpp       |  37 +-
 clang/test/CIR/IR/invalid-static-local.cir    |   6 +-
 clang/test/CIR/IR/static-local.cir            |   6 +-
 12 files changed, 501 insertions(+), 37 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 47a0b3394c48e..15c2c6e034af9 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1288,6 +1288,26 @@ def CIR_SideEffect : CIR_I32EnumAttr<
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// StaticLocalGuardAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_StaticLocalGuardAttr : CIR_Attr<"StaticLocalGuard",
+                                         "static_local_guard"> {
+  let summary = "Guard variable name for static local variables";
+  let description = [{
+    Contains the mangled guard variable name for static local variable
+    initialization.
+
+    Example:
+    ```mlir
+    cir.global internal static_local_guard<"_ZGVZ3foovE1x"> @_ZZ3foovE1x = ...
+    ```
+  }];
+  let parameters = (ins "mlir::StringAttr":$name);
+  let assemblyFormat = "`<` $name `>`";
+}
+
 
//===----------------------------------------------------------------------===//
 // AST Wrappers
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 25b09c9eb4ed4..f2c1578235107 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2386,9 +2386,10 @@ def CIR_GlobalOp : CIR_Op<"global", [
     Symbol visibility in `sym_visibility` is defined in terms of MLIR's 
visibility
     and verified to be in accordance to `linkage`.
 
-    The `static_local` attribute indicates that this global represents a
+    The `static_local_guard` attribute indicates that this global represents a
     function-local static variable that requires guarded initialization
     (e.g., C++ static local variables with non-constant initializers).
+    It contains the mangled name of the guard variable.
   }];
 
   // Note that both sym_name and sym_visibility are tied to Symbol trait.
@@ -2407,7 +2408,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
                        UnitAttr:$comdat,
                        UnitAttr:$constant,
                        UnitAttr:$dso_local,
-                       UnitAttr:$static_local,
+                       
OptionalAttr<CIR_StaticLocalGuardAttr>:$static_local_guard,
                        OptionalAttr<I64Attr>:$alignment,
                        OptionalAttr<ASTVarDeclInterface>:$ast);
 
@@ -2422,7 +2423,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
     (`comdat` $comdat^)?
     ($tls_model^)?
     (`dso_local` $dso_local^)?
-    (`static_local` $static_local^)?
+    (`static_local_guard` `` $static_local_guard^)?
     $sym_name
     custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value,
                                         $ctorRegion, $dtorRegion)
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
index 12a6bfa8691cc..4bfa8df7ad55a 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
@@ -10,11 +10,9 @@
 #define CLANG_CIR_INTERFACES_ASTATTRINTERFACES_H
 
 #include "mlir/IR/Attributes.h"
-#include "llvm/Support/raw_ostream.h"
 
 #include "clang/AST/Attr.h"
 #include "clang/AST/DeclTemplate.h"
-#include "clang/AST/Mangle.h"
 
 /// Include the generated interface declarations.
 #include "clang/CIR/Interfaces/ASTAttrInterfaces.h.inc"
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
index c0d4da81cb979..53909063abbab 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
@@ -14,13 +14,6 @@ include "mlir/IR/OpBase.td"
 let cppNamespace = "::cir" in {
   def ASTVarDeclInterface : AttrInterface<"ASTVarDeclInterface"> {
     let methods = [
-      InterfaceMethod<"", "void", "mangleStaticGuardVariable",
-                      (ins "llvm::raw_ostream&":$out), [{}],
-                      /*defaultImplementation=*/[{
-        std::unique_ptr<clang::MangleContext> mangleCtx(
-          $_attr.getAst()->getASTContext().createMangleContext());
-        mangleCtx->mangleStaticGuardVariable($_attr.getAst(), out);
-      }]>,
       InterfaceMethod<"", "bool", "isLocalVarDecl", (ins), [{}],
                       /*defaultImplementation=*/[{
         return $_attr.getAst()->isLocalVarDecl();
@@ -38,11 +31,6 @@ let cppNamespace = "::cir" in {
                       "getTemplateSpecializationKind", (ins), [{}],
                       /*defaultImplementation=*/[{
         return $_attr.getAst()->getTemplateSpecializationKind();
-      }]>,
-      InterfaceMethod<"Get the underlying VarDecl pointer.",
-                      "const clang::VarDecl *", "getVarDecl", (ins), [{}],
-                      /*defaultImplementation=*/[{
-        return $_attr.getAst();
       }]>
     ];
   }
diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 5cb0991326a3c..132616df531c5 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -286,6 +286,7 @@ struct MissingFeatures {
   static bool getRuntimeFunctionDecl() { return false; }
   static bool globalViewIndices() { return false; }
   static bool globalViewIntLowering() { return false; }
+  static bool guardAbortOnException() { return false; }
   static bool handleBuiltinICEArguments() { return false; }
   static bool hip() { return false; }
   static bool incrementProfileCounter() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
index 9c2a5fe0cda07..255e5ead77072 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -10,10 +10,13 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "CIRGenCXXABI.h"
 #include "CIRGenFunction.h"
 #include "CIRGenModule.h"
 #include "clang/AST/Attr.h"
+#include "clang/AST/Mangle.h"
 #include "clang/Basic/LangOptions.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
 
 using namespace clang;
 using namespace clang::CIRGen;
@@ -27,12 +30,24 @@ void CIRGenFunction::emitCXXGuardedInit(const VarDecl 
&varDecl,
   if (cgm.getCodeGenOpts().ForbidGuardVariables)
     cgm.error(varDecl.getLocation(), "guard variables are forbidden");
 
+  // Compute the mangled guard variable name and set the static_local attribute
+  // BEFORE emitting initialization. This ensures that GetGlobalOps created
+  // during initialization (e.g., in the ctor region) will see the attribute
+  // and be marked with static_local accordingly.
+  llvm::SmallString<256> guardName;
+  {
+    llvm::raw_svector_ostream out(guardName);
+    cgm.getCXXABI().getMangleContext().mangleStaticGuardVariable(&varDecl, 
out);
+  }
+
+  // Mark the global as static local with the guard name. The emission of the
+  // guard/acquire is done during LoweringPrepare.
+  auto guardAttr = mlir::StringAttr::get(&cgm.getMLIRContext(), guardName);
+  globalOp.setStaticLocalGuardAttr(
+      cir::StaticLocalGuardAttr::get(&cgm.getMLIRContext(), guardAttr));
+
   // Emit the initializer and add a global destructor if appropriate.
   cgm.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit);
-
-  // Mark the global as static local. The emission of the guard/acquire
-  // is done during LoweringPrepare.
-  globalOp.setStaticLocal(true);
 }
 
 void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 133eb0f3dfe7e..69b8c95331360 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -829,9 +829,10 @@ mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl 
*d, mlir::Type ty,
   bool tlsAccess = d->getTLSKind() != VarDecl::TLS_None;
   cir::GlobalOp g = getOrCreateCIRGlobal(d, ty, isForDefinition);
   mlir::Type ptrTy = builder.getPointerTo(g.getSymType());
-  return cir::GetGlobalOp::create(builder, getLoc(d->getSourceRange()), ptrTy,
-                                  g.getSymNameAttr(), tlsAccess,
-                                  g.getStaticLocal());
+  return cir::GetGlobalOp::create(
+      builder, getLoc(d->getSourceRange()), ptrTy, g.getSymNameAttr(),
+      tlsAccess,
+      /*static_local=*/g.getStaticLocalGuard().has_value());
 }
 
 cir::GlobalViewAttr CIRGenModule::getAddrOfGlobalVarAttr(const VarDecl *d) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index c38a77a7598b8..3f6c8489e6896 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1854,9 +1854,13 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection 
&symbolTable) {
     // be marked with tls bits.
     if (getTls() && !g.getTlsModel())
       return emitOpError("access to global not marked thread local");
-    // Verify that the static_local attribute matches between get_global and
-    // global. Skip when inside a GlobalOp region (e.g., ctor/dtor regions).
-    if (getStaticLocal() != g.getStaticLocal() &&
+
+    // Verify that the static_local attribute on GetGlobalOp matches the
+    // static_local_guard attribute on GlobalOp. GetGlobalOp uses a UnitAttr,
+    // GlobalOp uses StaticLocalGuardAttr. Both should be present, or neither.
+    bool getGlobalIsStaticLocal = getStaticLocal();
+    bool globalIsStaticLocal = g.getStaticLocalGuard().has_value();
+    if (getGlobalIsStaticLocal != globalIsStaticLocal &&
         !getOperation()->getParentOfType<cir::GlobalOp>())
       return emitOpError("static_local attribute mismatch");
   } else if (auto f = dyn_cast<FuncOp>(op)) {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 5c6d40027158f..688dfdc29e850 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -9,13 +9,18 @@
 #include "PassDetail.h"
 #include "mlir/IR/Attributes.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Mangle.h"
 #include "clang/Basic/Module.h"
+#include "clang/Basic/Specifiers.h"
+#include "clang/Basic/TargetCXXABI.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
 #include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRDataLayout.h"
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/IR/CIROpsEnums.h"
 #include "clang/CIR/Dialect/Passes.h"
+#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
 #include "clang/CIR/MissingFeatures.h"
 #include "llvm/Support/Path.h"
 
@@ -99,6 +104,74 @@ struct LoweringPreparePass
       cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage,
       cir::VisibilityKind visibility = cir::VisibilityKind::Default);
 
+  /// Handle static local variable initialization with guard variables.
+  void handleStaticLocal(cir::GlobalOp globalOp, cir::GetGlobalOp getGlobalOp);
+
+  /// Get or create __cxa_guard_acquire function.
+  cir::FuncOp getGuardAcquireFn(cir::PointerType guardPtrTy);
+
+  /// Get or create __cxa_guard_release function.
+  cir::FuncOp getGuardReleaseFn(cir::PointerType guardPtrTy);
+
+  /// Create a guard global variable for a static local.
+  cir::GlobalOp createGuardGlobalOp(CIRBaseBuilderTy &builder,
+                                    mlir::Location loc, llvm::StringRef name,
+                                    cir::IntType guardTy,
+                                    cir::GlobalLinkageKind linkage);
+
+  /// Get the guard variable for a static local declaration.
+  cir::GlobalOp getStaticLocalDeclGuardAddress(llvm::StringRef globalSymName) {
+    auto it = staticLocalDeclGuardMap.find(globalSymName);
+    if (it != staticLocalDeclGuardMap.end())
+      return it->second;
+    return nullptr;
+  }
+
+  /// Set the guard variable for a static local declaration.
+  void setStaticLocalDeclGuardAddress(llvm::StringRef globalSymName,
+                                      cir::GlobalOp guard) {
+    staticLocalDeclGuardMap[globalSymName] = guard;
+  }
+
+  /// Get or create the guard variable for a static local declaration.
+  cir::GlobalOp getOrCreateStaticLocalDeclGuardAddress(
+      CIRBaseBuilderTy &builder, cir::GlobalOp globalOp,
+      cir::ASTVarDeclInterface varDecl, cir::IntType guardTy,
+      clang::CharUnits guardAlignment) {
+    llvm::StringRef globalSymName = globalOp.getSymName();
+    cir::GlobalOp guard = getStaticLocalDeclGuardAddress(globalSymName);
+    if (!guard) {
+      // Get the guard name from the static_local attribute.
+      llvm::StringRef guardName =
+          globalOp.getStaticLocalGuard()->getName().getValue();
+
+      // Create the guard variable with a zero-initializer.
+      guard = createGuardGlobalOp(builder, globalOp->getLoc(), guardName,
+                                  guardTy, globalOp.getLinkage());
+      guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
+      guard.setDSOLocal(globalOp.getDsoLocal());
+      guard.setAlignment(guardAlignment.getAsAlign().value());
+
+      // The ABI says: "It is suggested that it be emitted in the same COMDAT
+      // group as the associated data object." In practice, this doesn't work
+      // for non-ELF and non-Wasm object formats, so only do it for ELF and
+      // Wasm.
+      bool hasComdat = globalOp.getComdat();
+      const llvm::Triple &triple = astCtx->getTargetInfo().getTriple();
+      if (!varDecl.isLocalVarDecl() && hasComdat &&
+          (triple.isOSBinFormatELF() || triple.isOSBinFormatWasm())) {
+        globalOp->emitError("NYI: guard COMDAT for non-local variables");
+        return {};
+      } else if (hasComdat && globalOp.isWeakForLinker()) {
+        globalOp->emitError("NYI: guard COMDAT for weak linkage");
+        return {};
+      }
+
+      setStaticLocalDeclGuardAddress(globalSymName, guard);
+    }
+    return guard;
+  }
+
   ///
   /// AST related
   /// -----------
@@ -112,11 +185,119 @@ struct LoweringPreparePass
   llvm::StringMap<uint32_t> dynamicInitializerNames;
   llvm::SmallVector<cir::FuncOp> dynamicInitializers;
 
+  /// Tracks guard variables for static locals (keyed by global symbol name).
+  llvm::StringMap<cir::GlobalOp> staticLocalDeclGuardMap;
+
   /// 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;
 
+  /// Returns true if the target uses ARM-style guard variables for static
+  /// local initialization (32-bit guard, check bit 0 only).
+  bool useARMGuardVarABI() const {
+    switch (astCtx->getCXXABIKind()) {
+    case clang::TargetCXXABI::GenericARM:
+    case clang::TargetCXXABI::iOS:
+    case clang::TargetCXXABI::WatchOS:
+    case clang::TargetCXXABI::GenericAArch64:
+    case clang::TargetCXXABI::WebAssembly:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  /// Emit the guarded initialization for a static local variable.
+  /// This handles the if/else structure after the guard byte check,
+  /// following OG's ItaniumCXXABI::EmitGuardedInit skeleton.
+  void emitCXXGuardedInitIf(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp,
+                            cir::ASTVarDeclInterface varDecl,
+                            mlir::Value guardPtr, cir::PointerType guardPtrTy,
+                            bool threadsafe) {
+    auto loc = globalOp->getLoc();
+
+    // The semantics of dynamic initialization of variables with static or
+    // thread storage duration depends on whether they are declared at
+    // block-scope. The initialization of such variables at block-scope can be
+    // aborted with an exception and later retried (per C++20 [stmt.dcl]p4),
+    // and recursive entry to their initialization has undefined behavior (also
+    // per C++20 [stmt.dcl]p4). For such variables declared at non-block scope,
+    // exceptions lead to termination (per C++20 [except.terminate]p1), and
+    // recursive references to the variables are governed only by the lifetime
+    // rules (per C++20 [class.cdtor]p2), which means such references are
+    // perfectly fine as long as they avoid touching memory. As a result,
+    // block-scope variables must not be marked as initialized until after
+    // initialization completes (unless the mark is reverted following an
+    // exception), but non-block-scope variables must be marked prior to
+    // initialization so that recursive accesses during initialization do not
+    // restart initialization.
+
+    // Variables used when coping with thread-safe statics and exceptions.
+    if (threadsafe) {
+      // Call __cxa_guard_acquire.
+      cir::CallOp acquireCall = builder.createCallOp(
+          loc, getGuardAcquireFn(guardPtrTy), mlir::ValueRange{guardPtr});
+      mlir::Value acquireResult = acquireCall.getResult();
+
+      auto acquireZero = builder.getConstantInt(
+          loc, mlir::cast<cir::IntType>(acquireResult.getType()), 0);
+      auto shouldInit = builder.createCompare(loc, cir::CmpOpKind::ne,
+                                              acquireResult, acquireZero);
+
+      // Create the IfOp for the shouldInit check.
+      // Pass an empty callback to avoid auto-creating a yield terminator.
+      auto ifOp =
+          cir::IfOp::create(builder, loc, shouldInit, /*withElseRegion=*/false,
+                            [](mlir::OpBuilder &, mlir::Location) {});
+      mlir::OpBuilder::InsertionGuard insertGuard(builder);
+      builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+
+      // Call __cxa_guard_abort along the exceptional edge.
+      // OG: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard);
+      assert(!cir::MissingFeatures::guardAbortOnException());
+
+      // Emit the initializer and add a global destructor if appropriate.
+      auto &ctorRegion = globalOp.getCtorRegion();
+      assert(!ctorRegion.empty() && "This should never be empty here.");
+      if (!ctorRegion.hasOneBlock())
+        llvm_unreachable("Multiple blocks NYI");
+      mlir::Block &block = ctorRegion.front();
+      mlir::Block *insertBlock = builder.getInsertionBlock();
+      insertBlock->getOperations().splice(insertBlock->end(),
+                                          block.getOperations(), block.begin(),
+                                          std::prev(block.end()));
+      builder.setInsertionPointToEnd(insertBlock);
+      ctorRegion.getBlocks().clear();
+
+      // Pop the guard-abort cleanup if we pushed one.
+      // OG: CGF.PopCleanupBlock();
+      assert(!cir::MissingFeatures::guardAbortOnException());
+
+      // Call __cxa_guard_release. This cannot throw.
+      builder.createCallOp(loc, getGuardReleaseFn(guardPtrTy),
+                           mlir::ValueRange{guardPtr});
+
+      builder.createYield(loc);
+    } else if (!varDecl.isLocalVarDecl()) {
+      // For non-local variables, store 1 into the first byte of the guard
+      // variable before the object initialization begins so that references
+      // to the variable during initialization don't restart initialization.
+      // OG: Builder.CreateStore(llvm::ConstantInt::get(CGM.Int8Ty, 1), ...);
+      // Then: CGF.EmitCXXGlobalVarDeclInit(D, var, shouldPerformInit);
+      globalOp->emitError("NYI: non-threadsafe init for non-local variables");
+      return;
+    } else {
+      // For local variables, store 1 into the first byte of the guard variable
+      // after the object initialization completes so that initialization is
+      // retried if initialization is interrupted by an exception.
+      globalOp->emitError("NYI: non-threadsafe init for local variables");
+      return;
+    }
+
+    builder.createYield(loc); // Outermost IfOp
+  }
+
   void setASTContext(clang::ASTContext *c) { astCtx = c; }
 };
 
@@ -871,9 +1052,213 @@ 
LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
   return f;
 }
 
+cir::FuncOp
+LoweringPreparePass::getGuardAcquireFn(cir::PointerType guardPtrTy) {
+  // int __cxa_guard_acquire(__guard *guard_object);
+  CIRBaseBuilderTy builder(getContext());
+  mlir::OpBuilder::InsertionGuard ipGuard{builder};
+  builder.setInsertionPointToStart(mlirModule.getBody());
+  mlir::Location loc = mlirModule.getLoc();
+  cir::IntType intTy = cir::IntType::get(&getContext(), 32, /*isSigned=*/true);
+  auto fnType = cir::FuncType::get({guardPtrTy}, intTy);
+  return buildRuntimeFunction(builder, "__cxa_guard_acquire", loc, fnType);
+}
+
+cir::FuncOp
+LoweringPreparePass::getGuardReleaseFn(cir::PointerType guardPtrTy) {
+  // void __cxa_guard_release(__guard *guard_object);
+  CIRBaseBuilderTy builder(getContext());
+  mlir::OpBuilder::InsertionGuard ipGuard{builder};
+  builder.setInsertionPointToStart(mlirModule.getBody());
+  mlir::Location loc = mlirModule.getLoc();
+  cir::VoidType voidTy = cir::VoidType::get(&getContext());
+  auto fnType = cir::FuncType::get({guardPtrTy}, voidTy);
+  return buildRuntimeFunction(builder, "__cxa_guard_release", loc, fnType);
+}
+
+cir::GlobalOp LoweringPreparePass::createGuardGlobalOp(
+    CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef name,
+    cir::IntType guardTy, cir::GlobalLinkageKind linkage) {
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPointToStart(mlirModule.getBody());
+  cir::GlobalOp g = cir::GlobalOp::create(builder, loc, name, guardTy);
+  g.setLinkageAttr(
+      cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
+  mlir::SymbolTable::setSymbolVisibility(
+      g, mlir::SymbolTable::Visibility::Private);
+  return g;
+}
+
+void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
+                                            cir::GetGlobalOp getGlobalOp) {
+  CIRBaseBuilderTy builder(getContext());
+
+  std::optional<cir::ASTVarDeclInterface> astOption = globalOp.getAst();
+  assert(astOption.has_value());
+  cir::ASTVarDeclInterface varDecl = astOption.value();
+
+  builder.setInsertionPointAfter(getGlobalOp);
+  mlir::Block *getGlobalOpBlock = builder.getInsertionBlock();
+
+  // Remove the terminator temporarily - we'll add it back at the end.
+  mlir::Operation *ret = getGlobalOpBlock->getTerminator();
+  ret->remove();
+  builder.setInsertionPointAfter(getGlobalOp);
+
+  // Inline variables that weren't instantiated from variable templates have
+  // partially-ordered initialization within their translation unit.
+  bool nonTemplateInline =
+      varDecl.isInline() &&
+      !clang::isTemplateInstantiation(varDecl.getTemplateSpecializationKind());
+
+  // Inline namespace-scope variables require guarded initialization in a
+  // __cxx_global_var_init function. This is not yet implemented.
+  if (nonTemplateInline) {
+    globalOp->emitError(
+        "NYI: guarded initialization for inline namespace-scope variables");
+    return;
+  }
+
+  // We only need to use thread-safe statics for local non-TLS variables and
+  // inline variables; other global initialization is always single-threaded
+  // or (through lazy dynamic loading in multiple threads) unsequenced.
+  bool threadsafe = astCtx->getLangOpts().ThreadsafeStatics &&
+                    (varDecl.isLocalVarDecl() || nonTemplateInline) &&
+                    !varDecl.getTLSKind();
+
+  // TLS variables need special handling - the guard must also be thread-local.
+  if (varDecl.getTLSKind()) {
+    globalOp->emitError("NYI: guarded initialization for thread-local 
statics");
+    return;
+  }
+
+  // If we have a global variable with internal linkage and thread-safe statics
+  // are disabled, we can just let the guard variable be of type i8.
+  bool useInt8GuardVariable = !threadsafe && globalOp.hasInternalLinkage();
+  if (useInt8GuardVariable) {
+    globalOp->emitError("NYI: int8 guard variables for non-threadsafe 
statics");
+    return;
+  }
+
+  // Guard variables are 64 bits in the generic ABI and size width on ARM
+  // (i.e. 32-bit on AArch32, 64-bit on AArch64).
+  if (useARMGuardVarABI()) {
+    globalOp->emitError("NYI: ARM-style guard variables for static locals");
+    return;
+  }
+  cir::IntType guardTy =
+      cir::IntType::get(&getContext(), 64, /*isSigned=*/true);
+  cir::CIRDataLayout dataLayout(mlirModule);
+  clang::CharUnits guardAlignment =
+      clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
+  auto guardPtrTy = cir::PointerType::get(guardTy);
+
+  // Create the guard variable if we don't already have it.
+  cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress(
+      builder, globalOp, varDecl, guardTy, guardAlignment);
+  if (!guard) {
+    // Error was already emitted, just restore the terminator and return.
+    getGlobalOpBlock->push_back(ret);
+    return;
+  }
+
+  mlir::Value guardPtr = builder.createGetGlobal(guard, /*threadLocal*/ false);
+
+  // Test whether the variable has completed initialization.
+  //
+  // Itanium C++ ABI 3.3.2:
+  //   The following is pseudo-code showing how these functions can be used:
+  //     if (obj_guard.first_byte == 0) {
+  //       if ( __cxa_guard_acquire (&obj_guard) ) {
+  //         try {
+  //           ... initialize the object ...;
+  //         } catch (...) {
+  //            __cxa_guard_abort (&obj_guard);
+  //            throw;
+  //         }
+  //         ... queue object destructor with __cxa_atexit() ...;
+  //         __cxa_guard_release (&obj_guard);
+  //       }
+  //     }
+  //
+  // If threadsafe statics are enabled, but we don't have inline atomics, just
+  // call __cxa_guard_acquire unconditionally. The "inline" check isn't
+  // actually inline, and the user might not expect calls to __atomic libcalls.
+  unsigned maxInlineWidthInBits =
+      astCtx->getTargetInfo().getMaxAtomicInlineWidth();
+
+  if (!threadsafe || maxInlineWidthInBits) {
+    // Load the first byte of the guard variable.
+    auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8));
+    mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy);
+    mlir::Value guardLoad = builder.createAlignedLoad(
+        getGlobalOp.getLoc(), bytePtr, guardAlignment.getAsAlign().value());
+
+    // Itanium ABI:
+    //   An implementation supporting thread-safety on multiprocessor
+    //   systems must also guarantee that references to the initialized
+    //   object do not occur before the load of the initialization flag.
+    //
+    // In LLVM, we do this by marking the load Acquire.
+    if (threadsafe) {
+      auto loadOp = mlir::cast<cir::LoadOp>(guardLoad.getDefiningOp());
+      loadOp.setMemOrder(cir::MemOrder::Acquire);
+      loadOp.setSyncScope(cir::SyncScopeKind::System);
+    }
+
+    // For ARM, we should only check the first bit, rather than the entire 
byte:
+    //
+    // ARM C++ ABI 3.2.3.1:
+    //   To support the potential use of initialization guard variables
+    //   as semaphores that are the target of ARM SWP and LDREX/STREX
+    //   synchronizing instructions we define a static initialization
+    //   guard variable to be a 4-byte aligned, 4-byte word with the
+    //   following inline access protocol.
+    //     #define INITIALIZED 1
+    //     if ((obj_guard & INITIALIZED) != INITIALIZED) {
+    //       if (__cxa_guard_acquire(&obj_guard))
+    //         ...
+    //     }
+    //
+    // and similarly for ARM64:
+    //
+    // ARM64 C++ ABI 3.2.2:
+    //   This ABI instead only specifies the value bit 0 of the static guard
+    //   variable; all other bits are platform defined. Bit 0 shall be 0 when
+    //   the variable is not initialized and 1 when it is.
+    if (useARMGuardVarABI()) {
+      globalOp->emitError(
+          "NYI: ARM-style guard variable check (bit 0 only) for static 
locals");
+      return;
+    }
+
+    // Check if the first byte of the guard variable is zero.
+    auto zero = builder.getConstantInt(
+        getGlobalOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 
0);
+    auto needsInit = builder.createCompare(getGlobalOp.getLoc(),
+                                           cir::CmpOpKind::eq, guardLoad, 
zero);
+
+    // Build the guarded initialization inside an if block.
+    cir::IfOp::create(builder, globalOp.getLoc(), needsInit,
+                      /*withElseRegion=*/false,
+                      [&](mlir::OpBuilder &, mlir::Location) {
+                        emitCXXGuardedInitIf(builder, globalOp, varDecl,
+                                             guardPtr, guardPtrTy, threadsafe);
+                      });
+  } else {
+    // Threadsafe statics without inline atomics - call __cxa_guard_acquire
+    // unconditionally without the initial guard byte check.
+    globalOp->emitError("NYI: guarded init without inline atomics support");
+    return;
+  }
+
+  // Insert the removed terminator back.
+  builder.getInsertionBlock()->push_back(ret);
+}
+
 void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
   // Static locals are handled separately via guard variables.
-  if (op.getStaticLocal())
+  if (op.getStaticLocalGuard())
     return;
 
   mlir::Region &ctorRegion = op.getCtorRegion();
@@ -1089,6 +1474,23 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
     lowerComplexMulOp(complexMul);
   } else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) {
     lowerGlobalOp(glob);
+  } else if (auto getGlobal = mlir::dyn_cast<cir::GetGlobalOp>(op)) {
+    // Handle static local variables with guard variables.
+    // Only process GetGlobalOps inside function bodies, not in GlobalOp
+    // regions.
+    if (getGlobal.getStaticLocal() &&
+        getGlobal->getParentOfType<cir::FuncOp>()) {
+      auto globalOp = mlir::dyn_cast_or_null<cir::GlobalOp>(
+          mlir::SymbolTable::lookupNearestSymbolFrom(getGlobal,
+                                                     getGlobal.getNameAttr()));
+      // Only process if the GlobalOp has static_local and the ctor region is
+      // not empty. After handleStaticLocal processes a static local, the ctor
+      // region is cleared. GetGlobalOps that were spliced from the ctor region
+      // into the function will be skipped on subsequent iterations.
+      if (globalOp && globalOp.getStaticLocalGuard() &&
+          !globalOp.getCtorRegion().empty())
+        handleStaticLocal(globalOp, getGlobal);
+    }
   } else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op)) {
     lowerUnaryOp(unary);
   } else if (auto callOp = dyn_cast<cir::CallOp>(op)) {
@@ -1111,7 +1513,8 @@ void LoweringPreparePass::runOnOperation() {
   op->walk([&](mlir::Operation *op) {
     if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
                   cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp,
-                  cir::FuncOp, cir::CallOp, cir::GlobalOp, cir::UnaryOp>(op))
+                  cir::FuncOp, cir::CallOp, cir::GetGlobalOp, cir::GlobalOp,
+                  cir::UnaryOp>(op))
       opsToTransform.push_back(op);
   });
 
diff --git a/clang/test/CIR/CodeGen/static-local.cpp 
b/clang/test/CIR/CodeGen/static-local.cpp
index 0297028e96438..4bfba7efe4806 100644
--- a/clang/test/CIR/CodeGen/static-local.cpp
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -1,4 +1,6 @@
 // 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>&1 
| FileCheck %s --check-prefix=CIR-BEFORE-LPP
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir %s -o - | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
 // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s 
-o - | FileCheck %s --check-prefix=OGCG
 
 class A {
@@ -6,19 +8,49 @@ class A {
   A();
 };
 
+void use(A*);
 void f() {
   static A a;
+  use(&a);
 }
 
-// CIR-BEFORE-LPP: cir.global "private" internal dso_local static_local 
@_ZZ1fvE1a = ctor : !rec_A {
-// CIR-BEFORE-LPP:   %[[ADDR:.*]] = cir.get_global @_ZZ1fvE1a : 
!cir.ptr<!rec_A>
+// CIR-BEFORE-LPP: cir.global "private" internal dso_local 
static_local_guard<"_ZGVZ1fvE1a"> @_ZZ1fvE1a = ctor : !rec_A {
+// CIR-BEFORE-LPP:   %[[ADDR:.*]] = cir.get_global static_local @_ZZ1fvE1a : 
!cir.ptr<!rec_A>
 // CIR-BEFORE-LPP:   cir.call @_ZN1AC1Ev(%[[ADDR]]) : (!cir.ptr<!rec_A>) -> ()
 // CIR-BEFORE-LPP: } {alignment = 1 : i64, ast = #cir.var.decl.ast}
 
 // CIR-BEFORE-LPP: cir.func no_inline dso_local @_Z1fv()
 // CIR-BEFORE-LPP:   %[[VAR:.*]] = cir.get_global static_local @_ZZ1fvE1a : 
!cir.ptr<!rec_A>
+// CIR-BEFORE-LPP:   cir.call @_Z3useP1A(%[[VAR]])
 // CIR-BEFORE-LPP:   cir.return
 
+// CIR: cir.global "private" internal dso_local @_ZGVZ1fvE1a = #cir.int<0> : 
!s64i
+// CIR: cir.func{{.*}}@_Z1fv()
+// CIR:   %[[ADDR:.*]] = cir.get_global static_local @_ZZ1fvE1a : 
!cir.ptr<!rec_A>
+// CIR:   %[[GUARD:.*]] = cir.get_global @_ZGVZ1fvE1a : !cir.ptr<!s64i>
+// CIR:   %[[GUARD_BYTE_PTR:.*]] = cir.cast bitcast %[[GUARD]] : 
!cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR:   %[[GUARD_LOAD:.*]] = cir.load{{.*}}%[[GUARD_BYTE_PTR]]
+// CIR:   %[[ZERO:.*]] = cir.const #cir.int<0>
+// CIR:   %[[IS_UNINIT:.*]] = cir.cmp(eq, %[[GUARD_LOAD]], %[[ZERO]])
+// CIR:   cir.if %[[IS_UNINIT]]
+// CIR:     cir.call @__cxa_guard_acquire
+// CIR:     cir.if
+// CIR:       cir.call @_ZN1AC1Ev
+// CIR:       cir.call @__cxa_guard_release
+// CIR:   cir.call @_Z3useP1A(%[[ADDR]])
+// CIR:   cir.return
+
+// LLVM: @_ZGVZ1fvE1a = internal global i64 0
+// LLVM: define{{.*}}void @_Z1fv()
+// LLVM:   %[[GUARD:.*]] = load atomic i8, ptr @_ZGVZ1fvE1a acquire
+// LLVM:   %[[IS_UNINIT:.*]] = icmp eq i8 %[[GUARD]], 0
+// LLVM:   br i1 %[[IS_UNINIT]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// LLVM: call i32 @__cxa_guard_acquire
+// LLVM: call void @_ZN1AC1Ev
+// LLVM: call void @__cxa_guard_release
+// LLVM: call void @_Z3useP1A(ptr @_ZZ1fvE1a)
+// LLVM: ret void
+
 // OGCG: @_ZGVZ1fvE1a = internal global i64 0
 // OGCG: define{{.*}}void @_Z1fv()
 // OGCG:   %[[GUARD:.*]] = load atomic i8, ptr @_ZGVZ1fvE1a acquire
@@ -27,4 +59,5 @@ void f() {
 // OGCG: call i32 @__cxa_guard_acquire
 // OGCG: call void @_ZN1AC1Ev
 // OGCG: call void @__cxa_guard_release
+// OGCG: call void @_Z3useP1A(ptr {{.*}}@_ZZ1fvE1a)
 // OGCG: ret void
diff --git a/clang/test/CIR/IR/invalid-static-local.cir 
b/clang/test/CIR/IR/invalid-static-local.cir
index 13ef4f63a8288..7a6b245d97eb7 100644
--- a/clang/test/CIR/IR/invalid-static-local.cir
+++ b/clang/test/CIR/IR/invalid-static-local.cir
@@ -4,7 +4,7 @@
 
 module {
 
-// Global is not marked static_local, but get_global is
+// Global is not marked static_local_guard, but get_global is static_local
 cir.global "private" internal @_ZZ1fvE1x : !s32i
 
 cir.func @test_static_local_mismatch() {
@@ -21,8 +21,8 @@ cir.func @test_static_local_mismatch() {
 
 module {
 
-// Global is marked static_local, but get_global is not
-cir.global "private" internal static_local @_ZZ1fvE1y : !s32i
+// Global is marked static_local_guard, but get_global is not static_local
+cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : 
!s32i
 
 cir.func @test_static_local_mismatch_reverse() {
   // expected-error @below {{static_local attribute mismatch}}
diff --git a/clang/test/CIR/IR/static-local.cir 
b/clang/test/CIR/IR/static-local.cir
index d4c5a743358de..b1f4a6a937bb2 100644
--- a/clang/test/CIR/IR/static-local.cir
+++ b/clang/test/CIR/IR/static-local.cir
@@ -4,9 +4,9 @@
 
 module {
 
-// Test static_local attribute on global and get_global
-cir.global "private" internal static_local @_ZZ1fvE1x : !s32i
-// CHECK: cir.global "private" internal static_local @_ZZ1fvE1x : !s32i
+// Test static_local_guard attribute on global and static_local on get_global
+cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : 
!s32i
+// CHECK: cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> 
@_ZZ1fvE1x : !s32i
 
 cir.func @test_static_local() {
   %0 = cir.get_global static_local @_ZZ1fvE1x : !cir.ptr<!s32i>

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to