[clang] Fix thread_local incorrectly treated as tentative definition (fixes #217636) (PR #219780)
ahmed mohamed kamel via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 4 15:28:46 PDT 2026
https://github.com/AhmedKamel10 updated https://github.com/llvm/llvm-project/pull/219780
>From 5eaf3f6e46cefc81bcb5bf60b0f48d0e784f16e0 Mon Sep 17 00:00:00 2001
From: ahmedkamel10 <amkzaher at gmail.com>
Date: Sun, 30 Aug 2026 09:50:50 +0300
Subject: [PATCH] [Clang] Prevent thread_local from creating tentative
definitions (#217636)
---
clang/lib/AST/Decl.cpp | 18 +++++++++++-------
clang/lib/Sema/SemaDecl.cpp | 8 +++++++-
.../test/CodeGen/PowerPC/toc-data-attribute.c | 3 +--
.../CodeGen/PowerPC/toc-data-diagnostics.c | 2 +-
clang/test/CodeGen/thread-specifier.c | 4 ++--
clang/test/Sema/PowerPC/aix-attr-tls_model.c | 8 ++++----
clang/test/Sema/attr-tls_model.c | 10 ++++++----
clang/test/Sema/tentative-decls.c | 3 +++
8 files changed, 35 insertions(+), 21 deletions(-)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b145560508e95..afc53ff5cc3f2 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2309,16 +2309,20 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
if (isSingleLineLanguageLinkage(*this))
return DeclarationOnly;
- // C99 6.9.2p2:
- // A declaration of an object that has file scope without an initializer,
- // and without a storage class specifier or the scs 'static', constitutes
- // a tentative definition.
+ // C23 6.9.3p2:
+ // A declaration of an identifier for an object that has file scope
+ // without an initializer, and without the storage-class specifier extern
+ // or thread_local, constitutes a tentative definition.
+ // File-scope thread_local objects are instead external definitions (C23
+ // 6.9.3p1). Earlier standards have no thread_local, but we apply the rule
+ // in every C mode, including for __thread.
// No such thing in C++.
- if (!C.getLangOpts().CPlusPlus && isFileVarDecl())
+ if (!C.getLangOpts().CPlusPlus && isFileVarDecl() && getTLSKind() == TLS_None)
return TentativeDefinition;
- // What's left is (in C, block-scope) declarations without initializers or
- // external storage. These are definitions.
+ // What's left is (in C) block-scope declarations and file-scope thread_local
+ // declarations without initializers or external storage. These are
+ // definitions.
return Definition;
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 094601a58d508..dbf623a4874ac 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4976,7 +4976,13 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
// [basic.def]p2 for details, but the basic idea is: if the old declaration
// contains the extern specifier and doesn't have an initializer, it's fine
// in C++.
- if (Old->getStorageClass() != SC_Extern || Old->hasInit()) {
+ if (New->getTLSKind() != VarDecl::TLS_None &&
+ New->isThisDeclarationADefinition() == VarDecl::Definition) {
+ VarDecl *Def = Old->getDefinition();
+ if (Def && checkVarDeclRedefinition(Def, New)) {
+ return;
+ }
+ } else if (Old->getStorageClass() != SC_Extern || Old->hasInit()) {
Diag(New->getLocation(), diag::warn_cxx_compat_tentative_definition)
<< New;
Diag(Old->getLocation(), diag::note_previous_declaration);
diff --git a/clang/test/CodeGen/PowerPC/toc-data-attribute.c b/clang/test/CodeGen/PowerPC/toc-data-attribute.c
index d3a2d43b064ff..7c2459cb2588f 100644
--- a/clang/test/CodeGen/PowerPC/toc-data-attribute.c
+++ b/clang/test/CodeGen/PowerPC/toc-data-attribute.c
@@ -41,10 +41,9 @@ struct SomeStruct* getAddress(void) {
// CHECK32: @l = global double 2.500000e+00, align 8
// CHECK64: @l = global double 2.500000e+00, align 8 #0
// COMMON: @m = global i32 10, section "foo", align 4
+// COMMON: @n = thread_local global i32 0, align 4
// COMMON: @f = external global i32, align 4 #0
// COMMON: @o = external global %struct.SomeStruct, align 1
// CHECK32: @i = global ptr null, align 4 #0
// CHECK64: @i = global ptr null, align 8 #0
-// COMMON: @n = thread_local global i32 0, align 4
// COMMON: @p = external global [0 x i32], align 4
-// COMMON: attributes #0 = { "toc-data" }
diff --git a/clang/test/CodeGen/PowerPC/toc-data-diagnostics.c b/clang/test/CodeGen/PowerPC/toc-data-diagnostics.c
index 169c355136993..77e9d399288c0 100644
--- a/clang/test/CodeGen/PowerPC/toc-data-diagnostics.c
+++ b/clang/test/CodeGen/PowerPC/toc-data-diagnostics.c
@@ -56,10 +56,10 @@ long long test2() {
// CHECK: @c = global i32 0, align 128
// CHECK: @d = global double 2.500000e+00, align 8
// CHECK: @e = global i32 10, section "foo", align 4
+// CHECK: @f = thread_local global i32 0, align 4
// CHECK: @globalOneWithAlias = global i32 10, align 4
// CHECK: @globalTwoWithAlias = global i32 20, align 4
// CHECK: @a = global %struct.large_struct zeroinitializer, align 4
-// CHECK: @f = thread_local global i32 0, align 4
// CHECK: @h = external global [0 x i32], align 4
// CHECK: @g = external global %struct.SomeStruct, align 1
// CHECK: @test2.ll = internal global i64 5, align 8
diff --git a/clang/test/CodeGen/thread-specifier.c b/clang/test/CodeGen/thread-specifier.c
index 18a14f912aba4..ed8542cafa92d 100644
--- a/clang/test/CodeGen/thread-specifier.c
+++ b/clang/test/CodeGen/thread-specifier.c
@@ -2,15 +2,15 @@
// RUN: %clang_cc1 -triple riscv32 -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple riscv64 -emit-llvm -o - %s | FileCheck %s
+// CHECK: @a ={{.*}} thread_local global
// CHECK: @b = external thread_local global
// CHECK: @d.e = internal thread_local global
// CHECK: @d.f = internal thread_local global
-// CHECK: @f.a = internal thread_local(initialexec) global
-// CHECK: @a ={{.*}} thread_local global
// CHECK: @g ={{.*}} thread_local global
// CHECK: @h ={{.*}} thread_local(localdynamic) global
// CHECK: @i ={{.*}} thread_local(initialexec) global
// CHECK: @j ={{.*}} thread_local(localexec) global
+// CHECK: @f.a = internal thread_local(initialexec) global
// CHECK-NOT: @_ZTW
// CHECK-NOT: @_ZTH
diff --git a/clang/test/Sema/PowerPC/aix-attr-tls_model.c b/clang/test/Sema/PowerPC/aix-attr-tls_model.c
index 7c2047bced939..616f5526932fd 100644
--- a/clang/test/Sema/PowerPC/aix-attr-tls_model.c
+++ b/clang/test/Sema/PowerPC/aix-attr-tls_model.c
@@ -5,7 +5,7 @@
#error "Should support tls_model attribute"
#endif
-static __thread int y __attribute((tls_model("global-dynamic"))); // no-warning
-static __thread int y __attribute((tls_model("local-dynamic"))); // expected-no-diagnostics
-static __thread int y __attribute((tls_model("initial-exec"))); // no-warning
-static __thread int y __attribute((tls_model("local-exec"))); // no-warning
+static __thread int y_gd __attribute((tls_model("global-dynamic"))); // no-warning
+static __thread int y_ld __attribute((tls_model("local-dynamic"))); // expected-no-diagnostics
+static __thread int y_ie __attribute((tls_model("initial-exec"))); // no-warning
+static __thread int y_le __attribute((tls_model("local-exec"))); // no-warning
\ No newline at end of file
diff --git a/clang/test/Sema/attr-tls_model.c b/clang/test/Sema/attr-tls_model.c
index ec99c7aacaa3a..dfc94e663ea13 100644
--- a/clang/test/Sema/attr-tls_model.c
+++ b/clang/test/Sema/attr-tls_model.c
@@ -7,8 +7,10 @@
int f(void) __attribute((tls_model("global-dynamic"))); // expected-error {{'tls_model' attribute only applies to thread-local variables}}
int x __attribute((tls_model("global-dynamic"))); // expected-error {{'tls_model' attribute only applies to thread-local variables}}
-static __thread int y __attribute((tls_model("global-dynamic"))); // no-warning
+static __thread int y_ok __attribute((tls_model("global-dynamic"))); // no-warning
+
+static __thread int y_two_args __attribute((tls_model("local", "dynamic"))); // expected-error {{'tls_model' attribute takes one argument}}
+static __thread int y_not_str __attribute((tls_model(123))); // expected-error {{expected string literal as argument of 'tls_model' attribute}}
+static __thread int y_bad_model __attribute((tls_model("foobar"))); // expected-error {{tls_model must be "global-dynamic", "local-dynamic", "initial-exec" or "local-exec"}}
+
-static __thread int y __attribute((tls_model("local", "dynamic"))); // expected-error {{'tls_model' attribute takes one argument}}
-static __thread int y __attribute((tls_model(123))); // expected-error {{expected string literal as argument of 'tls_model' attribute}}
-static __thread int y __attribute((tls_model("foobar"))); // expected-error {{tls_model must be "global-dynamic", "local-dynamic", "initial-exec" or "local-exec"}}
diff --git a/clang/test/Sema/tentative-decls.c b/clang/test/Sema/tentative-decls.c
index 94d21bdbf94da..3d2dd147905cc 100644
--- a/clang/test/Sema/tentative-decls.c
+++ b/clang/test/Sema/tentative-decls.c
@@ -63,3 +63,6 @@ static int b0;
static int a0[] = { 4 };
static int b0 = 5;
+
+_Thread_local int tls_redef; // expected-note {{previous definition is here}}
+_Thread_local int tls_redef; // expected-error {{redefinition of 'tls_redef'}}
More information about the cfe-commits
mailing list