diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index e830444d24de..05dbcc4cd80c 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..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)) - 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,13 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) { } // Handle threadlocal function locals. - if (VD->getTLSKind() != VarDecl::TLS_None) - llvm_unreachable("thread-local storage is NYI"); + // 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) { + // Nothing additional needed - the GlobalOp is already thread_local. + } // Check for OpenMP threadprivate variables. if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd && 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 8ba9c1501f12..30da5a357f66 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) { + // 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); + + 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..6c1e1832c491 --- /dev/null +++ b/clang/test/CIR/CodeGen/thread-local.cpp @@ -0,0 +1,65 @@ +// 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 +// OGCG: @llvm.threadlocal.address.p0(ptr{{.*}}@_ZZ14get_tls_staticvE15tls_func_static) + +// 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 +// LLVM: @llvm.threadlocal.address.p0(ptr @tls_extern) +// OGCG-LABEL: @_Z14use_extern_tlsv +// OGCG: call ptr @_ZTW10tls_extern()