Author: Erich Keane
Date: 2026-05-11T06:40:08-07:00
New Revision: fd30f5bb55a8b8868fabdca33675e7438da70a1a

URL: 
https://github.com/llvm/llvm-project/commit/fd30f5bb55a8b8868fabdca33675e7438da70a1a
DIFF: 
https://github.com/llvm/llvm-project/commit/fd30f5bb55a8b8868fabdca33675e7438da70a1a.diff

LOG: [CIR] Implement Namespace/global TLS CIR CodeGen (#196332)

Unlike local TLS, global TLS functions need to be initialized upon their
first use in a thread.

First, all attempts to 'get' said TLS global are replaced with calls to
a 'wrapper' function, which calls an 'init' alias function, then returns
the global. While classic codegen manages to omit this in simple cases
sometimes, this CIR implementation doesn't attempt to do such constant
folding/inlining. The call to the 'init' is omitted if there is no
ctor/dtor setup required, so sometimes the wrapper is just a 'no-op'
(intentionally!).

There are also two types of 'global' TLS functions: unordered, and
ordered. Unordered are typically variable templates, and their 'init'
function initializes JUST them. The rest are ordered, which requires all
ordered initializations to happen as soon as any happen.

The Wrapper:
If necessary (omitted in a few places), calls the 'init' alias, then
returns the global.

Global Init Function (__tls_init):
For ordered global TLS, a use of 1 TLS is required to initialize ALL of
them. This function checks the global guard (__tls_guard), and calls the
individual 'init' functions for each global(__cxx_global_var_init).
Unordered ones do not get entered here.

Individual init Functions (__cxx_global_var_init): Like the rest of our
globals, these emit a __cxx_global_var_init function. However, they are
not added to the global constructors list. These are identically emitted
with one exception: Unordered Global TLS variables wrap said init in a
guard check (ordered have their's guarded by __tls_guard in __tls_init).

Init Alias:
This alias is called by the wrapper function to make sure said variable
is initialized before use. IN the case of ordered globals, this is an
alias to __tls_init. IN the case of unordered globals, this goes
directly to the __cxx_global_var_init for that variable.

This patch implements the codegen part of the above by introducing a set
of 3 strings on a GlobalOp in an attribute. if present, this will cause
the above global tls behavior (with the next patch). At the moment, this
only generates the attribute, and the lowering-prepare patches will come
in future patches.

Added: 
    clang/test/CIR/CodeGen/global-tls-dyn-init.cpp
    clang/test/CIR/CodeGen/global-tls-simple-init.cpp
    clang/test/CIR/CodeGen/global-tls-templates.cpp

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
    clang/include/clang/CIR/Dialect/IR/CIROps.td
    clang/lib/CIR/CodeGen/CIRGenCXX.cpp
    clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
    clang/lib/CIR/CodeGen/CIRGenExpr.cpp
    clang/lib/CIR/CodeGen/CIRGenModule.cpp
    clang/lib/CIR/CodeGen/CIRGenModule.h
    clang/lib/CIR/Dialect/IR/CIRDialect.cpp
    clang/test/CIR/IR/invalid-tls.cir

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 1520999e3f85f..4032d8219fff3 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1530,6 +1530,65 @@ def CIR_StaticLocalGuardAttr : 
CIR_Attr<"StaticLocalGuard",
   let canHaveIllegalCXXABIType = 0;
 }
 
+//===----------------------------------------------------------------------===//
+// ThreadLocalGlobalWrapperInitAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_ThreadLocalGlobalWrapperInitAttr : CIR_Attr<
+    "ThreadLocalGlobalWrapperInit", "tls_wrapper_init"> {
+    let summary = "Wrapper and Init function names for thread local variables";
+    let description = [{
+      Contains the mangled name of the wrapper function, init function, and
+      guard variable for a namespace/global scope thread local variable. The
+      guard variable is optional, as it is only required for unordered thread
+      local variables, as ordered thread local variables share a guard.
+
+      Unordered global thread local variables (such as variable template
+      instantiations) are individually initialized when first used on a thread.
+      Ordered global thread local variables are ALL initialized together when
+      any that require initialization are referenced.
+
+      This is accomplished by rewriting all calls to these variables as calls 
to
+      the wrapper.  If the variable requires initialization, the wrapper calls
+      the init function, then returns the global variable reference.
+
+      Throughout CIR though, these are just represented as normal `get_global`
+      calls to `global`s with `ctor`/`dtor` regions (if necessary).  The
+      lowering-prepare pass manages the generation of the wrapper,x
+      initialization, and call rewrites.
+
+      Example:
+      ```
+      cir.global tls_dyn dyn_tls_refs = <"_ZTW7tls_var", "_ZTH7tls_var"> 
@_ZZZ7tls_var = ...
+      ...
+      cir.get_global thread_local @ZZZ7tls_var : !cir.ptr<!s32i>
+      ```
+    }];
+    let parameters = (ins
+        "mlir::StringAttr" : $wrapper_name,
+        "mlir::StringAttr" : $init_name,
+        OptionalParameter<"mlir::StringAttr">: $guard_name
+        );
+
+    let builders = [
+      AttrBuilder<(ins "llvm::StringRef"
+                   : $wrapper, "llvm::StringRef"
+                   : $init, "llvm::StringRef"
+                   : $guard),
+                  [{
+                    mlir::StringAttr guardAttr;
+                    if (!guard.empty())
+                      guardAttr = mlir::StringAttr::get($_ctxt, guard);
+                    return $_get($_ctxt, mlir::StringAttr::get($_ctxt, 
wrapper),
+                                 mlir::StringAttr::get($_ctxt, init),
+                                 guardAttr);
+                  }]>,
+    ];
+    let assemblyFormat =
+        "`<` $wrapper_name `,` $init_name (`,` $guard_name^)? `>`";
+    let canHaveIllegalCXXABIType = 0;
+}
+
 
//===----------------------------------------------------------------------===//
 // UsualDeleteParamsAttr
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index b87286e846879..ed285201ae8f8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2863,6 +2863,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
                        CIR_GlobalLinkageKind:$linkage,
                        OptionalAttr<MemorySpaceAttrInterface>:$addr_space,
                        OptionalAttr<CIR_TLSModel>:$tls_model,
+                       
OptionalAttr<CIR_ThreadLocalGlobalWrapperInitAttr>:$dyn_tls_refs,
                        OptionalAttr<AnyAttr>:$initial_value,
                        UnitProp:$comdat,
                        UnitProp:$constant,
@@ -2885,6 +2886,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
     $linkage
     (`comdat` $comdat^)?
     ($tls_model^)?
+    (`dyn_tls_refs` `=` $dyn_tls_refs^)?
     (`dso_local` $dso_local^)?
     (`static_local_guard` `` $static_local_guard^)?
     (` ` custom<GlobalAddressSpaceValue>($addr_space)^ )?

diff  --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index b12307b124777..d1a9110125af7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -295,7 +295,7 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl 
*varDecl,
                                      builder.getInsertionBlock()};
   scope.setAsGlobalInit();
   builder.setInsertionPointToStart(block);
