Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
11 changes: 8 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 &&
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
28 changes: 28 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class CIRGenModule : public CIRGenTypeCache {
/// for the same decl.
llvm::DenseSet<clang::GlobalDecl> DiagnosedConflictingDefinitions;

/// thread_local variables defined or used in this TU.
std::vector<const clang::VarDecl *> CXXThreadLocals;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be lowercase too!


/// -------
/// Annotations
/// -------
Expand Down
65 changes: 65 additions & 0 deletions clang/test/CIR/CodeGen/thread-local.cpp
Original file line number Diff line number Diff line change
@@ -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()