Hi,

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.

Tested on i686-w64-mingw32 and x86_64-w64-mingw32, OK for the mainline?


2025-12-10  Eric Botcazou  <[email protected]>

        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.


2025-12-10  Eric Botcazou  <[email protected]>

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

-- 
Eric Botcazou
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index db43045753b..097025c73f6 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -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.
diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
index b93411796af..4f5954507ce 100644
--- a/gcc/config/i386/i386.h
+++ b/gcc/config/i386/i386.h
@@ -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
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index df7135f84d4..574685a5398 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -23507,10 +23507,8 @@
   [(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"
@@ -23523,10 +23521,8 @@
   [(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>"
@@ -23543,10 +23539,8 @@
 	   (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"
@@ -23564,10 +23558,8 @@
 	     (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.
diff --git a/gcc/config/i386/predicates.md b/gcc/config/i386/predicates.md
index 2863b3ec333..b1c9aba568c 100644
--- a/gcc/config/i386/predicates.md
+++ b/gcc/config/i386/predicates.md
@@ -543,12 +543,12 @@
   /* 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;
     }
 
@@ -578,6 +578,7 @@
 	  || (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;
@@ -589,9 +590,10 @@
       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);
diff --git a/gcc/config/mingw/mingw32.h b/gcc/config/mingw/mingw32.h
index be2461f4db8..3ff9552c31d 100644
--- a/gcc/config/mingw/mingw32.h
+++ b/gcc/config/mingw/mingw32.h
@@ -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
/* { 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;
}

Reply via email to