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.

Reply via email to