-  mlir::Value getGlobal = builder.createGetGlobal(addr);
+  mlir::Value getGlobal = builder.createGetGlobal(addr, varDecl->getTLSKind());
   // If we're initializing a static local with a guard variable, set the flag
   // that indicates that.
   getGlobal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
@@ -328,8 +328,7 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl 
*varDecl,
 void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
                                             cir::GlobalOp addr,
                                             bool performInit) {
-  assert(!varDecl->isStaticLocal() &&
-         varDecl->getTLSKind() == VarDecl::TLS_None);
+  assert(!varDecl->isStaticLocal());
 
   // Create a CIRGenFunction to emit the initializer. While this isn't a true
   // function, the handling works the same way.

diff  --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
index 955f3f4815a06..03929e50f44f9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -59,6 +59,45 @@ void CIRGenFunction::emitCXXGuardedInit(const VarDecl 
&varDecl,
   cgm.emitCXXStaticLocalVarDeclInit(&varDecl, globalOp, performInit);
 }
 
+void CIRGenModule::setGlobalTlsReferences(const VarDecl &vd,
+                                          cir::GlobalOp globalOp) {
+  assert(!vd.isStaticLocal() && vd.getTLSKind());
+
+  // C doesn't need guarded thread-local init, because it can't have
+  // non-constant init.
+  if (!getLangOpts().CPlusPlus)
+    return;
+
+  if (globalOp.getTlsModel() != cir::TLS_Model::GeneralDynamic)
+    return;
+
+  llvm::SmallString<256> wrapperFuncName;
+  llvm::SmallString<256> initFuncName;
+  llvm::SmallString<256> guardName;
+
+  if (getCXXABI().getMangleContext().getKind() == MangleContext::MK_Itanium) {
+    llvm::raw_svector_ostream wrapperOut(wrapperFuncName);
+    llvm::raw_svector_ostream initOut(initFuncName);
+    llvm::raw_svector_ostream guardStream(guardName);
+
+    auto &mc = cast<ItaniumMangleContext>(getCXXABI().getMangleContext());
+    mc.mangleItaniumThreadLocalWrapper(&vd, wrapperOut);
+    mc.mangleItaniumThreadLocalInit(&vd, initOut);
+    if (globalOp.hasWeakLinkage() || globalOp.hasLinkOnceLinkage() ||
+        isTemplateInstantiation(vd.getTemplateSpecializationKind())) {
+      getCXXABI().getMangleContext().mangleStaticGuardVariable(&vd,
+                                                               guardStream);
+    }
+
+  } else {
+    errorNYI(vd.getSourceRange(),
+             "setGlobalTlsReferences: non-itanium mangler");
+    return;
+  }
+  globalOp.setDynTlsRefsAttr(cir::ThreadLocalGlobalWrapperInitAttr::get(
+      &getMLIRContext(), wrapperFuncName, initFuncName, guardName));
+}
+
 void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
                                                 cir::GlobalOp addr,
                                                 bool performInit) {

diff  --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index cb53430438219..b8cfb74fc81e4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -425,10 +425,9 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, 
const Expr *e,
                                       const VarDecl *vd) {
   QualType t = e->getType();
 
-  // If it's thread_local, emit a call to its wrapper function instead.
-  if (vd->getTLSKind() == VarDecl::TLS_Dynamic)
-    cgf.cgm.errorNYI(e->getSourceRange(),
-                     "emitGlobalVarDeclLValue: thread_local variable");
+  // In classic codegen, thread-locals get a wrapper function here. Rather than
+  // doing that, we instead treat this as a normal 'global', and leave it to
+  // lowerng-prepare to correctly generate the wrapper/etc.
 
   // Check if the variable is marked as declare target with link clause in
   // device codegen.

diff  --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 04e413aa916ec..8c39d94a6b2ec 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1160,11 +1160,8 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef 
mangledName, mlir::Type ty,
 
     setLinkageForGV(gv, d);
 
-    if (d->getTLSKind()) {
-      if (d->getTLSKind() == VarDecl::TLS_Dynamic)
-        errorNYI(d->getSourceRange(), "getOrCreateCIRGlobal: TLS dynamic");
+    if (d->getTLSKind())
       setTLSMode(gv, *d);
-    }
 
     setGVProperties(gv, d);
 
@@ -1508,7 +1505,8 @@ void CIRGenModule::emitGlobalVarDefinition(const 
clang::VarDecl *vd,
 
   setNonAliasAttributes(vd, gv);
 
-  assert(!cir::MissingFeatures::opGlobalThreadLocal());
+  if (vd->getTLSKind() && !vd->isStaticLocal())
+    setTLSMode(gv, *vd);
 
   maybeSetTrivialComdat(*vd, gv);
 
@@ -2774,6 +2772,13 @@ void CIRGenModule::setTLSMode(mlir::Operation *op, const 
VarDecl &d) {
 
   auto global = cast<cir::GlobalOp>(op);
   global.setTlsModel(tlm);
+
+  // For namespace-scope dyanmic TLS we need to set the wrapper, int, or guard
+  // info.
+  if (d.isStaticLocal() || tlm != cir::TLS_Model::GeneralDynamic)
+    return;
+
+  setGlobalTlsReferences(d, global);
 }
 
 void CIRGenModule::setCIRFunctionAttributes(GlobalDecl globalDecl,

diff  --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 1146e4561db0b..e2184ef8640f3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -648,6 +648,7 @@ class CIRGenModule : public CIRGenTypeCache {
   void emitCXXGlobalVarDeclInit(const VarDecl *varDecl, cir::GlobalOp addr,
                                 bool performInit);
 
+  void setGlobalTlsReferences(const VarDecl &vd, cir::GlobalOp globalOp);
   void emitCXXGlobalVarDeclInitFunc(const VarDecl *vd, cir::GlobalOp addr,
                                     bool performInit);
 

diff  --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 79d09b3ba1eb0..74ef856c5a067 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1903,12 +1903,19 @@ mlir::LogicalResult cir::GlobalOp::verify() {
       return failure();
   }
 
-  if ((getStaticLocalGuard().has_value() || getTlsModel()) &&
+  if ((getStaticLocalGuard().has_value()) &&
       (!getCtorRegion().empty() || !getDtorRegion().empty()))
     return emitOpError(
-        "Cannot have a thread-local or static-local global-op "
-        "with a constructor or destructor, they require in-function "
-        "initialization via LocalInitOp");
+        "Cannot have a static-local global-op with a constructor or "
+        "destructor, they require in-function initialization via LocalInitOp");
+
+  if (getDynTlsRefs()) {
+    if (getStaticLocalGuard().has_value())
+      return emitOpError(
+          "cannot have both static local and dynamic tls references");
+    if (!getTlsModel() || getTlsModel() != TLS_Model::GeneralDynamic)
+      return emitOpError("'dyn_tls_refs' only valid for dynamic tls");
+  }
 
   if (getAliasee().has_value()) {
     if (getInitialValue().has_value() || !getCtorRegion().empty() ||

diff  --git a/clang/test/CIR/CodeGen/global-tls-dyn-init.cpp 
b/clang/test/CIR/CodeGen/global-tls-dyn-init.cpp
new file mode 100644
index 0000000000000..ef3c1e306f62d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-tls-dyn-init.cpp
@@ -0,0 +1,50 @@
+// 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
+
+int get_i();
+struct CtorDtor {
+  constexpr CtorDtor(int i) : i(i){}
+  ~CtorDtor(){}
+    int i;
+};
+
+thread_local CtorDtor tls_cd = 5;
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = <"_ZTW6tls_cd", 
"_ZTH6tls_cd"> @tls_cd = #cir.const_record<{#cir.int<5> : !s32i}> : 
!rec_CtorDtor dtor {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local @tls_cd : 
!cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:   cir.call @_ZN8CtorDtorD1Ev(%[[GET_GLOB]]) : 
(!cir.ptr<!rec_CtorDtor>) -> ()
+// CIR-BEFORE-LPP: }
+
+thread_local CtorDtor tls_cd_dyn = get_i();
+// CIR-BEFORE-LPP:  cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW10tls_cd_dyn", "_ZTH10tls_cd_dyn"> @tls_cd_dyn = ctor : !rec_CtorDtor {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_cd_dyn : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:    %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:    cir.call @_ZN8CtorDtorC1Ei(%[[GET_GLOB]], %[[CALL]]) : 
(!cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:  } dtor {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_cd_dyn : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:    cir.call @_ZN8CtorDtorD1Ev(%[[GET_GLOB]]) : 
(!cir.ptr<!rec_CtorDtor>) -> ()
+// CIR-BEFORE-LPP:  }
+
+thread_local CtorDtor &tls_cd_ref = tls_cd_dyn;
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW10tls_cd_ref", "_ZTH10tls_cd_ref"> @tls_cd_ref = ctor : 
!cir.ptr<!rec_CtorDtor> {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_cd_ref : !cir.ptr<!cir.ptr<!rec_CtorDtor>>
+// CIR-BEFORE-LPP:   %[[CALL:.*]] = cir.get_global thread_local @tls_cd_dyn : 
!cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:   cir.store {{.*}}%[[CALL]], %[[GET_GLOB]] : 
!cir.ptr<!rec_CtorDtor>, !cir.ptr<!cir.ptr<!rec_CtorDtor>>
+// CIR-BEFORE-LPP: }
+
+thread_local CtorDtor tls_cd_dyn_not_used = get_i();
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW19tls_cd_dyn_not_used", "_ZTH19tls_cd_dyn_not_used"> @tls_cd_dyn_not_used 
= ctor : !rec_CtorDtor {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_cd_dyn_not_used : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:   %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:   cir.call @_ZN8CtorDtorC1Ei(%[[GET_GLOB]], %[[CALL]])
+// CIR-BEFORE-LPP: } dtor {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_cd_dyn_not_used : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:   cir.call @_ZN8CtorDtorD1Ev(%[[GET_GLOB]]) : 
(!cir.ptr<!rec_CtorDtor>) -> ()
+// CIR-BEFORE-LPP: }
+
+void uses() {
+  auto a = tls_cd;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_cd : 
!cir.ptr<!rec_CtorDtor>
+  auto b = tls_cd_dyn;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_cd_dyn : 
!cir.ptr<!rec_CtorDtor>
+  auto c = tls_cd_ref;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_cd_ref : 
!cir.ptr<!cir.ptr<!rec_CtorDtor>>
+}

diff  --git a/clang/test/CIR/CodeGen/global-tls-simple-init.cpp 
b/clang/test/CIR/CodeGen/global-tls-simple-init.cpp
new file mode 100644
index 0000000000000..fef55b0298c33
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-tls-simple-init.cpp
@@ -0,0 +1,65 @@
+// 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
+
+int get_i();
+struct CtorDtor {
+  constexpr CtorDtor(int i) : i(i){}
+  ~CtorDtor(){}
+    int i;
+};
+
+thread_local int tls_int = 5;
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = <"_ZTW7tls_int", 
"_ZTH7tls_int"> @tls_int = #cir.int<5> : !s32i
+
+thread_local int tls_int_dyn = get_i();
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW11tls_int_dyn", "_ZTH11tls_int_dyn"> @tls_int_dyn = ctor : !s32i {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_int_dyn : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:   %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:   cir.store {{.*}}%[[CALL]], %[[GET_GLOB]] : !s32i, 
!cir.ptr<!s32i>
+// CIR-BEFORE-LPP: }
+
+thread_local int &tls_int_ref = tls_int_dyn;
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW11tls_int_ref", "_ZTH11tls_int_ref"> @tls_int_ref = ctor : 
!cir.ptr<!s32i> {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_int_ref : !cir.ptr<!cir.ptr<!s32i>>
+// CIR-BEFORE-LPP:   %[[GET_OTHER:.*]] = cir.get_global thread_local 
@tls_int_dyn : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:   cir.store {{.*}}%[[GET_OTHER]], %[[GET_GLOB]] : 
!cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
+// CIR-BEFORE-LPP: }
+
+thread_local int tls_int_self_init = tls_int_self_init + get_i();
+// CIR-BEFORE-LPP:  cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW17tls_int_self_init", "_ZTH17tls_int_self_init"> @tls_int_self_init = 
ctor : !s32i {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@tls_int_self_init : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:    %[[GET_SELF:.*]] = cir.get_global thread_local 
@tls_int_self_init : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:    %[[LOAD_SELF:.*]] = cir.load {{.*}}%[[GET_SELF]] : 
!cir.ptr<!s32i>, !s32i
+// CIR-BEFORE-LPP:    %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:    %[[ADD:.*]] = cir.add nsw %[[LOAD_SELF]], %[[CALL]] : 
!s32i
+// CIR-BEFORE-LPP:    cir.store {{.*}}%[[ADD]], %[[GET_GLOB]] : !s32i, 
!cir.ptr<!s32i>
+// CIR-BEFORE-LPP:  }
+
+extern thread_local int definitely_inited = 5;
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW17definitely_inited", "_ZTH17definitely_inited"> @definitely_inited = 
#cir.int<5> : !s32i
+
+extern thread_local int definitely_inited_dyn = get_i();
+// CIR-BEFORE-LPP: cir.global external tls_dyn dyn_tls_refs = 
<"_ZTW21definitely_inited_dyn", "_ZTH21definitely_inited_dyn"> 
@definitely_inited_dyn = ctor : !s32i {
+// CIR-BEFORE-LPP:   %[[GET_GLOB:.*]] = cir.get_global thread_local 
@definitely_inited_dyn : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:   %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:   cir.store {{.*}}%[[CALL]], %[[GET_GLOB]] : !s32i, 
!cir.ptr<!s32i>
+// CIR-BEFORE-LPP: }
+
+extern thread_local int maybe_inited;
+// CIR-BEFORE-LPP: cir.global "private" external tls_dyn dyn_tls_refs = 
<"_ZTW12maybe_inited", "_ZTH12maybe_inited"> @maybe_inited : !s32i
+
+void uses() {
+  auto a = tls_int;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_int : !cir.ptr<!s32i>
+  auto b = tls_int_dyn;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_int_dyn : !cir.ptr<!s32i>
+  auto c = tls_int_ref;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_int_ref : 
!cir.ptr<!cir.ptr<!s32i>>
+  auto d = tls_int_self_init;
+// CIR-BEFORE-LPP: cir.get_global thread_local @tls_int_self_init : 
!cir.ptr<!s32i>
+  auto e = maybe_inited;
+// CIR-BEFORE-LPP: cir.get_global thread_local @maybe_inited : !cir.ptr<!s32i>
+  auto f = definitely_inited;
+// CIR-BEFORE-LPP: cir.get_global thread_local @definitely_inited : 
!cir.ptr<!s32i>
+  auto g = definitely_inited_dyn;
+// CIR-BEFORE-LPP: cir.get_global thread_local @definitely_inited_dyn : 
!cir.ptr<!s32i>
+}

diff  --git a/clang/test/CIR/CodeGen/global-tls-templates.cpp 
b/clang/test/CIR/CodeGen/global-tls-templates.cpp
new file mode 100644
index 0000000000000..bad1f1440dde5
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-tls-templates.cpp
@@ -0,0 +1,34 @@
+// 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
+
+int get_i();
+struct CtorDtor {
+  constexpr CtorDtor(int i) : i(i){}
+  ~CtorDtor(){}
+    int i;
+};
+
+template<typename T>
+thread_local T tls_templ = {get_i()};
+
+// CIR-BEFORE-LPP-LABEL:  cir.global linkonce_odr comdat tls_dyn dyn_tls_refs 
= <"_ZTW9tls_templIiE", "_ZTH9tls_templIiE", "_ZGV9tls_templIiE"> 
@_Z9tls_templIiE = ctor : !s32i {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@_Z9tls_templIiE : !cir.ptr<!s32i>
+// CIR-BEFORE-LPP:    %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:    cir.store{{.*}} %[[CALL]], %[[GET_GLOB]] : !s32i, 
!cir.ptr<!s32i>
+// CIR-BEFORE-LPP:  }
+//
+// CIR-BEFORE-LPP-LABEL:  cir.global linkonce_odr comdat tls_dyn dyn_tls_refs 
= <"_ZTW9tls_templI8CtorDtorE", "_ZTH9tls_templI8CtorDtorE", 
"_ZGV9tls_templI8CtorDtorE"> @_Z9tls_templI8CtorDtorE = ctor : !rec_CtorDtor {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@_Z9tls_templI8CtorDtorE : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:    %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i 
{llvm.noundef})
+// CIR-BEFORE-LPP:    cir.call @_ZN8CtorDtorC1Ei(%[[GET_GLOB]], %[[CALL]]) : 
(!cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:  } dtor {
+// CIR-BEFORE-LPP:    %[[GET_GLOB:.*]] = cir.get_global thread_local 
@_Z9tls_templI8CtorDtorE : !cir.ptr<!rec_CtorDtor>
+// CIR-BEFORE-LPP:    cir.call @_ZN8CtorDtorD1Ev(%[[GET_GLOB]]) : 
(!cir.ptr<!rec_CtorDtor>) -> ()
+// CIR-BEFORE-LPP:  }
+
+// CIR-BEFORE-LPP-LABEL: cir.func{{.*}}@_Z4usesv
+void uses() {
+  auto x = tls_templ<int>;
+// CIR-BEFORE-LPP: cir.get_global thread_local @_Z9tls_templIiE : 
!cir.ptr<!s32i>
+  auto y = tls_templ<CtorDtor>;
+// CIR-BEFORE-LPP: cir.get_global thread_local @_Z9tls_templI8CtorDtorE : 
!cir.ptr<!rec_CtorDtor>
+}

diff  --git a/clang/test/CIR/IR/invalid-tls.cir 
b/clang/test/CIR/IR/invalid-tls.cir
index 36df7fdb1e619..e33b7070eb790 100644
--- a/clang/test/CIR/IR/invalid-tls.cir
+++ b/clang/test/CIR/IR/invalid-tls.cir
@@ -11,3 +11,21 @@ module {
   }
 }
 
+// -----
+
+!s32i = !cir.int<s, 32>
+
+module {
+  // expected-error@+1{{op cannot have both static local and dynamic tls 
references}}
+cir.global "private" internal tls_dyn dyn_tls_refs = <"asdf", "asdf", "asdf"> 
static_local_guard<"asdf"> @_ZZ1fvE1y : !s32i
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+module {
+  // expected-error@+1{{'dyn_tls_refs' only valid for dynamic tls}}
+cir.global "private" internal tls_local_dyn dyn_tls_refs = <"asdf", "asdf", 
"asdf"> @_ZZ1fvE1y : !s32i
+}
+


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

Reply via email to