[committed] c: Do not allow thread-local tentative definitions for C2x

Message ID 37764f8a-a164-bd7c-79d-ef4913becf74@codesourcery.com
State Accepted
Headers
Series [committed] c: Do not allow thread-local tentative definitions for C2x |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Joseph Myers May 19, 2023, 12:42 a.m. UTC
  C2x makes it clear that thread-local declarations can never be
tentative definitions (the legacy feature of C where you can e.g. do
"int i;" more than once at file scope, possibly with one of the
declarations initialized, and it counts as exactly one definition),
but are always definitions in the absence of "extern".  The wording
about external definitions was unclear in the thread-local case in C11
/ C17 (both about what counts as a tentative definition, and what is a
"definition" at all), not having been updated to cover the addition of
thread-local storage.

Implement this C2x requirement.  Arguably this is a defect fix that
would be appropriate to apply for all standard versions, but for now
the change is conditional on flag_isoc2x (however, it doesn't handle
_Thread_local / thread_local any different from GNU __thread).  Making
the change unconditional results in various TLS tests failing to
compile (gcc.dg/c11-thread-local-1.c gcc.dg/tls/thr-init-1.c
gcc.dg/tls/thr-init-2.c gcc.dg/torture/tls/thr-init-2.c
objc.dg/torture/tls/thr-init.m), though it's not clear if those tests
reflect any real code similarly trying to make use of thread-local
tentative definitions.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.

gcc/c/
	* c-decl.cc (diagnose_mismatched_decls): Do not handle
	thread-local declarations as tentative definitions for C2x.
	(finish_decl): Do not allow thread-local definition with
	incomplete type for C2x.

gcc/testsuite/
	* gcc.dg/c2x-thread-local-2.c: New test.
  

Patch

diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 945e45bff89..b5b491cf2da 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -2442,8 +2442,20 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 	  return false;
 	}
 
-      /* Multiple initialized definitions are not allowed (6.9p3,5).  */
-      if (DECL_INITIAL (newdecl) && DECL_INITIAL (olddecl))
+      /* Multiple initialized definitions are not allowed (6.9p3,5).
+	 For this purpose, C2x makes it clear that thread-local
+	 declarations without extern are definitions, not tentative
+	 definitions, whether or not they have initializers.  The
+	 wording before C2x was unclear; literally it would have made
+	 uninitialized thread-local declarations into tentative
+	 definitions only if they also used static, but without saying
+	 explicitly whether or not other cases count as
+	 definitions at all.  */
+      if ((DECL_INITIAL (newdecl) && DECL_INITIAL (olddecl))
+	  || (flag_isoc2x
+	      && DECL_THREAD_LOCAL_P (newdecl)
+	      && !DECL_EXTERNAL (newdecl)
+	      && !DECL_EXTERNAL (olddecl)))
 	{
 	  auto_diagnostic_group d;
 	  error ("redefinition of %q+D", newdecl);
@@ -5714,10 +5726,12 @@  finish_decl (tree decl, location_t init_loc, tree init,
 	      /* A static variable with an incomplete type
 		 is an error if it is initialized.
 		 Also if it is not file scope.
+		 Also if it is thread-local (in C2x).
 		 Otherwise, let it through, but if it is not `extern'
 		 then it may cause an error message later.  */
 	      ? (DECL_INITIAL (decl) != NULL_TREE
-		 || !DECL_FILE_SCOPE_P (decl))
+		 || !DECL_FILE_SCOPE_P (decl)
+		 || (flag_isoc2x && DECL_THREAD_LOCAL_P (decl)))
 	      /* An automatic variable with an incomplete type
 		 is an error.  */
 	      : !DECL_EXTERNAL (decl)))
diff --git a/gcc/testsuite/gcc.dg/c2x-thread-local-2.c b/gcc/testsuite/gcc.dg/c2x-thread-local-2.c
new file mode 100644
index 00000000000..d199ff23848
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-thread-local-2.c
@@ -0,0 +1,40 @@ 
+/* Test that thread-local declarations are not considered tentative definitions
+   in C2x.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+thread_local int a; /* { dg-message "previous" } */
+thread_local int a; /* { dg-error "redefinition" } */
+
+static thread_local int b; /* { dg-message "previous" } */
+static thread_local int b; /* { dg-error "redefinition" } */
+
+thread_local int c; /* { dg-message "previous" } */
+thread_local int c = 1; /* { dg-error "redefinition" } */
+
+static thread_local int d; /* { dg-message "previous" } */
+static thread_local int d = 1; /* { dg-error "redefinition" } */
+
+thread_local int e = 1; /* { dg-message "previous" } */
+thread_local int e; /* { dg-error "redefinition" } */
+
+static thread_local int f = 1; /* { dg-message "previous" } */
+static thread_local int f; /* { dg-error "redefinition" } */
+
+/* Not being a tentative definition means that incomplete arrays are an error
+   rather than defaulting to size 1.  */
+thread_local int g[]; /* { dg-error "storage size" } */
+static thread_local int h[]; /* { dg-error "array size missing" } */
+extern thread_local int i[];
+
+thread_local int j[] = { 0 };
+static thread_local int k[] = { 0 };
+
+thread_local int l;
+extern thread_local int l;
+
+extern thread_local int m;
+thread_local int m;
+
+static thread_local int n;
+extern thread_local int n;