https://github.com/AhmedKamel10 updated 
https://github.com/llvm/llvm-project/pull/219780

>From 3fc9fd22b4ad358fd37b66550170fb49792d0923 Mon Sep 17 00:00:00 2001
From: ahmedkamel10 <[email protected]>
Date: Sun, 30 Aug 2026 09:50:50 +0300
Subject: [PATCH 1/2] [Clang] Prevent thread_local from creating tentative
 definitions (#217636)

---
 clang/lib/AST/Decl.cpp                        | 21 +++++++++-------
 clang/lib/Sema/SemaDecl.cpp                   |  8 ++++++-
 clang/test/Sema/c23-thread-local-definition.c | 24 +++++++++++++++++++
 3 files changed, 44 insertions(+), 9 deletions(-)
 create mode 100644 clang/test/Sema/c23-thread-local-definition.c

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b145560508e956..a4f7560db19ca3 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2309,16 +2309,21 @@ 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.
-  // No such thing in C++.
-  if (!C.getLangOpts().CPlusPlus && isFileVarDecl())
+  // 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.
+  // In C23 and later, file-scope thread_local / _Thread_local / __thread
+  // declarations without initializers are full external definitions
+  // (C23 6.9.3p1). Pre-C23 standards allowed tentative TLS definitions, so we
+  // preserve that behavior in earlier language modes.
+  if (!C.getLangOpts().CPlusPlus && isFileVarDecl() &&
+      (getTLSKind() == TLS_None || !C.getLangOpts().C23))
     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, in C23, 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 094601a58d5085..dbf623a4874acd 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/Sema/c23-thread-local-definition.c 
b/clang/test/Sema/c23-thread-local-definition.c
new file mode 100644
index 00000000000000..cb5aeea2feb40e
--- /dev/null
+++ b/clang/test/Sema/c23-thread-local-definition.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=pre23 %s
+// pre23-no-diagnostics
+
+_Thread_local int a;     // expected-note {{previous definition is here}}
+_Thread_local int a;     // expected-error {{redefinition of 'a'}}
+
+static _Thread_local int b; // expected-note {{previous definition is here}}
+static _Thread_local int b; // expected-error {{redefinition of 'b'}}
+
+__thread int c;          // expected-note {{previous definition is here}}
+__thread int c;          // expected-error {{redefinition of 'c'}}
+
+// 'extern' makes these declarations, not definitions.
+extern _Thread_local int e;
+_Thread_local int e;
+
+_Thread_local int f;
+extern _Thread_local int f;
+
+#if __STDC_VERSION__ >= 202311L
+thread_local int x, x;   // expected-error {{redefinition of 'x'}} \
+                         // expected-note {{previous definition is here}}
+#endif

>From abecb99fa87e0da49450e970a83f2b99ef76ab52 Mon Sep 17 00:00:00 2001
From: ahmedkamel10 <[email protected]>
Date: Tue, 6 Oct 2026 18:53:47 +0300
Subject: [PATCH 2/2] Add release note

---
 clang/docs/ReleaseNotes.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7ec126a065ae5a..ba13120cb10756 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -603,7 +603,8 @@ features cannot lower the translation-unit ABI level;
 - No longer crashing due to follow-on diagnostics when there is an invalid 
operand in a logical operator involving a vector operand. (#GH227588)
 - Fixed assertion failures caused by stale linkage information when an extern 
variable or function declaration is merged with a preceding static declaration. 
(#GH204759, #GH204754)
 - Fixed a crash due to typo correction mishandling custom keywords 
`_virtual_inheritance` and `_multiple_inheritance` in `-fms-compatibility` 
mode. (#GH228003)
-  
+- Clang no longer treats a file-scope `thread_local` declaration without an 
initializer as a tentative definition in C23 mode. As specified by C23 6.9.3, 
such a declaration is a definition, so declaring the same variable more that 
once is now diagnosed as a redefinition. (#GH217636)
+
 #### Bug Fixes to Compiler Builtins
 
 - Fixed a crash when classifying a call to a builtin with dependent arguments,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to