[llvm-branch-commits] [clang] 0a2e56d - [CIR] Add support for thread-local storage (TLS) (#168662)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Dec 9 15:51:26 PST 2025
Author: adams381
Date: 2025-12-09T15:11:01-08:00
New Revision: 0a2e56df64c936bacc746aeb94878d66ee00dec3
URL: https://github.com/llvm/llvm-project/commit/0a2e56df64c936bacc746aeb94878d66ee00dec3
DIFF: https://github.com/llvm/llvm-project/commit/0a2e56df64c936bacc746aeb94878d66ee00dec3.diff
LOG: [CIR] Add support for thread-local storage (TLS) (#168662)
This commit adds full support for thread-local storage variables in
ClangIR, including code generation, lowering to LLVM IR, and
comprehensive testing.
Changes include:
- Added CIR_TLSModel enum with 4 TLS models (GeneralDynamic,
LocalDynamic, InitialExec, LocalExec) to CIROps.td
- Extended GlobalOp with optional tls_model attribute
- Extended GetGlobalOp with thread_local unit attribute
- Added verification to ensure thread_local GetGlobalOp references
globals with tls_model set
- Implemented GetDefaultCIRTLSModel() and setTLSMode() in CIRGenModule
- Updated getAddrOfGlobalVar() to handle TLS access
- Removed MissingFeatures assertions for TLS operations
- Added lowering of GetGlobalOp with TLS to llvm.threadlocal.address
intrinsic
- Added lowering of GlobalOp with tls_model to LLVM thread_local globals
- Added comprehensive test with CIR, LLVM, and OGCG checks
Known limitations (matching incubator):
- Static local TLS variables not yet implemented
- TLS_Dynamic with wrapper functions not yet implemented
Fixes #153270
Added:
clang/test/CIR/CodeGen/tls.c
clang/test/CIR/IR/invalid-tls.cir
Modified:
clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
clang/include/clang/CIR/Dialect/IR/CIROps.td
clang/lib/CIR/CodeGen/CIRGenDecl.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/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index aa47c4bce189b..7ee3785ef74f0 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -308,14 +308,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return cir::GlobalViewAttr::get(type, symbol, indices);
}
- mlir::Value createGetGlobal(mlir::Location loc, cir::GlobalOp global) {
+ mlir::Value createGetGlobal(mlir::Location loc, cir::GlobalOp global,
+ bool threadLocal = false) {
assert(!cir::MissingFeatures::addressSpace());
- return cir::GetGlobalOp::create(
- *this, loc, getPointerTo(global.getSymType()), global.getSymName());
+ return cir::GetGlobalOp::create(*this, loc,
+ getPointerTo(global.getSymType()),
+ global.getSymNameAttr(), threadLocal);
}
- mlir::Value createGetGlobal(cir::GlobalOp global) {
- return createGetGlobal(global.getLoc(), global);
+ mlir::Value createGetGlobal(cir::GlobalOp global, bool threadLocal = false) {
+ return createGetGlobal(global.getLoc(), global, threadLocal);
}
/// Create a copy with inferred length.
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 9bd24cf0bcf27..04648bee848f1 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2078,6 +2078,13 @@ def CIR_GlobalLinkageKind : CIR_I32EnumAttr<
// properties of a global variable will be added over time as more of ClangIR
// is upstreamed.
+def CIR_TLSModel : CIR_I32EnumAttr<"TLS_Model", "TLS model", [
+ I32EnumAttrCase<"GeneralDynamic", 0, "tls_dyn">,
+ I32EnumAttrCase<"LocalDynamic", 1, "tls_local_dyn">,
+ I32EnumAttrCase<"InitialExec", 2, "tls_init_exec">,
+ I32EnumAttrCase<"LocalExec", 3, "tls_local_exec">
+]>;
+
def CIR_GlobalOp : CIR_Op<"global", [
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
DeclareOpInterfaceMethods<CIRGlobalValueInterface>,
@@ -2106,6 +2113,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
OptionalAttr<StrAttr>:$sym_visibility,
TypeAttr:$sym_type,
CIR_GlobalLinkageKind:$linkage,
+ OptionalAttr<CIR_TLSModel>:$tls_model,
OptionalAttr<AnyAttr>:$initial_value,
UnitAttr:$comdat,
UnitAttr:$constant,
@@ -2121,6 +2129,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
(`constant` $constant^)?
$linkage
(`comdat` $comdat^)?
+ ($tls_model^)?
(`dso_local` $dso_local^)?
$sym_name
custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value,
@@ -2184,16 +2193,22 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [
undefined. The resulting type must always be a `!cir.ptr<...>` type with the
same address space as the global variable.
+ Addresses of thread local globals can only be retrieved if this operation
+ is marked `thread_local`, which indicates the address isn't constant.
+
Example:
```mlir
%x = cir.get_global @gv : !cir.ptr<i32>
+ ...
+ %y = cir.get_global thread_local @tls_gv : !cir.ptr<i32>
```
}];
- let arguments = (ins FlatSymbolRefAttr:$name);
+ let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls);
let results = (outs Res<CIR_PointerType, "", []>:$addr);
let assemblyFormat = [{
+ (`thread_local` $tls^)?
$name `:` qualified(type($addr)) attr-dict
}];
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 948245ceab2cd..12b153af36c3e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -454,7 +454,8 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
if (supportsCOMDAT() && gv.isWeakForLinker())
gv.setComdat(true);
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
+ if (d.getTLSKind())
+ errorNYI(d.getSourceRange(), "getOrCreateStaticVarDecl: TLS");
setGVProperties(gv, &d);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 5d509e37f4621..cac046c41e30d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -282,7 +282,6 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e,
QualType t = e->getType();
// If it's thread_local, emit a call to its wrapper function instead.
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
if (vd->getTLSKind() == VarDecl::TLS_Dynamic)
cgf.cgm.errorNYI(e->getSourceRange(),
"emitGlobalVarDeclLValue: thread_local variable");
@@ -318,7 +317,6 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr,
bool isVolatile, QualType ty,
LValueBaseInfo baseInfo, bool isInit,
bool isNontemporal) {
- assert(!cir::MissingFeatures::opLoadStoreThreadLocal());
if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
// Boolean vectors use `iN` as storage type.
@@ -569,7 +567,8 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, LValue lvalue,
mlir::Value CIRGenFunction::emitLoadOfScalar(Address addr, bool isVolatile,
QualType ty, SourceLocation loc,
LValueBaseInfo baseInfo) {
- assert(!cir::MissingFeatures::opLoadStoreThreadLocal());
+ // Traditional LLVM codegen handles thread local separately, CIR handles
+ // as part of getAddrOfGlobalVar (GetGlobalOp).
mlir::Type eltTy = addr.getElementType();
if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 41a5d9db83e2b..eaa9e946e243d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -682,8 +682,11 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty,
setLinkageForGV(gv, d);
- if (d->getTLSKind())
- errorNYI(d->getSourceRange(), "thread local global variable");
+ if (d->getTLSKind()) {
+ if (d->getTLSKind() == VarDecl::TLS_Dynamic)
+ errorNYI(d->getSourceRange(), "TLS dynamic");
+ setTLSMode(gv, *d);
+ }
setGVProperties(gv, d);
@@ -738,12 +741,11 @@ mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty,
if (!ty)
ty = getTypes().convertTypeForMem(astTy);
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
-
+ 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.getSymName());
+ g.getSymNameAttr(), tlsAccess);
}
cir::GlobalViewAttr CIRGenModule::getAddrOfGlobalVarAttr(const VarDecl *d) {
@@ -1953,6 +1955,33 @@ void CIRGenModule::setGVPropertiesAux(mlir::Operation *op,
assert(!cir::MissingFeatures::opGlobalPartition());
}
+cir::TLS_Model CIRGenModule::getDefaultCIRTLSModel() const {
+ switch (getCodeGenOpts().getDefaultTLSModel()) {
+ case CodeGenOptions::GeneralDynamicTLSModel:
+ return cir::TLS_Model::GeneralDynamic;
+ case CodeGenOptions::LocalDynamicTLSModel:
+ return cir::TLS_Model::LocalDynamic;
+ case CodeGenOptions::InitialExecTLSModel:
+ return cir::TLS_Model::InitialExec;
+ case CodeGenOptions::LocalExecTLSModel:
+ return cir::TLS_Model::LocalExec;
+ }
+ llvm_unreachable("Invalid TLS model!");
+}
+
+void CIRGenModule::setTLSMode(mlir::Operation *op, const VarDecl &d) {
+ assert(d.getTLSKind() && "setting TLS mode on non-TLS var!");
+
+ cir::TLS_Model tlm = getDefaultCIRTLSModel();
+
+ // Override the TLS model if it is explicitly specified.
+ if (d.getAttr<TLSModelAttr>())
+ errorNYI(d.getSourceRange(), "TLS model attribute");
+
+ auto global = cast<cir::GlobalOp>(op);
+ global.setTlsModel(tlm);
+}
+
void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
cir::FuncOp func,
bool isIncompleteFunction,
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 9c0961579718d..de263f4868507 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -447,6 +447,13 @@ class CIRGenModule : public CIRGenTypeCache {
void setGVProperties(mlir::Operation *op, const NamedDecl *d) const;
void setGVPropertiesAux(mlir::Operation *op, const NamedDecl *d) const;
+ /// Set TLS mode for the given operation based on the given variable
+ /// declaration.
+ void setTLSMode(mlir::Operation *op, const VarDecl &d);
+
+ /// Get TLS mode from CodeGenOptions.
+ cir::TLS_Model getDefaultCIRTLSModel() const;
+
/// Set function attributes for a function declaration.
void setFunctionAttributes(GlobalDecl gd, cir::FuncOp f,
bool isIncompleteFunction, bool isThunk);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 38a2cecbb8617..0f546cb254db4 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1737,7 +1737,10 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
if (auto g = dyn_cast<GlobalOp>(op)) {
symTy = g.getSymType();
assert(!cir::MissingFeatures::addressSpace());
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
+ // Verify that for thread local global access, the global needs to
+ // be marked with tls bits.
+ if (getTls() && !g.getTlsModel())
+ return emitOpError("access to global not marked thread local");
} else if (auto f = dyn_cast<FuncOp>(op)) {
symTy = f.getFunctionType();
} else {
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 88ca8033b48ea..0ad3360e1357e 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2058,7 +2058,11 @@ mlir::LogicalResult CIRToLLVMGetGlobalOpLowering::matchAndRewrite(
mlir::Operation *newop = mlir::LLVM::AddressOfOp::create(
rewriter, op.getLoc(), type, op.getName());
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
+ if (op.getTls()) {
+ // Handle access to TLS via intrinsic.
+ newop = mlir::LLVM::ThreadlocalAddressOp::create(rewriter, op.getLoc(),
+ type, newop->getResult(0));
+ }
rewriter.replaceOp(op, newop);
return mlir::success();
@@ -2079,8 +2083,7 @@ void CIRToLLVMGlobalOpLowering::setupRegionInitializedLLVMGlobalOp(
assert(!cir::MissingFeatures::addressSpace());
const unsigned addrSpace = 0;
const bool isDsoLocal = op.getDsoLocal();
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
- const bool isThreadLocal = false;
+ const bool isThreadLocal = (bool)op.getTlsModelAttr();
const uint64_t alignment = op.getAlignment().value_or(0);
const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
const StringRef symbol = op.getSymName();
@@ -2140,8 +2143,7 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
assert(!cir::MissingFeatures::addressSpace());
const unsigned addrSpace = 0;
const bool isDsoLocal = op.getDsoLocal();
- assert(!cir::MissingFeatures::opGlobalThreadLocal());
- const bool isThreadLocal = false;
+ const bool isThreadLocal = (bool)op.getTlsModelAttr();
const uint64_t alignment = op.getAlignment().value_or(0);
const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
const StringRef symbol = op.getSymName();
diff --git a/clang/test/CIR/CodeGen/tls.c b/clang/test/CIR/CodeGen/tls.c
new file mode 100644
index 0000000000000..582a716f0fa73
--- /dev/null
+++ b/clang/test/CIR/CodeGen/tls.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s
+
+extern __thread int b;
+// CIR: cir.global "private" external tls_dyn @b : !s32i
+
+__thread int a;
+// CIR: cir.global external tls_dyn @a = #cir.int<0> : !s32i
+
+int c(void) { return *&b; }
+// CIR: cir.func no_inline dso_local @c() -> !s32i
+// CIR: %[[TLS_ADDR:.*]] = cir.get_global thread_local @b : !cir.ptr<!s32i>
+
+// LLVM: @b = external thread_local global i32
+// LLVM: @a = thread_local global i32 0
+
+// LLVM-LABEL: @c
+// LLVM: = call ptr @llvm.threadlocal.address.p0(ptr @b)
+
+// OGCG: @b = external thread_local{{.*}} global i32
+// OGCG: @a = thread_local{{.*}} global i32 0
+
+// OGCG-LABEL: define{{.*}} @c
+// OGCG: call{{.*}} ptr @llvm.threadlocal.address.p0(ptr{{.*}} @b)
+
diff --git a/clang/test/CIR/IR/invalid-tls.cir b/clang/test/CIR/IR/invalid-tls.cir
new file mode 100644
index 0000000000000..36df7fdb1e619
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-tls.cir
@@ -0,0 +1,13 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!s32i = !cir.int<s, 32>
+
+module {
+ cir.global "private" external @non_tls : !s32i
+ cir.func @error() {
+ // expected-error at +1 {{access to global not marked thread local}}
+ %0 = cir.get_global thread_local @non_tls : !cir.ptr<!s32i>
+ cir.return
+ }
+}
+
More information about the llvm-branch-commits
mailing list