https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125418

            Bug ID: 125418
           Summary: c: tls_model("initial-exec") attribute ignored for
                    hidden symbols with -fno-pie
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: mattst88 at gmail dot com
  Target Milestone: ---

Commit 8cad8f94b45 (PR c/107419, "c: Update TLS model after processing a TLS
variable") introduced a regression in c_decl_attributes. After processing a TLS
variable's attributes, it unconditionally upgrades the TLS model to the default
computed by decl_default_tls_model, even when the user explicitly set
tls_model("initial-exec") via an attribute.

For a hidden __thread variable compiled with -fno-pie, decl_default_tls_model
returns TLS_MODEL_LOCAL_EXEC. Since LOCAL_EXEC > INITIAL_EXEC, the upgrade
fires and the explicit tls_model("initial-exec") attribute is silently
overridden.

The IPA visibility pass (1d561e1851c) correctly guards the same upgrade logic
with !lookup_attribute("tls_model", DECL_ATTRIBUTES(decl)). The
c_decl_attributes code lacks this guard.

Observed impact:

The regression was discovered when building glibc for Alpha with GCC 16
(https://bugs.gentoo.org/975324). glibc's _nl_current_LC_* symbols are declared
attribute_hidden attribute_tls_model_ie
(__attribute__((tls_model("initial-exec")))) and referenced weakly via #pragma
weak from locale/setlocale.c. With the regression, GCC 16 emits TPRELHI/TPRELLO
(local-exec) instead of GOTTPREL (initial-exec). Since these are undefined weak
symbols at link time, the linker cannot compute the TP-relative offset,
producing:

relocation truncated to fit: TPRELHI against undefined symbol
`_nl_current_LC_COLLATE'

Note on other architectures: The incorrect local-exec promotion affects all
architectures with GCC 16 when compiling hidden initial-exec TLS with -fno-pie.
Alpha produces a hard linker error because R_ALPHA_TPRELHI encodes a 16-bit TP
offset that overflows for undefined symbols. On x86_64, R_X86_64_TPOFF32
silently resolves undefined weak symbols to 0 (fitting in 32 bits), so the
linker accepts the broken code without error. Static glibc binaries using
locale functions on x86_64 may be silently miscompiled when built with GCC 16.

```
/* { dg-require-effective-target tls } */
/* { dg-options "-O2 -fno-pie -fdump-ipa-whole-program" } */

/* tls_model("initial-exec") must not be upgraded to local-exec even when
   the symbol is hidden and -fno-pie is used. */
__attribute__((visibility("hidden")))
__attribute__((tls_model("initial-exec")))
extern __thread int x;

void reference(void) { x++; }

/* { dg-final { scan-ipa-dump "Varpool flags: tls-initial-exec" "whole-program"
} } */
```

With GCC 16 (regression): dump shows tls-local-exec. With GCC 15 or fix
applied: tls-initial-exec. Verified with clean builds of all three states.

Fix:
```
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -5739,8 +5739,9 @@ c_decl_attributes (tree *node, tree attributes, int
flags)
   if (last_decl == error_mark_node)
     last_decl = NULL_TREE;
   tree attr = decl_attributes (node, attributes, flags, last_decl);
-  if (VAR_P (*node) && DECL_THREAD_LOCAL_P (*node))
+  if (VAR_P (*node) && DECL_THREAD_LOCAL_P (*node)
+      && !lookup_attribute ("tls_model", DECL_ATTRIBUTES (*node)))
     {
-      // tls_model attribute can set a stronger TLS access model.
       tls_model model = DECL_TLS_MODEL (*node);
       tls_model default_model = decl_default_tls_model (*node);
       if (default_model > model)
```

Reply via email to