This is too long for a comment, though I don't claim to fully understand it.
I have a shorter version you can run in Coliru
#include <iostream>
using namespace std;
struct foo {
int i;
foo() : i{42} {}
};
struct bar {
static thread_local foo FOO;
};
thread_local foo bar::FOO;
int main() {
//cerr << string((bar::FOO.i == 42) ? "Ok" : "Bug") << endl; //Ok
cerr << string((bar().FOO.i == 42) ? "Ok" : "Bug") << endl; //Bug
}
I think the bug is in this gcc source file
https://chromium.googlesource.com/native_client/nacl-gcc/+/upstream/master/gcc/cp/decl2.c
At this point gcc is trying to decide if FOO
, which is a static member of bar
, needs a wrapper function to detect if it has been initialized... it decides no wrapper is needed, which is incorrect. It checks
- Is it not an error_operand_p ? Yes, it is not. (I guess)
- Is it thread_local (DECL_THREAD_LOCAL_P) ? Yes it is thread_local.
- Is it not gnu __thread extension (DECL_GNU_TLS_P) ? Yes, it is not.
- Is it not declared in function scope (DECL_FUNCTION_SCOPE_P) ? Yes, it is not.
- Is the variable not defined in another translation unit (TU)? Yes, it is not. (bug?)
- Does it not have a non-trivial destructor? Yes, it does not.
- Does it have no initializer or a constant one? It has an initializer, but it is constant.
- It doesn't need a wrapper
The flaw is either:
- Concluding that if the initializer is constant then it isn't dynamically initialized, or
- Failing to properly do the static initialization, or
- Failing to notice that even though it is a member variable it could be externally defined
Since the initialization is done by the constructor, I think that is the source of the confusion, a constructor is called, but the value is a constant.
Here's the code
/* Returns true iff we can tell that VAR does not have a dynamic
initializer. */
static bool
var_defined_without_dynamic_init (tree var)
{
/* If it's defined in another TU, we can't tell. */
if (DECL_EXTERNAL (var))
return false;
/* If it has a non-trivial destructor, registering the destructor
counts as dynamic initialization. */
if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (var)))
return false;
/* If it's in this TU, its initializer has been processed. */
gcc_assert (DECL_INITIALIZED_P (var));
/* If it has no initializer or a constant one, it's not dynamic. */
return (!DECL_NONTRIVIALLY_INITIALIZED_P (var)
|| DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (var));
}
/* Returns true iff VAR is a variable that needs uses to be
wrapped for possible dynamic initialization. */
static bool
var_needs_tls_wrapper (tree var)
{
return (!error_operand_p (var)
&& DECL_THREAD_LOCAL_P (var)
&& !DECL_GNU_TLS_P (var)
&& !DECL_FUNCTION_SCOPE_P (var)
&& !var_defined_without_dynamic_init (var));
}