On Mon, Oct 12, 2026 at 9:05 PM Matt Turner <[email protected]> wrote: > > Since r16-3673 (C) and r16-3637 (C++) the front ends upgrade a TLS > variable's access model to decl_default_tls_model as soon as its > declaration has been processed. #pragma weak is applied after that, so > the model is chosen while the symbol still appears to bind locally. A > hidden extern TLS variable that is then made weak keeps a model that > assumes it is defined in the same module, which is wrong for an > undefined weak symbol: with local-exec the linker has no TLS segment > offset to use. This breaks static links of glibc on ia64, and on alpha > when the linker lacks the fix for PR ld/34165. glibc declares its > _nl_current_LC_* variables as > > extern __thread struct __locale_data *const *_nl_current_LC_CTYPE > attribute_hidden attribute_tls_model_ie; > #pragma weak _nl_current_LC_CTYPE > > __attribute__((weak)) is not affected, because DECL_WEAK is already set > when the model is chosen. > > Recompute the default model in apply_pragma_weak once the symbol is > weak, and lower the model if the new default is weaker. An explicit > tls_model attribute remains a lower bound, as it is when the declaration > is first processed, so the example above ends up initial-exec rather > than local-exec, and an explicit local-exec is left alone. To read the > attribute, split the string matching out of handle_tls_model_attribute. > > Nothing is recomputed for "#pragma weak sym = target". That makes the > symbol an alias defined in this translation unit, so it binds no less > locally than before, but decl_default_tls_model cannot see that yet and > would treat it as weak undefined. The test for this scans the early > visibility dump, because the whole-program visibility pass upgrades the > model of an attribute-free variable again and would hide the difference. > > gcc/c-family/ChangeLog: > > PR c/125418 > * c-attribs.cc (parse_tls_model): New function, split out of... > (handle_tls_model_attribute): ...here. Use it. > * c-common.h (parse_tls_model): Declare. > * c-pragma.cc (apply_pragma_weak): Recompute the TLS model after > declaring a symbol weak. > > gcc/testsuite/ChangeLog: > PR c/125418 > * c-c++-common/tls-pragma-weak-1.c: New test. > * c-c++-common/tls-pragma-weak-2.c: New test. > * gcc.dg/tls/pragma-weak-alias.c: New test. > > --- > This replaces the two patches I posted in May for PR c/125418, a 16/17 > regression. > > Changes in v2: > - Drop 1/2 (don't upgrade past an explicit tls_model attribute). It > was not wanted. > - With 1/2 dropped, 2/2 alone no longer covers glibc, whose declarations > have a tls_model attribute: it skipped the recomputation for them. > Recompute for those too and treat the attribute as a lower bound, the > same way the front ends do when they first process the declaration. > To read the attribute, split the string matching out of > handle_tls_model_attribute. > - Leave "#pragma weak sym = target" alone and add a test for it, as > Alexander asked. > - Move the tests to c-c++-common, cover both orders of pragma and > declaration, and drop -fno-pie. > - Reword the comment and commit message so they are not specific to > local-exec, and cite PR c/125418 rather than PR c/107419. > - Link to v1: > https://inbox.sourceware.org/gcc-patches/[email protected]/ > > Alexander also suggested doing this in varasm.cc:mark_weak. I kept it > in apply_pragma_weak because it needs the tls_model attribute, whose > parsing lives in c-family, and because __attribute__((weak)) already > sets DECL_WEAK before the model is chosen, so the pragma is the only > path that gets the order wrong. apply_pragma_weak is shared by the C > and C++ front ends, so one change covers both. I can move it if you > would rather have it in mark_weak. > > Bootstrapped and regtested on x86_64-pc-linux-gnu with no regressions. > tls-pragma-weak-{1,2}.c fail without the c-pragma.cc change; > pragma-weak-alias.c passes either way and guards the alias exclusion. > The patch also applies to 16.2.0, and with it an ia64-unknown-linux-gnu > cross compiler builds glibc again, including the static links that > failed before. > > On alpha the failure is hidden by the linker fix for PR ld/34165, so I > tested with an ld built from the commit before that fix. With it, > alpha-unknown-linux-gnu GCC 16.2.0 fails to build glibc 2.43 in its > static links ("relocation truncated to fit: TPRELHI against undefined > symbol `_nl_current_LC_COLLATE'"), and the same compiler with this patch > builds it. > > OK for trunk and for the 16 branch? > --- > gcc/c-family/c-attribs.cc | 33 > +++++++++++++++++++------- > gcc/c-family/c-common.h | 1 + > gcc/c-family/c-pragma.cc | 18 ++++++++++++++ > gcc/testsuite/c-c++-common/tls-pragma-weak-1.c | 22 +++++++++++++++++ > gcc/testsuite/c-c++-common/tls-pragma-weak-2.c | 22 +++++++++++++++++ > gcc/testsuite/gcc.dg/tls/pragma-weak-alias.c | 21 ++++++++++++++++ > 6 files changed, 108 insertions(+), 9 deletions(-) > > diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc > index 470054f044a..ef71f9ca8ba 100644 > --- a/gcc/c-family/c-attribs.cc > +++ b/gcc/c-family/c-attribs.cc > @@ -3010,6 +3010,29 @@ handle_weakref_attribute (tree *node, tree name, tree > args, > return NULL_TREE; > } > > +/* If ID, the argument of a "tls_model" attribute, names a TLS access model, > + store that model in *KIND and return true. Otherwise return false. */ > + > +bool > +parse_tls_model (const_tree id, enum tls_model *kind) > +{ > + if (TREE_CODE (id) != STRING_CST) > + return false; > + > + if (!strcmp (TREE_STRING_POINTER (id), "local-exec")) > + *kind = TLS_MODEL_LOCAL_EXEC; > + else if (!strcmp (TREE_STRING_POINTER (id), "initial-exec")) > + *kind = TLS_MODEL_INITIAL_EXEC; > + else if (!strcmp (TREE_STRING_POINTER (id), "local-dynamic")) > + *kind = optimize ? TLS_MODEL_LOCAL_DYNAMIC : TLS_MODEL_GLOBAL_DYNAMIC; > + else if (!strcmp (TREE_STRING_POINTER (id), "global-dynamic")) > + *kind = TLS_MODEL_GLOBAL_DYNAMIC; > + else > + return false; > + > + return true; > +} > + > /* Handle an "tls_model" attribute; arguments as in > struct attribute_spec.handler. */ > > @@ -3045,15 +3068,7 @@ handle_tls_model_attribute (tree *node, tree name, > tree args, > return NULL_TREE; > } > > - if (!strcmp (TREE_STRING_POINTER (id), "local-exec")) > - kind = TLS_MODEL_LOCAL_EXEC; > - else if (!strcmp (TREE_STRING_POINTER (id), "initial-exec")) > - kind = TLS_MODEL_INITIAL_EXEC; > - else if (!strcmp (TREE_STRING_POINTER (id), "local-dynamic")) > - kind = optimize ? TLS_MODEL_LOCAL_DYNAMIC : TLS_MODEL_GLOBAL_DYNAMIC; > - else if (!strcmp (TREE_STRING_POINTER (id), "global-dynamic")) > - kind = TLS_MODEL_GLOBAL_DYNAMIC; > - else > + if (!parse_tls_model (id, &kind)) > error ("%qE argument must be one of %qs, %qs, %qs, or %qs", > name, > "local-exec", "initial-exec", "local-dynamic", "global-dynamic"); > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h > index 197db508c16..3680d341db6 100644 > --- a/gcc/c-family/c-common.h > +++ b/gcc/c-family/c-common.h > @@ -1689,6 +1689,7 @@ extern void check_for_xor_used_as_pow (location_t > lhs_loc, tree lhs_val, > > /* In c-attribs.cc. */ > extern bool attribute_takes_identifier_p (const_tree); > +extern bool parse_tls_model (const_tree, enum tls_model *); > extern tree handle_deprecated_attribute (tree *, tree, tree, int, bool *); > extern tree handle_unused_attribute (tree *, tree, tree, int, bool *); > extern tree handle_fallthrough_attribute (tree *, tree, tree, int, bool *); > diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc > index d0d4ff69fd7..481e16e68a8 100644 > --- a/gcc/c-family/c-pragma.cc > +++ b/gcc/c-family/c-pragma.cc > @@ -270,6 +270,24 @@ apply_pragma_weak (tree decl, tree value) > "results in unspecified behavior", decl); > > declare_weak (decl); > + > + /* The TLS access model was chosen before DECL became weak. If DECL no > + longer binds locally, that model may be too strong, so recompute it. > + An explicit tls_model attribute remains a lower bound. With VALUE, > + DECL is now an alias defined in this translation unit, which binds no > + less locally than the declaration the model was chosen for. */ > + if (!value && VAR_P (decl) && DECL_THREAD_LOCAL_P (decl)) > + { > + tls_model model = decl_default_tls_model (decl); > + tree attr = lookup_attribute ("tls_model", DECL_ATTRIBUTES (decl)); > + tls_model attr_model; > + if (attr > + && parse_tls_model (TREE_VALUE (TREE_VALUE (attr)), &attr_model) > + && attr_model > model) > + model = attr_model; > + if (model < DECL_TLS_MODEL (decl)) > + set_decl_tls_model (decl, model); > + } > } > > void > diff --git a/gcc/testsuite/c-c++-common/tls-pragma-weak-1.c > b/gcc/testsuite/c-c++-common/tls-pragma-weak-1.c > new file mode 100644 > index 00000000000..884f64db5c3 > --- /dev/null > +++ b/gcc/testsuite/c-c++-common/tls-pragma-weak-1.c > @@ -0,0 +1,22 @@ > +/* PR c/125418 */ > +/* { dg-do compile { target tls_native } } */ > +/* { dg-require-weak "" } */ > +/* { dg-require-visibility "" } */ > +/* { dg-options "-O2 -fdump-ipa-whole-program" } */ > + > +/* A hidden TLS variable made weak by #pragma weak may be undefined, so it > + does not bind locally and must not get a local TLS access model, whether > + the pragma follows or precedes the declaration. */ > + > +__attribute__((visibility("hidden"))) > +extern __thread int t1; > +#pragma weak t1 > + > +#pragma weak t2 > +__attribute__((visibility("hidden"))) > +extern __thread int t2; > + > +int *f1 (void) { return &t1; } > +int *f2 (void) { return &t2; } > + > +/* { dg-final { scan-ipa-dump-not "Varpool flags: tls-local" "whole-program" > } } */ > diff --git a/gcc/testsuite/c-c++-common/tls-pragma-weak-2.c > b/gcc/testsuite/c-c++-common/tls-pragma-weak-2.c > new file mode 100644 > index 00000000000..2c01f1435c1 > --- /dev/null > +++ b/gcc/testsuite/c-c++-common/tls-pragma-weak-2.c > @@ -0,0 +1,22 @@ > +/* PR c/125418 */ > +/* { dg-do compile { target tls_native } } */ > +/* { dg-require-weak "" } */ > +/* { dg-require-visibility "" } */ > +/* { dg-options "-O2 -fdump-ipa-whole-program" } */ > + > +/* As tls-pragma-weak-1.c, but with an explicit tls_model attribute, which > + is how glibc declares _nl_current_LC_*. The attribute is a lower bound: > + the model must stay initial-exec rather than be upgraded to local-exec. > */ > + > +__attribute__((visibility("hidden"), tls_model("initial-exec"))) > +extern __thread int t1; > +#pragma weak t1 > + > +#pragma weak t2 > +__attribute__((visibility("hidden"), tls_model("initial-exec"))) > +extern __thread int t2; > + > +int *f1 (void) { return &t1; } > +int *f2 (void) { return &t2; } > + > +/* { dg-final { scan-ipa-dump-times "Varpool flags: tls-initial-exec" 2 > "whole-program" } } */ > diff --git a/gcc/testsuite/gcc.dg/tls/pragma-weak-alias.c > b/gcc/testsuite/gcc.dg/tls/pragma-weak-alias.c > new file mode 100644 > index 00000000000..68c3d1f1c76 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tls/pragma-weak-alias.c > @@ -0,0 +1,21 @@ > +/* PR c/125418 */ > +/* { dg-do compile { target tls_native } } */ > +/* { dg-require-weak "" } */ > +/* { dg-require-alias "" } */ > +/* { dg-require-visibility "" } */ > +/* { dg-options "-O2 -fdump-ipa-visibility" } */ > + > +/* A hidden TLS variable that #pragma weak makes an alias of a variable > + defined here still binds locally, so the front end must not weaken its > + TLS access model the way it does for a weak undefined symbol. Scan the > + early dump: later IPA passes would upgrade the model again. */ > + > +static __thread int local_def; > + > +__attribute__((visibility("hidden"))) > +extern __thread int t; > +#pragma weak t = local_def > + > +int *f (void) { return &t; } > + > +/* { dg-final { scan-ipa-dump-not "Varpool flags: tls-initial-exec" > "visibility" } } */ > > --- > base-commit: 83dbe75bb696d899901f3adf252dcb61ea7c1b8b > change-id: 20261010-pr125418-pragma-weak-v2-80abdf5c8cfa > > Best regards, > -- > Matt Turner <[email protected]>
Ping for review.
