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
29 changes: 25 additions & 4 deletions src/c_gen_be.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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. */
Expand Down
4 changes: 2 additions & 2 deletions src/decls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions src/decls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 23 additions & 0 deletions src/pending_changes/gh-536-thread-variable-template.txt
Original file line number Diff line number Diff line change
@@ -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<class T> __thread T tv = 10;
int *p = &tv<int>; // Previously the same object in every thread.

int f();
template<class T> __thread T dv = f();
int use() { return dv<int>; } // 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.
19 changes: 19 additions & 0 deletions src/templates.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down
19 changes: 19 additions & 0 deletions tests/tests/regressions/.gh-536-negative.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -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<class T> __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<class T> __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<int>; // error
^
"Test_name.c", line 17: note: a pointer or reference to variable "av<int>" (declared at line 16) cannot be used as a constant
constexpr int *q = &av<int>; // error
^

3 errors detected in the compilation of "Test_name.c".
19 changes: 19 additions & 0 deletions tests/tests/regressions/.gh-536-negative.rto/default.2.1.txt
Original file line number Diff line number Diff line change
@@ -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<class T> __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<class T> __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<int>; // error
^
"Test_name.c", line 17: note: a pointer or reference to variable "av<int>" (declared at line 16) cannot be used as a constant
constexpr int *q = &av<int>; // error
^

3 errors detected in the compilation of "Test_name.c".
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=1 --c++17 --g++ --gnu_version=150000 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.1.2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.2.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=2 --c++17 --g++ --gnu_version=150000 -tused Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.2.2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.3.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=3 --c++17 --clang --clang_version=190100 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-536.rto/default.3.2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
18 changes: 18 additions & 0 deletions tests/tests/regressions/gh-536-negative.sft.cpp
Original file line number Diff line number Diff line change
@@ -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<class T> __thread T dv = f();
template<class T> __thread T ddv;
int use() { return dv<int> + ddv<N>.i; } // error (twice)

// The address of a thread-local variable is not a constant.
template<class T> __thread T av = 1;
constexpr int *q = &av<int>; // error
int use_q() { return *q; }
93 changes: 93 additions & 0 deletions tests/tests/regressions/gh-536.sft.cpp
Original file line number Diff line number Diff line change
@@ -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<class T> __thread T tv = 10;
template<class T> __thread T *tv<T*> = nullptr; // Partial specialization.
template<> long tv<long> = 10; // Explicit specialization: not thread-local.
template<class T> thread_local T tl = 10; // Control.
struct S { template<class T> static __thread T m; };
template<class T> __thread T S::m = 10;
template<class U> struct C { template<class T> static __thread T cm; };
template<class U> template<class T> __thread T C<U>::cm = 10;
namespace { template<class T> __thread T anon = 10; }
template<class T> static __thread T st = 10;
template<class T> static __thread T st_noinit;
template<class T> inline __thread T inl = 10;
template<class T> T nt = 10;
template<> __thread int nt<int> = 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<int>; a[1] = &tl<int>; a[2] = &S::m<int>;
a[3] = &C<int>::cm<int>; a[4] = &anon<int>; a[5] = &st<int>;
a[6] = &inl<int>; a[7] = &plain; a[8] = &nt<int>;
a[9] = &st_noinit<int>; 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<int*>;
worker_saw_ptr = tv<int*>;
tv<int*> = &tv<int>;
worker_long_addr = &tv<long>;
worker_saw_long = tv<long>;
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<int*>;
tv<int*> = main_addrs[0];
tv<long> = 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<int*> != main_addrs[0]) {
fails |= 8;
} // if
// The explicit specialization tv<long> is shared by the threads.
if (worker_long_addr != &tv<long> || worker_saw_long != 99) fails |= 16;
return fails;
}
Loading