From d527e52970d31a46e07a4de86019028cd5101620 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Thu, 20 Nov 2025 13:57:12 -0800 Subject: [PATCH 1/4] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 4 ++ clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 10 ++- clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 28 +++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 4 +- clang/lib/CIR/CodeGen/CIRGenModule.h | 3 + clang/test/CIR/CodeGen/thread-local.cpp | 62 +++++++++++++++++++ 7 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 clang/test/CIR/CodeGen/thread-local.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index e830444d24de..648c51521a10 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -347,6 +347,10 @@ class CIRGenCXXABI { // directly or require access through a thread wrapper function. virtual bool usesThreadWrapperFunction(const VarDecl *VD) const = 0; + virtual LValue EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, + const VarDecl *VD, + QualType LValType) = 0; + /// Emit the code to initialize hidden members required to handle virtual /// inheritance, if needed by the ABI. virtual void diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index a394d355a910..6d3c035e3901 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -503,7 +503,7 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &D, GV.setComdat(true); if (D.getTLSKind()) - llvm_unreachable("TLS mode is NYI"); + setTLSMode(GV, D); setGVProperties(GV, &D); diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index c3ad8a54684e..300be2f64394 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -937,7 +937,7 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &CGF, const Expr *E, // If it's thread_local, emit a call to its wrapper function instead. if (VD->getTLSKind() == VarDecl::TLS_Dynamic && CGF.CGM.getCXXABI().usesThreadWrapperFunction(VD)) - assert(0 && "not implemented"); + return CGF.CGM.getCXXABI().EmitThreadLocalVarDeclLValue(CGF, VD, T); // Check if the variable is marked as declare target with link clause in // device codegen. @@ -1095,8 +1095,12 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) { } // Handle threadlocal function locals. - if (VD->getTLSKind() != VarDecl::TLS_None) - llvm_unreachable("thread-local storage is NYI"); + if (VD->getTLSKind() != VarDecl::TLS_None) { + // TODO(cir): For more complex thread-local access patterns, we may need + // to wrap the address with a thread-local wrapper function (similar to + // LLVM's CreateThreadLocalAddress). For now, the GlobalOp is already + // marked as thread_local, which provides the correct semantics. + } // Check for OpenMP threadprivate variables. if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd && diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 8ba9c1501f12..7ab01b51ab22 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -318,6 +318,9 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { return !isEmittedWithConstantInitializer(VD) || mayNeedDestruction(VD); } + LValue EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, const VarDecl *VD, + QualType LValType) override; + bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) override { return true; } @@ -2925,3 +2928,28 @@ Address CIRGenARMCXXABI::initializeArrayCookie(CIRGenFunction &cgf, return Address(dataPtr, cgf.getBuilder().getUIntNTy(8), newPtr.getAlignment()); } + +LValue CIRGenItaniumCXXABI::EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, + const VarDecl *VD, + QualType LValType) { + // TODO(cir): For now, we're not implementing the full wrapper function + // mechanism. Instead, we rely on the fact that the global variable is + // already marked as thread_local, and we just access it directly. + // In the future, we may need to implement proper wrapper functions for + // dynamic TLS initialization similar to traditional CodeGen's + // getOrCreateThreadLocalWrapper. + + mlir::Value V = CGF.CGM.getAddrOfGlobalVar(VD); + + auto RealVarTy = CGF.convertTypeForMem(VD->getType()); + CharUnits Alignment = CGF.getContext().getDeclAlign(VD); + Address Addr(V, RealVarTy, Alignment); + + LValue LV; + if (VD->getType()->isReferenceType()) + LV = CGF.emitLoadOfReferenceLValue(Addr, CGF.getLoc(VD->getLocation()), + VD->getType(), AlignmentSource::Decl); + else + LV = CGF.makeAddrLValue(Addr, LValType, AlignmentSource::Decl); + return LV; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 302cfd646318..582b6f2a9074 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -1197,7 +1197,7 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, if (d->getTLSKind()) { if (d->getTLSKind() == VarDecl::TLS_Dynamic) - llvm_unreachable("NYI"); + CXXThreadLocals.push_back(d); setTLSMode(gv, *d); } @@ -1622,7 +1622,7 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *d, if (d->getTLSKind() && !gv.getTlsModelAttr()) { if (d->getTLSKind() == VarDecl::TLS_Dynamic) - llvm_unreachable("NYI"); + CXXThreadLocals.push_back(d); setTLSMode(gv, *d); } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 5c505cddf723..5c1c281dfe1c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -135,6 +135,9 @@ class CIRGenModule : public CIRGenTypeCache { /// for the same decl. llvm::DenseSet DiagnosedConflictingDefinitions; + /// thread_local variables defined or used in this TU. + std::vector CXXThreadLocals; + /// ------- /// Annotations /// ------- diff --git a/clang/test/CIR/CodeGen/thread-local.cpp b/clang/test/CIR/CodeGen/thread-local.cpp new file mode 100644 index 000000000000..80e10d1e8c11 --- /dev/null +++ b/clang/test/CIR/CodeGen/thread-local.cpp @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 -std=c++20 -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 -std=c++20 -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 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.og.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.og.ll %s + +// Test basic thread_local variable with constant initialization +thread_local int tls_const = 42; +// CIR: cir.global{{.*}}tls_dyn{{.*}}@tls_const = #cir.int<42> : !s32i +// LLVM: @tls_const = thread_local global i32 42 +// OGCG: @tls_const = thread_local global i32 42 + +// Test __thread (GNU-style) thread_local +__thread int tls_gnu_style = 10; +// CIR: cir.global{{.*}}tls_dyn{{.*}}@tls_gnu_style = #cir.int<10> : !s32i +// LLVM: @tls_gnu_style = thread_local global i32 10 +// OGCG: @tls_gnu_style = thread_local global i32 10 + +// Test thread_local function-local static (constant init) +int get_tls_static() { + thread_local int tls_func_static = 100; + return ++tls_func_static; +} +// CIR-LABEL: cir.func{{.*}}@_Z14get_tls_staticv +// CIR: cir.get_global{{.*}}@_ZZ14get_tls_staticvE15tls_func_static +// LLVM-LABEL: @_Z14get_tls_staticv +// LLVM: load{{.*}}@_ZZ14get_tls_staticvE15tls_func_static +// OGCG-LABEL: @_Z14get_tls_staticv + +// Test reading from thread_local variable +int read_tls() { + return tls_const; +} +// CIR-LABEL: cir.func{{.*}}@_Z8read_tlsv +// CIR: cir.get_global thread_local @tls_const +// LLVM-LABEL: @_Z8read_tlsv +// LLVM: @llvm.threadlocal.address.p0(ptr @tls_const) +// OGCG-LABEL: @_Z8read_tlsv +// OGCG: @llvm.threadlocal.address.p0(ptr{{.*}}@tls_const) + +// Test writing to thread_local variable +void write_tls(int val) { + tls_const = val; +} +// CIR-LABEL: cir.func{{.*}}@_Z9write_tlsi +// CIR: cir.get_global thread_local @tls_const +// CIR: cir.store +// LLVM-LABEL: @_Z9write_tlsi +// LLVM: @llvm.threadlocal.address.p0(ptr @tls_const) +// OGCG-LABEL: @_Z9write_tlsi +// OGCG: @llvm.threadlocal.address.p0(ptr{{.*}}@tls_const) + +// Test extern thread_local +extern thread_local int tls_extern; +int use_extern_tls() { + return tls_extern; +} +// CIR-LABEL: cir.func{{.*}}@_Z14use_extern_tlsv +// CIR: cir.get_global thread_local @tls_extern +// LLVM-LABEL: @_Z14use_extern_tlsv +// OGCG-LABEL: @_Z14use_extern_tlsv From 62f66f83596f9ddb9ce7cd934febdd459d782710 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Thu, 20 Nov 2025 14:43:30 -0800 Subject: [PATCH 2/4] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 8 ++++---- clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 300be2f64394..6460004a6f92 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -1095,11 +1095,11 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) { } // Handle threadlocal function locals. + // ClangIR marks the GlobalOp with thread_local, which gets lowered to + // @llvm.threadlocal.address intrinsics during LLVM lowering. This approach + // provides correct TLS semantics without requiring wrapper functions in CIR. if (VD->getTLSKind() != VarDecl::TLS_None) { - // TODO(cir): For more complex thread-local access patterns, we may need - // to wrap the address with a thread-local wrapper function (similar to - // LLVM's CreateThreadLocalAddress). For now, the GlobalOp is already - // marked as thread_local, which provides the correct semantics. + // Nothing additional needed - the GlobalOp is already thread_local. } // Check for OpenMP threadprivate variables. diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 7ab01b51ab22..1c6cb2d2de45 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -2932,12 +2932,12 @@ Address CIRGenARMCXXABI::initializeArrayCookie(CIRGenFunction &cgf, LValue CIRGenItaniumCXXABI::EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, const VarDecl *VD, QualType LValType) { - // TODO(cir): For now, we're not implementing the full wrapper function - // mechanism. Instead, we rely on the fact that the global variable is - // already marked as thread_local, and we just access it directly. - // In the future, we may need to implement proper wrapper functions for - // dynamic TLS initialization similar to traditional CodeGen's - // getOrCreateThreadLocalWrapper. + // ClangIR's approach to thread-local variables differs from traditional + // CodeGen. Traditional CodeGen creates wrapper functions (e.g., _ZTW*) that + // handle dynamic initialization. ClangIR instead marks the GlobalOp with + // tls_dyn and relies on LLVM lowering to insert @llvm.threadlocal.address + // intrinsics, which achieves the same semantics without intermediate wrapper + // functions in CIR. mlir::Value V = CGF.CGM.getAddrOfGlobalVar(VD); From 6a59834dd632628ad86280853b6e61511eb98019 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Thu, 20 Nov 2025 14:51:18 -0800 Subject: [PATCH 3/4] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 6460004a6f92..a6c86f5bc6ac 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -1097,7 +1097,8 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) { // Handle threadlocal function locals. // ClangIR marks the GlobalOp with thread_local, which gets lowered to // @llvm.threadlocal.address intrinsics during LLVM lowering. This approach - // provides correct TLS semantics without requiring wrapper functions in CIR. + // provides correct TLS semantics without requiring wrapper functions in + // CIR. if (VD->getTLSKind() != VarDecl::TLS_None) { // Nothing additional needed - the GlobalOp is already thread_local. } From 90fb5baa0a332dc9160bd2007999bb21c22cd4ae Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Thu, 20 Nov 2025 16:57:31 -0800 Subject: [PATCH 4/4] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 2 +- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 3 +- clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 30 +++++++++---------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index 648c51521a10..05dbcc4cd80c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -347,7 +347,7 @@ class CIRGenCXXABI { // directly or require access through a thread wrapper function. virtual bool usesThreadWrapperFunction(const VarDecl *VD) const = 0; - virtual LValue EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, + virtual LValue emitThreadLocalVarDeclLValue(CIRGenFunction &CGF, const VarDecl *VD, QualType LValType) = 0; diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index a6c86f5bc6ac..adfec6387ca4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -937,7 +937,7 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &CGF, const Expr *E, // If it's thread_local, emit a call to its wrapper function instead. if (VD->getTLSKind() == VarDecl::TLS_Dynamic && CGF.CGM.getCXXABI().usesThreadWrapperFunction(VD)) - return CGF.CGM.getCXXABI().EmitThreadLocalVarDeclLValue(CGF, VD, T); + return CGF.CGM.getCXXABI().emitThreadLocalVarDeclLValue(CGF, VD, T); // Check if the variable is marked as declare target with link clause in // device codegen. diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index cadf1a36ffaa..a1adea6cd172 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1802,8 +1802,7 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { assert(Qualifier && "member pointer without class qualifier"); const Type *QualifierType = Qualifier.getAsType(); assert(QualifierType && "member pointer qualifier is not a type"); - const CXXRecordDecl *derivedClass = - QualifierType->getAsCXXRecordDecl(); + const CXXRecordDecl *derivedClass = QualifierType->getAsCXXRecordDecl(); CharUnits offset = CGF.CGM.computeNonVirtualBaseClassOffset( derivedClass, CE->path_begin(), CE->path_end()); diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 1c6cb2d2de45..30da5a357f66 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -318,8 +318,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { return !isEmittedWithConstantInitializer(VD) || mayNeedDestruction(VD); } - LValue EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, const VarDecl *VD, - QualType LValType) override; + LValue emitThreadLocalVarDeclLValue(CIRGenFunction &cgf, const VarDecl *vd, + QualType lvalType) override; bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) override { return true; @@ -2929,9 +2929,9 @@ Address CIRGenARMCXXABI::initializeArrayCookie(CIRGenFunction &cgf, newPtr.getAlignment()); } -LValue CIRGenItaniumCXXABI::EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, - const VarDecl *VD, - QualType LValType) { +LValue CIRGenItaniumCXXABI::emitThreadLocalVarDeclLValue(CIRGenFunction &cgf, + const VarDecl *vd, + QualType lvalType) { // ClangIR's approach to thread-local variables differs from traditional // CodeGen. Traditional CodeGen creates wrapper functions (e.g., _ZTW*) that // handle dynamic initialization. ClangIR instead marks the GlobalOp with @@ -2939,17 +2939,17 @@ LValue CIRGenItaniumCXXABI::EmitThreadLocalVarDeclLValue(CIRGenFunction &CGF, // intrinsics, which achieves the same semantics without intermediate wrapper // functions in CIR. - mlir::Value V = CGF.CGM.getAddrOfGlobalVar(VD); + mlir::Value v = cgf.CGM.getAddrOfGlobalVar(vd); - auto RealVarTy = CGF.convertTypeForMem(VD->getType()); - CharUnits Alignment = CGF.getContext().getDeclAlign(VD); - Address Addr(V, RealVarTy, Alignment); + auto realVarTy = cgf.convertTypeForMem(vd->getType()); + CharUnits alignment = cgf.getContext().getDeclAlign(vd); + Address addr(v, realVarTy, alignment); - LValue LV; - if (VD->getType()->isReferenceType()) - LV = CGF.emitLoadOfReferenceLValue(Addr, CGF.getLoc(VD->getLocation()), - VD->getType(), AlignmentSource::Decl); + LValue lv; + if (vd->getType()->isReferenceType()) + lv = cgf.emitLoadOfReferenceLValue(addr, cgf.getLoc(vd->getLocation()), + vd->getType(), AlignmentSource::Decl); else - LV = CGF.makeAddrLValue(Addr, LValType, AlignmentSource::Decl); - return LV; + lv = cgf.makeAddrLValue(addr, lvalType, AlignmentSource::Decl); + return lv; }