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]>

Reply via email to