Bug 108299 - [CWG2684] toplevel thread_local variables are not initialized if not referenced and initialized at wrong moment when referenced
Summary: [CWG2684] toplevel thread_local variables are not initialized if not referenc...
Status: SUSPENDED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 12.2.0
: P3 normal
Target Milestone: ---
Assignee: Jason Merrill
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2023-01-05 10:51 UTC by Andrea Griffini
Modified: 2024-07-24 14:07 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2023-01-06 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Andrea Griffini 2023-01-05 10:51:17 UTC
The standard mandates that thread_local variables at top level must be initialized in case anything in the compilation unit is referred to. There is a specific note on the fact that they must be initialized even if they're not used in the program.
Also they must be initialized at thread start or before anything from the compilation unit is accessed by the thread.

This code however shows that g++ doesn't follow this rule

    #include <stdio.h>

    thread_local int flag = ([](){
        printf("HERE!\n");
        return 1;
    })();

    int main() {
        printf("Hello, world.\n");
        return 0;
    }

In this version the thread local initialization is skipped.
Mover if after the printf the statement

    flag;

is added then the initialization is performed, but AFTER the "Hello, world"
message (another violation of the standard).

Trying on godbolt I saw the problem is present even in trunk and in clang (works as expected in MSVC).

The problems are present both on the main implicit thread and with threads started explicitly with std::thread.
Comment 1 Jakub Jelinek 2023-01-05 11:41:36 UTC
I don't think your claims are based on anything in the standard.
The standard clearly says that it is implementation defined if non-block thread_local vars are dynamically initialized right away or deferred and initialized on first non-initialization odr-use.  The latter is what GCC implements.
Comment 2 Jakub Jelinek 2023-01-05 11:41:53 UTC
See https://eel.is/c++draft/basic.start.dynamic#7 for details.
Comment 3 Andrea Griffini 2023-01-05 14:38:48 UTC
Thread storage duration is different from static storage duration.

The text I found on the topic is different from the one you are linking,
but even in this version (that is indeed more permissive) it's explicitly
stated that a thread_local variable must be initialized before any other
thread_local variable is accessed in the same compilation unit.

> it is implementation-defined whether the dynamic initialization of a
> non-block non-inline variable with thread storage duration is sequenced
> before the first statement of the initial function of a thread or is deferred.

> --------------------------------------------------------------------
> If it is deferred, the initialization associated with the entity for thread
> t is sequenced before the first non-initialization odr-use by t of any
> non-inline variable with thread storage duration defined in the same
> translation unit as the variable to be initialized.
> --------------------------------------------------------------------

> It is implementation-defined in which threads and at which points in
> the program such deferred dynamic initialization occurs.

g++ doesn't seem to follow the rule


#include <stdio.h>

thread_local int flag = ([](){
    printf("HERE!\n"); // This initialization never happens
    return 1;
})();

thread_local int flag2;

int main() {
    printf("Hello, world.\n");
    flag2 = 0; // accesses another thread_local variable in same compilation unit
    return 0;
}
Comment 4 Jakub Jelinek 2023-01-05 14:47:15 UTC
flag2 doesn't have dynamic initialization, if you add dynamic initialization to it, you'll see that flag is constructed before flag2 and that happens before it is referenced in the current thread (after the printf in main).
Comment 5 Andrea Griffini 2023-01-05 14:52:43 UTC
So you are saying that the standard forgot to add "that requires dynamic initialization" and that this is the intention?
Comment 6 Jakub Jelinek 2023-01-05 15:17:31 UTC
CCing C++ committee members.
Requiring that any odr-uses of thread_local vars which have static initialization (which strongly happens before any dynamic initialization) would dynamically initialize all thread_local variables in the same TU declared before such vars would have serious ABI and performance effects for no useful gains.
Comment 7 Jonathan Wakely 2023-01-05 15:40:34 UTC
Comment 0 seems wrong to me, there is no requirement that flag is initialized if not odr-used, and if it is initialized, it doesn't have to happen before the printf statement.

I agree that the standard says comment 3 should initialized both thread local variables.
Comment 8 Jonathan Wakely 2023-01-05 15:42:34 UTC
FWIW the current wording in the standard was introduced by https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0250r3.html
Comment 9 Andrea Griffini 2023-01-05 15:53:15 UTC
I agree that comment 0 is wrong and was based on a text that I thought was taken from the standard but apparently was not (cppreference.com). Sorry for the noise.

I think that if the dynamic initialization should be tied only to uses of other thread locals requiring dynamic initialization this should be stated more precisely in the standard text.
Comment 10 Jason Merrill 2023-01-06 19:57:35 UTC
(In reply to Jakub Jelinek from comment #6)

Hmm, I don't see the ABI problem: wrappers are comdat/weak and prepared to handle the absence of an init function, so TUs that either use or don't use wrappers should be ABI-compatible, though of course you would need both to be recompiled to get the currently specified behavior.

For performance, the one degradation I see is preventing the PR101786 optimization with extern constinit.

https://github.com/cplusplus/CWG/issues/210
Comment 11 Jakub Jelinek 2023-01-06 20:58:26 UTC
You're right.  All we'd need to do is revert PR101786 change and call __tls_init if in the current TU there are any dynamic TLS initializations when any TLS var from current TU is referenced, even when it has just static initializer.
Comment 12 Jason Merrill 2024-07-24 14:07:05 UTC
CWG2684 is still unresolved