diff --git a/src/c_gen_be.c b/src/c_gen_be.c index 849110531a..72d2145050 100644 --- a/src/c_gen_be.c +++ b/src/c_gen_be.c @@ -9201,6 +9201,26 @@ Generate C for an asm statement or declaration. } /* dump_asm_entry */ +#if C_GEN_BE_GENERATES_C23 + +static a_boolean is_thread_local_in_generated_c(a_variable_ptr var_ptr) +/* +Return TRUE if var_ptr is put out as a thread-local variable, either because +it was declared "thread_local" or because it was declared "__thread". +*/ +{ + a_boolean result = var_ptr->is_thread_local; + +#if DECL_MODIFIERS_IN_USE && \ + (MICROSOFT_EXTENSIONS_ALLOWED || THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED) + if ((var_ptr->decl_modifiers & DM_THREAD) != 0) result = TRUE; +#endif /* DECL_MODIFIERS_IN_USE && (MICROSOFT_EXTENSIONS_ALLOWED || ...) */ + return result; +} /* is_thread_local_in_generated_c */ + +#endif /* C_GEN_BE_GENERATES_C23 */ + + static void dump_scope_variables(a_scope_ptr scope, a_boolean interleave_asm_decls, a_boolean dump_vars_without_initializers, @@ -9255,16 +9275,17 @@ interleaved with the variables. definition in another translation unit. */ #if C_GEN_BE_GENERATES_C23 } else if (!dump_initializers && scope->kind == sck_file && - var_ptr->is_thread_local && + is_thread_local_in_generated_c(var_ptr) && var_ptr->storage_class != sc_extern) { /* In C23, a non-extern file-scope thread_local declaration is always - a definition, so we cannot follow the usual pattern of putting out - a non-defining declaration followed by the definition. */ + a definition (GCC treats "__thread" the same way), so we cannot + follow the usual pattern of putting out a non-defining declaration + followed by the definition. */ #endif /* C_GEN_BE_GENERATES_C23 */ } else { a_boolean dump_no_init = dump_vars_without_initializers; #if C_GEN_BE_GENERATES_C23 - if (scope->kind == sck_file && var_ptr->is_thread_local) { + if (scope->kind == sck_file && is_thread_local_in_generated_c(var_ptr)) { /* We skipped the "forward declaration" of thread-local variables, so we need to ensure that they are put out in the "definition" pass over the variable list. */ diff --git a/src/decls.c b/src/decls.c index 1ddc5f01dd..bced28b511 100644 --- a/src/decls.c +++ b/src/decls.c @@ -17888,8 +17888,8 @@ issue an error. #endif /* GNU_EXTENSIONS_ALLOWED */ #if DECL_MODIFIERS_IN_USE -static void check_variable_decl_modifiers(a_variable_ptr var_ptr, - a_decl_parse_state *dps) +void check_variable_decl_modifiers(a_variable_ptr var_ptr, + a_decl_parse_state *dps) /* Check that the given variable's initialization (if any) is compatible with the declaration modifiers recorded in *dps. diff --git a/src/decls.h b/src/decls.h index cd7f3ffed9..9d95efe9f5 100644 --- a/src/decls.h +++ b/src/decls.h @@ -1988,10 +1988,15 @@ void update_routine_decl_modifiers(a_routine_ptr routine, extern void update_variable_decl_modifiers(a_decl_parse_state *dps); + +extern +void check_variable_decl_modifiers(a_variable_ptr var_ptr, + a_decl_parse_state *dps); #else /* !DECL_MODIFIERS_IN_USE */ /* Define these as macros that expand to nothing. */ #define update_routine_decl_modifiers(a,b,c,d,e,f) /* nothing */ #define update_variable_decl_modifiers(a) /* nothing */ +#define check_variable_decl_modifiers(a,b) /* nothing */ #endif /* !DECL_MODIFIERS_IN_USE */ extern void check_default_args_for_param_type(a_param_type_ptr ptp, diff --git a/src/pending_changes/gh-536-thread-variable-template.txt b/src/pending_changes/gh-536-thread-variable-template.txt new file mode 100644 index 0000000000..0f891a233d --- /dev/null +++ b/src/pending_changes/gh-536-thread-variable-template.txt @@ -0,0 +1,23 @@ +[GH #536] +Instances of "__thread" variable templates are thread-local + +An instance of a variable template declared with the GNU "__thread" specifier +was given static storage duration instead of thread storage duration, so all +threads shared one object (with no diagnostic). Such instances are now +thread-local, like instances of "thread_local" variable templates, and are +diagnosed like other "__thread" variables when they need dynamic +initialization or when their address is used as a constant. For example, with +--c++17 --g++: + + template __thread T tv = 10; + int *p = &tv; // Previously the same object in every thread. + + int f(); + template __thread T dv = f(); + int use() { return dv; } // Now an error. + +Also, when generating C23 code (for GCC 15 or later), the C-generating back +end put out a non-defining declaration of a file-scope "__thread" variable +before its definition. For a variable with internal linkage, that +declaration is itself a definition in C23, and the generated C failed to +compile. For example, "static __thread int x = 1;" now compiles. diff --git a/src/templates.c b/src/templates.c index 2737ad9316..ee60f51f24 100644 --- a/src/templates.c +++ b/src/templates.c @@ -7830,6 +7830,13 @@ expression context) rather than a declaration. /* Scan or rescan the declaration of the variable template or static data member. */ scan_template_variable_declaration(tip, var_sym, decl_cache, &dps); +#if THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED + if (is_var_templ_instance && (dps.decl_modifiers.flags & DM_THREAD) != 0) { + /* The rescan records "__thread" only in dps (unlike "thread_local", which + marks the variable directly), so apply it to the instance here. */ + var_ptr->decl_modifiers |= DM_THREAD; + } /* if */ +#endif /* THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED */ if (dps.decltype_auto_specifier_seen) { var_ptr->declared_with_decltype_auto = TRUE; } else if (dps.auto_type_specifier_seen) { @@ -8060,6 +8067,11 @@ expression context) rather than a declaration. check_for_missing_initializer(var_sym, var_ptr->type); } /* if */ } /* if */ + if (is_var_templ_instance) { + /* Check the initialization against the modifiers (e.g., a "__thread" + instance cannot be dynamically initialized). */ + check_variable_decl_modifiers(var_ptr, &dps); + } /* if */ #if GNU_EXTENSIONS_ALLOWED && GNU_VISIBILITY_ATTRIBUTE_ALLOWED if (var_ptr->ELF_visibility == evk_unspecified) { var_ptr->ELF_visibility = proto_var->ELF_visibility; @@ -35458,6 +35470,13 @@ that follows. #endif /* GENERATE_SOURCE_SEQUENCE_LISTS */ check_assertion(vp != NULL); vp->is_specialized = TRUE; +#if THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED + if (dps->first_decl && symbol_is(sym, sk_variable)) { + /* The specialization does not inherit "__thread" from the + generated declaration of the instance. */ + vp->decl_modifiers &= ~DM_THREAD; + } /* if */ +#endif /* THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED */ if (dps->in_class_scope) { vp->is_in_class_specialization = TRUE; } /* if */ diff --git a/tests/tests/regressions/.gh-536-negative.rto/default.1.1.txt b/tests/tests/regressions/.gh-536-negative.rto/default.1.1.txt new file mode 100644 index 0000000000..b67f515b3a --- /dev/null +++ b/tests/tests/regressions/.gh-536-negative.rto/default.1.1.txt @@ -0,0 +1,19 @@ +fe_only -DTEST_NUMBER=1 --c++17 --g++ --gnu_version=150000 Test_name.c +"Test_name.c", line 11: error: thread-local variable cannot be dynamically initialized + template __thread T dv = f(); + ^ + detected during instantiation of "T dv [with T=int]" at line 13 + +"Test_name.c", line 12: error: thread-local variable cannot be dynamically initialized + template __thread T ddv; + ^ + detected during instantiation of "T ddv [with T=N]" at line 13 + +"Test_name.c", line 17: error: expression must have a constant value + constexpr int *q = &av; // error + ^ +"Test_name.c", line 17: note: a pointer or reference to variable "av" (declared at line 16) cannot be used as a constant + constexpr int *q = &av; // error + ^ + +3 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/regressions/.gh-536-negative.rto/default.2.1.txt b/tests/tests/regressions/.gh-536-negative.rto/default.2.1.txt new file mode 100644 index 0000000000..1015e1afcc --- /dev/null +++ b/tests/tests/regressions/.gh-536-negative.rto/default.2.1.txt @@ -0,0 +1,19 @@ +fe_only -DTEST_NUMBER=2 --c++17 --clang --clang_version=190100 Test_name.c +"Test_name.c", line 11: error: thread-local variable cannot be dynamically initialized + template __thread T dv = f(); + ^ + detected during instantiation of "T dv [with T=int]" at line 13 + +"Test_name.c", line 12: error: thread-local variable cannot be dynamically initialized + template __thread T ddv; + ^ + detected during instantiation of "T ddv [with T=N]" at line 13 + +"Test_name.c", line 17: error: expression must have a constant value + constexpr int *q = &av; // error + ^ +"Test_name.c", line 17: note: a pointer or reference to variable "av" (declared at line 16) cannot be used as a constant + constexpr int *q = &av; // error + ^ + +3 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/regressions/.gh-536.rto/default.1.1.txt b/tests/tests/regressions/.gh-536.rto/default.1.1.txt new file mode 100644 index 0000000000..fc05d27d1b --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c++17 --g++ --gnu_version=150000 Test_name.c diff --git a/tests/tests/regressions/.gh-536.rto/default.1.2.txt b/tests/tests/regressions/.gh-536.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-536.rto/default.2.1.txt b/tests/tests/regressions/.gh-536.rto/default.2.1.txt new file mode 100644 index 0000000000..6bcca5ce49 --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.2.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=2 --c++17 --g++ --gnu_version=150000 -tused Test_name.c diff --git a/tests/tests/regressions/.gh-536.rto/default.2.2.txt b/tests/tests/regressions/.gh-536.rto/default.2.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.2.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-536.rto/default.3.1.txt b/tests/tests/regressions/.gh-536.rto/default.3.1.txt new file mode 100644 index 0000000000..3129710097 --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.3.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=3 --c++17 --clang --clang_version=190100 Test_name.c diff --git a/tests/tests/regressions/.gh-536.rto/default.3.2.txt b/tests/tests/regressions/.gh-536.rto/default.3.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-536.rto/default.3.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-536-negative.sft.cpp b/tests/tests/regressions/gh-536-negative.sft.cpp new file mode 100644 index 0000000000..9ca258fb50 --- /dev/null +++ b/tests/tests/regressions/gh-536-negative.sft.cpp @@ -0,0 +1,18 @@ +//type:fn +//require:THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED 1 +//options:--c++17 --g++ --gnu_version=150000:--c++17 --clang --clang_version=190100 +// An instance of a "__thread" variable template is thread-local, so it is +// diagnosed like a non-template "__thread" variable: GCC and Clang reject +// both of these (they were accepted before the instances were thread-local). +int f(); +struct N { N(); int i; }; + +// A "__thread" variable cannot be dynamically initialized. +template __thread T dv = f(); +template __thread T ddv; +int use() { return dv + ddv.i; } // error (twice) + +// The address of a thread-local variable is not a constant. +template __thread T av = 1; +constexpr int *q = &av; // error +int use_q() { return *q; } diff --git a/tests/tests/regressions/gh-536.sft.cpp b/tests/tests/regressions/gh-536.sft.cpp new file mode 100644 index 0000000000..b7140c63b7 --- /dev/null +++ b/tests/tests/regressions/gh-536.sft.cpp @@ -0,0 +1,93 @@ +//type:rp +//require:THREAD_LOCAL_STORAGE_SPECIFIER_ALLOWED 1 +//options:--c++17 --g++ --gnu_version=150000:--c++17 --g++ --gnu_version=150000 -tused:--c++17 --clang --clang_version=190100 +//linker_options:-lpthread +// Each instance of a "__thread" variable template has thread storage +// duration, like an instance of a "thread_local" variable template. +// Previously the instances were ordinary static variables, shared by all +// threads. The internal-linkage cases (and the non-template "plain" and +// "u") also check that the generated C defines each "__thread" variable +// once. No headers are included; the pthread_t typedef below matches glibc +// on x86 targets. +extern "C" { +typedef unsigned long a_pthread; +int pthread_create(a_pthread *, const void *, void *(*)(void *), void *); +int pthread_join(a_pthread, void **); +} + +template __thread T tv = 10; +template __thread T *tv = nullptr; // Partial specialization. +template<> long tv = 10; // Explicit specialization: not thread-local. +template thread_local T tl = 10; // Control. +struct S { template static __thread T m; }; +template __thread T S::m = 10; +template struct C { template static __thread T cm; }; +template template __thread T C::cm = 10; +namespace { template __thread T anon = 10; } +template static __thread T st = 10; +template static __thread T st_noinit; +template inline __thread T inl = 10; +template T nt = 10; +template<> __thread int nt = 10; // Explicit specialization. +static __thread int plain = 10; +static __thread int u; + +const int n_vars = 11; +static const int initial[n_vars] = {10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0}; + +// Get the addresses of the int variables in the calling thread. +static void addresses(int **a) +{ + a[0] = &tv; a[1] = &tl; a[2] = &S::m; + a[3] = &C::cm; a[4] = &anon; a[5] = &st; + a[6] = &inl; a[7] = &plain; a[8] = &nt; + a[9] = &st_noinit; a[10] = &u; +} + +static int *main_addrs[n_vars], *worker_addrs[n_vars]; +static int worker_saw[n_vars]; +static int **main_ptr_addr, **worker_ptr_addr; +static int *worker_saw_ptr; +static long *worker_long_addr; +static long worker_saw_long; + +static void *worker(void *) +{ + addresses(worker_addrs); + for (int i = 0; i < n_vars; ++i) { + worker_saw[i] = *worker_addrs[i]; + *worker_addrs[i] = 1; + } // for + worker_ptr_addr = &tv; + worker_saw_ptr = tv; + tv = &tv; + worker_long_addr = &tv; + worker_saw_long = tv; + return nullptr; +} + +int main() +{ + int fails = 0; + a_pthread t; + addresses(main_addrs); + for (int i = 0; i < n_vars; ++i) *main_addrs[i] = 99; + main_ptr_addr = &tv; + tv = main_addrs[0]; + tv = 99; + if (pthread_create(&t, nullptr, worker, nullptr) != 0) return 100; + pthread_join(t, nullptr); + for (int i = 0; i < n_vars; ++i) { + // Each thread has its own object, initialized from the template. + if (worker_addrs[i] == main_addrs[i]) fails |= 1; + if (worker_saw[i] != initial[i]) fails |= 2; + if (*main_addrs[i] != 99) fails |= 4; + } // for + if (worker_ptr_addr == main_ptr_addr || worker_saw_ptr != nullptr || + tv != main_addrs[0]) { + fails |= 8; + } // if + // The explicit specialization tv is shared by the threads. + if (worker_long_addr != &tv || worker_saw_long != 99) fails |= 16; + return fails; +}