]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
i386: Fix and rework Windows TLS support
authorEric Botcazou <ebotcazou@adacore.com>
Thu, 11 Dec 2025 09:02:35 +0000 (10:02 +0100)
committerEric Botcazou <ebotcazou@adacore.com>
Thu, 11 Dec 2025 12:37:30 +0000 (13:37 +0100)
The compiler can emit invalid movabs instructions at -O0 for TLS accesses in
64-bit mode on Windows, for example with the attached testcase:

eric@fomalhaut:~/build/gcc/x86_64-w64-mingw32> gcc/xgcc -Bgcc -c struct-2.c
/tmp/ccOM8wdd.s: Assembler messages:
/tmp/ccOM8wdd.s:34: Error: operand type mismatch for `movabs'

This fixes the issue by leveraging the existing pic_32bit_operand predicate,
and fixing an oversight present in it for a couple of decades.  The patch
also reworks the support to make use of the get_thread_pointer machinery as
for other platforms, of more comments and of shorter lines.

gcc/
PR target/80881
* config/i386/i386.h (DEFAULT_TLS_SEG_OFFSET): New define.
* config/mingw/mingw32.h (DEFAULT_TLS_SEG_OFFSET): Likewise.
* config/i386/i386.cc (ix86_tls_index): Fix long line.
(legitimize_tls_address): Use get_thread_pointer, add comments and
fix long lines.
* config/i386/i386.md (*load_tp_<mode>): Use DEFAULT_TLS_SEG_OFFSET
(*load_tp_x32_zext): Likewise.
(*add_tp_<mode>): Likewise.
(*add_tp_x32_zext): Likewise.
* config/i386/predicates.md (pic_32bit_operand): Fix oversight.
(symbolic_operand): Accept UNSPEC_SECREL32 with or without offset.

gcc/testsuite/
* gcc.dg/tls/struct-2.c: New test.

gcc/config/i386/i386.cc
gcc/config/i386/i386.h
gcc/config/i386/i386.md
gcc/config/i386/predicates.md
gcc/config/mingw/mingw32.h
gcc/testsuite/gcc.dg/tls/struct-2.c [new file with mode: 0644]

index 75a9cb6211a14c941fe830fd6baf5bc724d5b815..001169b0d66b5ab4e9f9b9d9aee70cb2acdb2067 100644 (file)
@@ -12454,7 +12454,6 @@ get_thread_pointer (machine_mode tp_mode, bool to_reg)
 
 static GTY(()) rtx ix86_tls_index_symbol;
 
-#if TARGET_WIN32_TLS
 static rtx
 ix86_tls_index (void)
 {
@@ -12462,11 +12461,13 @@ ix86_tls_index (void)
     ix86_tls_index_symbol = gen_rtx_SYMBOL_REF (SImode, "_tls_index");
 
   if (flag_pic)
-    return gen_rtx_CONST (Pmode, gen_rtx_UNSPEC (Pmode, gen_rtvec (1, ix86_tls_index_symbol), UNSPEC_PCREL));
+    return gen_rtx_CONST (Pmode,
+                         gen_rtx_UNSPEC (Pmode,
+                                         gen_rtvec (1, ix86_tls_index_symbol),
+                                         UNSPEC_PCREL));
   else
     return ix86_tls_index_symbol;
 }
-#endif
 
 /* Construct the SYMBOL_REF for the tls_get_addr function.  */
 
@@ -12548,26 +12549,33 @@ legitimize_tls_address (rtx x, enum tls_model model, bool for_mov)
   machine_mode tp_mode = Pmode;
   int type;
 
-#if TARGET_WIN32_TLS
-  off = gen_const_mem (SImode, ix86_tls_index ());
-  set_mem_alias_set (off, GOT_ALIAS_SET);
-
-  tp = gen_const_mem (Pmode, GEN_INT (TARGET_64BIT ? 88 : 44));
-  set_mem_addr_space (tp, DEFAULT_TLS_SEG_REG);
-
-  if (TARGET_64BIT)
-    off = convert_to_mode (Pmode, off, 1);
-
-  base = force_reg (Pmode, off);
-  tp = copy_to_mode_reg (Pmode, tp);
-
-  tp = gen_const_mem (Pmode, gen_rtx_PLUS (Pmode, tp, gen_rtx_MULT (Pmode, base, GEN_INT (UNITS_PER_WORD))));
-  set_mem_alias_set (tp, GOT_ALIAS_SET);
-
-  base = force_reg (Pmode, tp);
+  /* Windows implements a single form of TLS.  */
+  if (TARGET_WIN32_TLS)
+    {
+      /* Load the 32-bit index.  */
+      rtx ind = gen_const_mem (SImode, ix86_tls_index ());
+      set_mem_alias_set (ind, GOT_ALIAS_SET);
+      if (TARGET_64BIT)
+       ind = convert_to_mode (Pmode, ind, 1);
+      ind = force_reg (Pmode, ind);
+
+      /* Add it to the thread pointer and load the base.  */
+      tp = get_thread_pointer (Pmode, true);
+      rtx addr = gen_rtx_PLUS (Pmode, tp,
+                              gen_rtx_MULT (Pmode, ind,
+                                            GEN_INT (UNITS_PER_WORD)));
+      base = gen_const_mem (Pmode, addr);
+      set_mem_alias_set (base, GOT_ALIAS_SET);
+
+      /* Add the 32-bit section-relative offset to the base.  */
+      base = force_reg (Pmode, base);
+      off = gen_rtx_CONST (Pmode,
+                          gen_rtx_UNSPEC (SImode,
+                                          gen_rtvec (1, x),
+                                          UNSPEC_SECREL32));
+      return gen_rtx_PLUS (Pmode, base, off);
+    }
 
-  return gen_rtx_PLUS (Pmode, base, gen_rtx_CONST (Pmode, gen_rtx_UNSPEC (SImode, gen_rtvec (1, x), UNSPEC_SECREL32)));
-#else
   /* Fall back to global dynamic model if tool chain cannot support local
      dynamic.  */
   if (TARGET_SUN_TLS && !TARGET_64BIT
@@ -12790,7 +12798,6 @@ legitimize_tls_address (rtx x, enum tls_model model, bool for_mov)
     }
 
   return dest;
-#endif
 }
 
 /* Return true if the TLS address requires insn using integer registers.
index b93411796af36e9d15199ae713cedf4c9c5aa292..4f5954507cea5b2fcdf3a0324d2a99d75f83dceb 100644 (file)
@@ -619,6 +619,9 @@ extern GTY(()) tree x86_mfence;
 #define DEFAULT_TLS_SEG_REG \
   (TARGET_64BIT ? ADDR_SPACE_SEG_FS : ADDR_SPACE_SEG_GS)
 
+/* The default TLS segment offset used by target.  */
+#define DEFAULT_TLS_SEG_OFFSET 0
+
 /* Subtargets may reset this to 1 in order to enable 96-bit long double
    with the rounding mode forced to 53 bits.  */
 #define TARGET_96_ROUND_53_LONG_DOUBLE 0
index b5d838934251c5e4b6f60cbe442cbc1732505bcc..7eafdf9ec1b6d7f1b073b931263d913c049fdd8c 100644 (file)
   [(set (match_dup 0)
        (match_dup 1))]
 {
-  addr_space_t as = DEFAULT_TLS_SEG_REG;
-
-  operands[1] = gen_const_mem (<MODE>mode, const0_rtx);
-  set_mem_addr_space (operands[1], as);
+  operands[1] = gen_const_mem (<MODE>mode, GEN_INT (DEFAULT_TLS_SEG_OFFSET));
+  set_mem_addr_space (operands[1], DEFAULT_TLS_SEG_REG);
 })
 
 (define_insn_and_split "*load_tp_x32_zext"
   [(set (match_dup 0)
        (zero_extend:DI (match_dup 1)))]
 {
-  addr_space_t as = DEFAULT_TLS_SEG_REG;
-
-  operands[1] = gen_const_mem (SImode, const0_rtx);
-  set_mem_addr_space (operands[1], as);
+  operands[1] = gen_const_mem (SImode, GEN_INT (DEFAULT_TLS_SEG_OFFSET));
+  set_mem_addr_space (operands[1], DEFAULT_TLS_SEG_REG);
 })
 
 (define_insn_and_split "*add_tp_<mode>"
           (plus:PTR (match_dup 1) (match_dup 2)))
       (clobber (reg:CC FLAGS_REG))])]
 {
-  addr_space_t as = DEFAULT_TLS_SEG_REG;
-
-  operands[2] = gen_const_mem (<MODE>mode, const0_rtx);
-  set_mem_addr_space (operands[2], as);
+  operands[2] = gen_const_mem (<MODE>mode, GEN_INT (DEFAULT_TLS_SEG_OFFSET));
+  set_mem_addr_space (operands[2], DEFAULT_TLS_SEG_REG);
 })
 
 (define_insn_and_split "*add_tp_x32_zext"
             (plus:SI (match_dup 1) (match_dup 2))))
       (clobber (reg:CC FLAGS_REG))])]
 {
-  addr_space_t as = DEFAULT_TLS_SEG_REG;
-
-  operands[2] = gen_const_mem (SImode, const0_rtx);
-  set_mem_addr_space (operands[2], as);
+  operands[2] = gen_const_mem (SImode, GEN_INT (DEFAULT_TLS_SEG_OFFSET));
+  set_mem_addr_space (operands[2], DEFAULT_TLS_SEG_REG);
 })
 
 ;; GNU2 TLS patterns can be split.
index 2863b3ec3337dc21f141c491c341aaeeaca8b9fc..db60fd810b3dd497623e0191a895f42b7e8bb983 100644 (file)
   /* Rule out relocations that translate into 64bit constants.  */
   if (TARGET_64BIT && GET_CODE (op) == CONST)
     {
-      op = XEXP (op, 0);
-      if (GET_CODE (op) == PLUS && CONST_INT_P (XEXP (op, 1)))
-       op = XEXP (op, 0);
-      if (GET_CODE (op) == UNSPEC
-         && (XINT (op, 1) == UNSPEC_GOTOFF
-             || XINT (op, 1) == UNSPEC_GOT))
+      rtx tmp = XEXP (op, 0);
+      if (GET_CODE (tmp) == PLUS && CONST_INT_P (XEXP (tmp, 1)))
+       tmp = XEXP (tmp, 0);
+      if (GET_CODE (tmp) == UNSPEC
+         && (XINT (tmp, 1) == UNSPEC_GOTOFF
+             || XINT (tmp, 1) == UNSPEC_GOT))
        return false;
     }
 
          || (GET_CODE (op) == UNSPEC
              && (XINT (op, 1) == UNSPEC_GOT
                  || XINT (op, 1) == UNSPEC_GOTOFF
+                 || XINT (op, 1) == UNSPEC_SECREL32
                  || XINT (op, 1) == UNSPEC_PCREL
                  || XINT (op, 1) == UNSPEC_GOTPCREL)))
        return true;
       if (SYMBOL_REF_P (op)
          || LABEL_REF_P (op))
        return true;
-      /* Only @GOTOFF gets offsets.  */
+      /* Only @GOTOFF and @SECREL32 get offsets.  */
       if (GET_CODE (op) != UNSPEC
-         || XINT (op, 1) != UNSPEC_GOTOFF)
+         || (XINT (op, 1) != UNSPEC_GOTOFF
+             && XINT (op, 1) != UNSPEC_SECREL32))
        return false;
 
       op = XVECEXP (op, 0, 0);
 {
   int nelt = XVECLEN (op, 0);
   int elt, i;
-  
+
   if (nelt < 2)
     return false;
 
index be2461f4db83b01ad84c5c73b706d10185a4efc9..3ff9552c31df3fa6b33f545a720c141b6aa178b5 100644 (file)
@@ -315,7 +315,11 @@ do {                                                        \
 #define TARGET_ASM_SELECT_SECTION mingw_pe_select_section
 
 #undef DEFAULT_TLS_SEG_REG
-#define DEFAULT_TLS_SEG_REG (TARGET_64BIT ? ADDR_SPACE_SEG_GS : ADDR_SPACE_SEG_FS)
+#define DEFAULT_TLS_SEG_REG \
+  (TARGET_64BIT ? ADDR_SPACE_SEG_GS : ADDR_SPACE_SEG_FS)
+
+#undef DEFAULT_TLS_SEG_OFFSET
+#define DEFAULT_TLS_SEG_OFFSET (TARGET_64BIT ? 88 : 44)
 
 #define HAVE_ENABLE_EXECUTE_STACK
 #undef  CHECK_EXECUTE_STACK_ENABLED
diff --git a/gcc/testsuite/gcc.dg/tls/struct-2.c b/gcc/testsuite/gcc.dg/tls/struct-2.c
new file mode 100644 (file)
index 0000000..ac8538c
--- /dev/null
@@ -0,0 +1,21 @@
+/* { dg-do assemble } */
+/* { dg-require-effective-target tls } */
+/* { dg-add-options tls } */
+
+struct pixel
+{
+  unsigned int r, g, b;
+};
+
+struct line
+{
+  unsigned int length;
+  struct pixel data[16];
+};
+
+__thread struct line L;
+
+unsigned int read_r (unsigned int i)
+{
+  return i < L.length ? L.data[i].r : 0;
+}