[clang] [Clang][MSVC] Avoid native CRT TLS accesses with emulated TLS (PR #220215)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 1 04:17:01 PDT 2026
https://github.com/Sp0tless created https://github.com/llvm/llvm-project/pull/220215
When targeting the Microsoft C++ ABI with `-femulated-tls`, Clang currently mixes two TLS models. User `thread_local` variables are lowered through emutls, but the Microsoft ABI initialization paths still directly access native CRT TLS variables:
- thread-safe local static initialization reads `_Init_thread_epoch`;
- dynamic TLS initialization reads `__tls_guard`.
These CRT-owned variables do not have emutls descriptors, so lowering produces unresolved references such as `__emutls_v._Init_thread_epoch` and `__emutls_v.__tls_guard`.
This is reproducible with ordinary Clang, without clang-repl or ORC. This Compiler Explorer example uses `x86-64 clang 22.1.0 --target=x86_64-pc-windows-msvc -femulated-tls` and shows both invalid emutls references in the generated assembly:
https://godbolt.org/z/hseq4TK4T
Avoid both direct native-TLS accesses when emulated TLS is enabled. Dynamic TLS calls `__dyn_tls_on_demand_init` directly; the CRT helper already performs the guard check. Local static initialization enters `_Init_thread_header` on every access and retains the existing second guard check, initialization, abort, and footer paths. The native TLS code path is unchanged.
The local-static fallback trades the native per-thread epoch fast path for correctness and may add synchronization overhead on repeated access. The affected combination, Microsoft ABI with explicitly enabled emulated TLS, appears uncommon. I would appreciate guidance on whether this fallback is acceptable or whether an emulated epoch fast path should be introduced instead.
I originally found this while testing emulated TLS in an experimental Windows `clang-repl` configuration. Fixing the general Clang CodeGen behavior is also a prerequisite for using emulated TLS there, but this patch does not contain interpreter or ORC integration changes. Related investigation: #213568.
Testing: Added `clang/test/CodeGenCXX/microsoft-abi-emulated-tls.cpp`; the new test and the neighboring native MSVC TLS tests pass.
Assisted-by: OpenAI Codex
>From f5dcb3ba98487c21a9b7cd9ff20b804f14853907 Mon Sep 17 00:00:00 2001
From: Sp0tless <Sp0tless at users.noreply.github.com>
Date: Mon, 31 Aug 2026 20:00:25 +0800
Subject: [PATCH] [Clang][MSVC] Avoid native CRT TLS accesses with emulated TLS
---
clang/lib/CodeGen/MicrosoftCXXABI.cpp | 38 ++++++++++++-------
.../CodeGenCXX/microsoft-abi-emulated-tls.cpp | 28 ++++++++++++++
2 files changed, 53 insertions(+), 13 deletions(-)
create mode 100644 clang/test/CodeGenCXX/microsoft-abi-emulated-tls.cpp
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 8b43dd887573d..4ae9ea7ec18c9 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -2531,19 +2531,23 @@ static void emitTlsGuardCheck(CodeGenFunction &CGF, llvm::GlobalValue *TlsGuard,
CGF.Builder.CreateCondBr(CmpResult, DynInitBB, ContinueBB);
}
-static void emitDynamicTlsInitializationCall(CodeGenFunction &CGF,
- llvm::GlobalValue *TlsGuard,
- llvm::BasicBlock *ContinueBB) {
+static void emitDynamicTlsInitializationCall(CodeGenFunction &CGF) {
llvm::FunctionCallee Initializer = getDynTlsOnDemandInitFn(CGF.CGM);
llvm::Function *InitializerFunction =
cast<llvm::Function>(Initializer.getCallee());
llvm::CallInst *CallVal = CGF.Builder.CreateCall(InitializerFunction);
CallVal->setCallingConv(InitializerFunction->getCallingConv());
-
- CGF.Builder.CreateBr(ContinueBB);
}
static void emitDynamicTlsInitialization(CodeGenFunction &CGF) {
+ if (CGF.CGM.getCodeGenOpts().EmulatedTLS) {
+ // __tls_guard is native TLS owned by the MSVC runtime and does not have an
+ // emutls descriptor. The runtime helper performs the same guard check, so
+ // call it directly rather than emitting a reference to __tls_guard.
+ emitDynamicTlsInitializationCall(CGF);
+ return;
+ }
+
llvm::BasicBlock *DynInitBB =
CGF.createBasicBlock("dyntls.dyn_init", CGF.CurFn);
llvm::BasicBlock *ContinueBB =
@@ -2553,7 +2557,8 @@ static void emitDynamicTlsInitialization(CodeGenFunction &CGF) {
emitTlsGuardCheck(CGF, TlsGuard, DynInitBB, ContinueBB);
CGF.Builder.SetInsertPoint(DynInitBB);
- emitDynamicTlsInitializationCall(CGF, TlsGuard, ContinueBB);
+ emitDynamicTlsInitializationCall(CGF);
+ CGF.Builder.CreateBr(ContinueBB);
CGF.Builder.SetInsertPoint(ContinueBB);
}
@@ -2791,13 +2796,20 @@ void MicrosoftCXXABI::EmitGuardedInit(CodeGenFunction &CGF, const VarDecl &D,
// The algorithm is almost identical to what can be found in the appendix
// found in N2325.
- // This BasicBLock determines whether or not we have any work to do.
- llvm::LoadInst *FirstGuardLoad = Builder.CreateLoad(GuardAddr);
- FirstGuardLoad->setOrdering(llvm::AtomicOrdering::Unordered);
- llvm::LoadInst *InitThreadEpoch =
- Builder.CreateLoad(getInitThreadEpochPtr(CGM));
- llvm::Value *IsUninitialized =
- Builder.CreateICmpSGT(FirstGuardLoad, InitThreadEpoch);
+ llvm::Value *IsUninitialized;
+ if (CGM.getCodeGenOpts().EmulatedTLS) {
+ // _Init_thread_epoch is native TLS owned by the MSVC runtime. It does
+ // not have an emutls descriptor. Without its per-thread epoch, enter the
+ // runtime on every access to preserve its synchronization guarantees.
+ IsUninitialized = Builder.getTrue();
+ } else {
+ // This test determines whether or not we have any work to do.
+ llvm::LoadInst *FirstGuardLoad = Builder.CreateLoad(GuardAddr);
+ FirstGuardLoad->setOrdering(llvm::AtomicOrdering::Unordered);
+ llvm::LoadInst *InitThreadEpoch =
+ Builder.CreateLoad(getInitThreadEpochPtr(CGM));
+ IsUninitialized = Builder.CreateICmpSGT(FirstGuardLoad, InitThreadEpoch);
+ }
llvm::BasicBlock *AttemptInitBlock = CGF.createBasicBlock("init.attempt");
llvm::BasicBlock *EndBlock = CGF.createBasicBlock("init.end");
CGF.EmitCXXGuardedInitBranch(IsUninitialized, AttemptInitBlock, EndBlock,
diff --git a/clang/test/CodeGenCXX/microsoft-abi-emulated-tls.cpp b/clang/test/CodeGenCXX/microsoft-abi-emulated-tls.cpp
new file mode 100644
index 0000000000000..0579660dbf5c2
--- /dev/null
+++ b/clang/test/CodeGenCXX/microsoft-abi-emulated-tls.cpp
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -std=c++11 \
+// RUN: -fms-compatibility-version=19.25 -femulated-tls \
+// RUN: -emit-llvm -o - %s | FileCheck \
+// RUN: --implicit-check-not=_Init_thread_epoch \
+// RUN: --implicit-check-not=__tls_guard %s
+
+int make_value();
+
+// CHECK-DAG: @"__tls_init$initializer$" = internal constant ptr @__tls_init, section ".CRT$XDU"
+// CHECK-LABEL: define dso_local noundef i32 @"?guarded_value@@YAHXZ"()
+// CHECK: br i1 true, label %[[ATTEMPT:[a-z.]+]], label %[[END:[a-z.]+]]
+// CHECK: [[ATTEMPT]]:
+// CHECK: call void @_Init_thread_header
+// CHECK: call void @_Init_thread_footer
+// CHECK: [[END]]:
+int guarded_value() {
+ static int value = make_value();
+ return value;
+}
+
+int make_tls_value();
+thread_local int dynamic_tls = make_tls_value();
+
+// CHECK-LABEL: define dso_local noundef i32 @"?read_dynamic_tls@@YAHXZ"()
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @__dyn_tls_on_demand_init()
+// CHECK: call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @"?dynamic_tls@@3HA")
+int read_dynamic_tls() { return dynamic_tls; }
More information about the cfe-commits
mailing list