[clang] 072e2a7 - [MS] Implement on-demand TLS initialization for Microsoft CXX ABI

David Majnemer via cfe-commits cfe-commits at lists.llvm.org
Thu Jan 13 21:24:14 PST 2022


Author: Maurice Heumann
Date: 2022-01-13T21:23:23-08:00
New Revision: 072e2a7c67b74b8bde35f78ac0b0b13a269380f8

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

LOG: [MS] Implement on-demand TLS initialization for Microsoft CXX ABI

TLS initializers, for example constructors of thread-local variables, don't necessarily get called. If a thread was created before a module is loaded, the module's TLS initializers are not executed for this particular thread.

This is why Microsoft added support for dynamic TLS initialization. Before every use of thread-local variables, a check is added that runs the module's TLS initializers on-demand.

To do this, the method `__dyn_tls_on_demand_init` gets called. Internally, it simply calls `__dyn_tls_init`.

No additional TLS initializer that sets the guard needs to be emitted, as the guard always gets set by `__dyn_tls_init`.
The guard is also checked again within `__dyn_tls_init`. This makes our check redundant, however, as Microsoft's compiler also emits this check, the behaviour is adopted here.

Reviewed By: majnemer

Differential Revision: https://reviews.llvm.org/D115456

Added: 
    

Modified: 
    clang/include/clang/Basic/LangOptions.h
    clang/lib/CodeGen/CGCXXABI.cpp
    clang/lib/CodeGen/CGCXXABI.h
    clang/lib/CodeGen/ItaniumCXXABI.cpp
    clang/lib/CodeGen/MicrosoftCXXABI.cpp
    clang/test/CodeGenCXX/ms-thread_local.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 35b33c2e09716..09afa641acf9b 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -124,6 +124,7 @@ class LangOptions : public LangOptionsBase {
     MSVC2017_5 = 1912,
     MSVC2017_7 = 1914,
     MSVC2019 = 1920,
+    MSVC2019_5 = 1925,
     MSVC2019_8 = 1928,
   };
 

diff  --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index 9714730e3c4bf..0b441e382f11c 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -154,6 +154,51 @@ void CGCXXABI::setCXXABIThisValue(CodeGenFunction &CGF, llvm::Value *ThisPtr) {
   CGF.CXXABIThisValue = ThisPtr;
 }
 
