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)
```