+bool CGCXXABI::mayNeedDestruction(const VarDecl *VD) const {
+  if (VD->needsDestruction(getContext()))
+    return true;
+
+  // If the variable has an incomplete class type (or array thereof), it
+  // might need destruction.
+  const Type *T = VD->getType()->getBaseElementTypeUnsafe();
+  if (T->getAs<RecordType>() && T->isIncompleteType())
+    return true;
+
+  return false;
+}
+
+bool CGCXXABI::isEmittedWithConstantInitializer(
+    const VarDecl *VD, bool InspectInitForWeakDef) const {
+  VD = VD->getMostRecentDecl();
+  if (VD->hasAttr<ConstInitAttr>())
+    return true;
+
+  // All later checks examine the initializer specified on the variable. If
+  // the variable is weak, such examination would not be correct.
+  if (!InspectInitForWeakDef && (VD->isWeak() || VD->hasAttr<SelectAnyAttr>()))
+    return false;
+
+  const VarDecl *InitDecl = VD->getInitializingDeclaration();
+  if (!InitDecl)
+    return false;
+
+  // If there's no initializer to run, this is constant initialization.
+  if (!InitDecl->hasInit())
+    return true;
+
+  // If we have the only definition, we don't need a thread wrapper if we
+  // will emit the value as a constant.
+  if (isUniqueGVALinkage(getContext().GetGVALinkageForVariable(VD)))
+    return !mayNeedDestruction(VD) && InitDecl->evaluateValue();
+
+  // Otherwise, we need a thread wrapper unless we know that every
+  // translation unit will emit the value as a constant. We rely on the
+  // variable being constant-initialized in every translation unit if it's
+  // constant-initialized in any translation unit, which isn't actually
+  // guaranteed by the standard but is necessary for sanity.
+  return InitDecl->hasConstantInitialization();
+}
+
 void CGCXXABI::EmitReturnFromThunk(CodeGenFunction &CGF,
                                    RValue RV, QualType ResultType) {
   assert(!CGF.hasAggregateEvaluationKind(ResultType) &&

diff  --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index e94e19bab0ad1..b96222b3ce280 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -79,6 +79,18 @@ class CGCXXABI {
 
   ASTContext &getContext() const { return CGM.getContext(); }
 
+  bool mayNeedDestruction(const VarDecl *VD) const;
+
+  /// Determine whether we will definitely emit this variable with a constant
+  /// initializer, either because the language semantics demand it or because
+  /// we know that the initializer is a constant.
+  // For weak definitions, any initializer available in the current translation
+  // is not necessarily reflective of the initializer used; such initializers
+  // are ignored unless if InspectInitForWeakDef is true.
+  bool
+  isEmittedWithConstantInitializer(const VarDecl *VD,
+                                   bool InspectInitForWeakDef = false) const;
+
   virtual bool requiresArrayCookie(const CXXDeleteExpr *E, QualType eltType);
   virtual bool requiresArrayCookie(const CXXNewExpr *E);
 

diff  --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index ce84430dd743e..6102046805503 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -334,59 +334,6 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
       ArrayRef<llvm::Function *> CXXThreadLocalInits,
       ArrayRef<const VarDecl *> CXXThreadLocalInitVars) override;
 
-  bool mayNeedDestruction(const VarDecl *VD) const {
-    if (VD->needsDestruction(getContext()))
-      return true;
-
-    // If the variable has an incomplete class type (or array thereof), it
-    // might need destruction.
-    const Type *T = VD->getType()->getBaseElementTypeUnsafe();
-    if (T->getAs<RecordType>() && T->isIncompleteType())
-      return true;
-
-    return false;
-  }
-
-  /// Determine whether we will definitely emit this variable with a constant
-  /// initializer, either because the language semantics demand it or because
-  /// we know that the initializer is a constant.
-  // For weak definitions, any initializer available in the current translation
-  // is not necessarily reflective of the initializer used; such initializers
-  // are ignored unless if InspectInitForWeakDef is true.
-  bool
-  isEmittedWithConstantInitializer(const VarDecl *VD,
-                                   bool InspectInitForWeakDef = false) const {
-    VD = VD->getMostRecentDecl();
-    if (VD->hasAttr<ConstInitAttr>())
-      return true;
-
-    // All later checks examine the initializer specified on the variable. If
-    // the variable is weak, such examination would not be correct.
-    if (!InspectInitForWeakDef &&
-        (VD->isWeak() || VD->hasAttr<SelectAnyAttr>()))
-      return false;
-
-    const VarDecl *InitDecl = VD->getInitializingDeclaration();
-    if (!InitDecl)
-      return false;
-
-    // If there's no initializer to run, this is constant initialization.
-    if (!InitDecl->hasInit())
-      return true;
-
-    // If we have the only definition, we don't need a thread wrapper if we
-    // will emit the value as a constant.
-    if (isUniqueGVALinkage(getContext().GetGVALinkageForVariable(VD)))
-      return !mayNeedDestruction(VD) && InitDecl->evaluateValue();
-
-    // Otherwise, we need a thread wrapper unless we know that every
-    // translation unit will emit the value as a constant. We rely on the
-    // variable being constant-initialized in every translation unit if it's
-    // constant-initialized in any translation unit, which isn't actually
-    // guaranteed by the standard but is necessary for sanity.
-    return InitDecl->hasConstantInitialization();
-  }
-
   bool usesThreadWrapperFunction(const VarDecl *VD) const override {
     return !isEmittedWithConstantInitializer(VD) ||
            mayNeedDestruction(VD);

diff  --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 5971a77093049..e00ff2b68719b 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -401,7 +401,9 @@ class MicrosoftCXXABI : public CGCXXABI {
       ArrayRef<const VarDecl *> CXXThreadLocalInitVars) override;
 
   bool usesThreadWrapperFunction(const VarDecl *VD) const override {
-    return false;
+    return getContext().getLangOpts().isCompatibleWithMSVC(
+               LangOptions::MSVC2019_5) &&
+           (!isEmittedWithConstantInitializer(VD) || mayNeedDestruction(VD));
   }
   LValue EmitThreadLocalVarDeclLValue(CodeGenFunction &CGF, const VarDecl *VD,
                                       QualType LValType) override;
@@ -2397,11 +2399,97 @@ void MicrosoftCXXABI::EmitThreadLocalInitFuncs(
   }
 }
 
+static llvm::GlobalValue *getTlsGuardVar(CodeGenModule &CGM) {
+  // __tls_guard comes from the MSVC runtime and reflects
+  // whether TLS has been initialized for a particular thread.
+  // It is set from within __dyn_tls_init by the runtime.
+  // Every library and executable has its own variable.
+  llvm::Type *VTy = llvm::Type::getInt8Ty(CGM.getLLVMContext());
+  llvm::Constant *TlsGuardConstant =
+      CGM.CreateRuntimeVariable(VTy, "__tls_guard");
+  llvm::GlobalValue *TlsGuard = cast<llvm::GlobalValue>(TlsGuardConstant);
+
+  TlsGuard->setThreadLocal(true);
+
+  return TlsGuard;
+}
+
+static llvm::FunctionCallee getDynTlsOnDemandInitFn(CodeGenModule &CGM) {
+  // __dyn_tls_on_demand_init comes from the MSVC runtime and triggers
+  // dynamic TLS initialization by calling __dyn_tls_init internally.
+  llvm::FunctionType *FTy =
+      llvm::FunctionType::get(llvm::Type::getVoidTy(CGM.getLLVMContext()), {},
+                              /*isVarArg=*/false);
+  return CGM.CreateRuntimeFunction(
+      FTy, "__dyn_tls_on_demand_init",
+      llvm::AttributeList::get(CGM.getLLVMContext(),
+                               llvm::AttributeList::FunctionIndex,
+                               llvm::Attribute::NoUnwind),
+      /*Local=*/true);
+}
+
+static void emitTlsGuardCheck(CodeGenFunction &CGF, llvm::GlobalValue *TlsGuard,
+                              llvm::BasicBlock *DynInitBB,
+                              llvm::BasicBlock *ContinueBB) {
+  llvm::LoadInst *TlsGuardValue =
+      CGF.Builder.CreateLoad(Address(TlsGuard, CharUnits::One()));
+  llvm::Value *CmpResult =
+      CGF.Builder.CreateICmpEQ(TlsGuardValue, CGF.Builder.getInt8(0));
+  CGF.Builder.CreateCondBr(CmpResult, DynInitBB, ContinueBB);
+}
+
+static void emitDynamicTlsInitializationCall(CodeGenFunction &CGF,
+                                             llvm::GlobalValue *TlsGuard,
+                                             llvm::BasicBlock *ContinueBB) {
+  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) {
+  llvm::BasicBlock *DynInitBB =
+      CGF.createBasicBlock("dyntls.dyn_init", CGF.CurFn);
+  llvm::BasicBlock *ContinueBB =
+      CGF.createBasicBlock("dyntls.continue", CGF.CurFn);
+
+  llvm::GlobalValue *TlsGuard = getTlsGuardVar(CGF.CGM);
+
+  emitTlsGuardCheck(CGF, TlsGuard, DynInitBB, ContinueBB);
+  CGF.Builder.SetInsertPoint(DynInitBB);
+  emitDynamicTlsInitializationCall(CGF, TlsGuard, ContinueBB);
+  CGF.Builder.SetInsertPoint(ContinueBB);
+}
+
 LValue MicrosoftCXXABI::EmitThreadLocalVarDeclLValue(CodeGenFunction &CGF,
                                                      const VarDecl *VD,
                                                      QualType LValType) {
-  CGF.CGM.ErrorUnsupported(VD, "thread wrappers");
-  return LValue();
+  // Dynamic TLS initialization works by checking the state of a
+  // guard variable (__tls_guard) to see whether TLS initialization
+  // for a thread has happend yet.
+  // If not, the initialization is triggered on-demand
+  // by calling __dyn_tls_on_demand_init.
+  emitDynamicTlsInitialization(CGF);
+
+  // Emit the variable just like any regular global variable.
+
+  llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(VD);
+  llvm::Type *RealVarTy = CGF.getTypes().ConvertTypeForMem(VD->getType());
+
+  unsigned AS = cast<llvm::PointerType>(V->getType())->getAddressSpace();
+  V = CGF.Builder.CreateBitCast(V, RealVarTy->getPointerTo(AS));
+
+  CharUnits Alignment = CGF.getContext().getDeclAlign(VD);
+  Address Addr(V, Alignment);
+
+  LValue LV = VD->getType()->isReferenceType()
+                  ? CGF.EmitLoadOfReferenceLValue(Addr, VD->getType(),
+                                                  AlignmentSource::Decl)
+                  : CGF.MakeAddrLValue(Addr, LValType, AlignmentSource::Decl);
+  return LV;
 }
 
 static ConstantAddress getInitThreadEpochPtr(CodeGenModule &CGM) {

diff  --git a/clang/test/CodeGenCXX/ms-thread_local.cpp b/clang/test/CodeGenCXX/ms-thread_local.cpp
index 3341e4f573888..59994bee82ede 100644
--- a/clang/test/CodeGenCXX/ms-thread_local.cpp
+++ b/clang/test/CodeGenCXX/ms-thread_local.cpp
@@ -1,5 +1,6 @@
-// RUN: %clang_cc1 %s -std=c++1y -triple=i686-pc-win32 -emit-llvm -o - | FileCheck %s
-// RUN: %clang_cc1 %s  -std=c++1y -triple=i686-pc-win32 -ftls-model=local-dynamic -emit-llvm -o - | FileCheck %s -check-prefix=CHECK-LD
+// RUN: %clang_cc1 %s -std=c++1y -triple=i686-pc-win32 -fms-compatibility-version=19.25 -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -std=c++1y -triple=i686-pc-win32 -fms-compatibility-version=19.20 -emit-llvm -o - | FileCheck %s -check-prefix=CHECK-LEGACY
+// RUN: %clang_cc1 %s -std=c++1y -triple=i686-pc-win32 -fms-compatibility-version=19.25 -ftls-model=local-dynamic -emit-llvm -o - | FileCheck %s -check-prefix=CHECK-LD
 
 struct A {
   A();
@@ -17,14 +18,22 @@ thread_local A a = A();
 
 // CHECK-DAG: @"?b@@3UA@@A" = dso_local thread_local global %struct.A zeroinitializer, align 1
 // CHECK-DAG: @"__tls_init$initializer$" = internal constant void ()* @__tls_init, section ".CRT$XDU"
+// CHECK-DAG: @__tls_guard = external dso_local thread_local global i8
 // CHECK-LD-DAG: @"?b@@3UA@@A" = dso_local thread_local(localdynamic) global %struct.A zeroinitializer, align 1
 // CHECK-LD-DAG: @"__tls_init$initializer$" = internal constant void ()* @__tls_init, section ".CRT$XDU"
+// CHECK-LD-DAG: @__tls_guard = external dso_local thread_local global i8
+// CHECK-LEGACY-NOT: @__tls_guard = external dso_local thread_local global i8
 thread_local A b;
 
-// CHECK-LABEL: define internal void @__tls_init()
-// CHECK: call void @"??__Eb@@YAXXZ"
-// CHECK-LD-LABEL: define internal void @__tls_init()
-// CHECK-LD: call void @"??__Eb@@YAXXZ"
+// CHECK-LABEL: declare dso_local void @__dyn_tls_on_demand_init()
+// CHECK-LD-LABEL: declare dso_local void @__dyn_tls_on_demand_init()
+// CHECK-LEGACY-NOT: declare dso_local void @__dyn_tls_on_demand_init()
+
+// CHECK-LABEL: define dso_local void @"?f@@YA?AUA@@XZ"(%struct.A* noalias sret(%struct.A) align 1 %agg.result)
+// CHECK: call void @__dyn_tls_on_demand_init()
+// CHECK-LD-LABEL: define dso_local void @"?f@@YA?AUA@@XZ"(%struct.A* noalias sret(%struct.A) align 1 %agg.result)
+// CHECK-LD: call void @__dyn_tls_on_demand_init()
+// CHECK-LEGACY-NOT: call void @__dyn_tls_on_demand_init()
 
 thread_local A &c = b;
 thread_local A &d = c;
@@ -35,6 +44,22 @@ A f() {
   return c;
 }
 
+// CHECK-LABEL: define dso_local i32 @"?g@@YAHXZ"()
+// CHECK-NOT: call void @__dyn_tls_on_demand_init()
+// CHECK-LD-LABEL: define dso_local i32 @"?g@@YAHXZ"()
+// CHECK-LD-NOT: call void @__dyn_tls_on_demand_init()
+
+thread_local int e = 2;
+
+int g() {
+  return e;
+}
+
+// CHECK-LABEL: define internal void @__tls_init()
+// CHECK: call void @"??__Eb@@YAXXZ"
+// CHECK-LD-LABEL: define internal void @__tls_init()
+// CHECK-LD: call void @"??__Eb@@YAXXZ"
+
 // CHECK: !llvm.linker.options = !{![[dyn_tls_init:[0-9]+]]}
 // CHECK: ![[dyn_tls_init]] = !{!"/include:___dyn_tls_init at 12"}
 // CHECK-LD: !llvm.linker.options = !{![[dyn_tls_init:[0-9]+]]}


        


More information about the cfe-commits mailing list