diff mbox series

expand: Handle non-word addressable modes.

Message ID DL5I10NSSDG4.1O85NWCDC46OH@gmail.com
State New
Headers show
Series expand: Handle non-word addressable modes. | expand

Commit Message

Robin Dapp Sept. 3, 2026, 7:22 a.m. UTC
Hi,

just posting this again in split-out form as Richard suggested.  Going 
to push this later today with the accompanying riscv changes.

In order to be able to set REGMODE_NATURAL_SIZE = "one vector register"
for aarch64 and riscv, we need to make some adjustments to how we expand
modes that cannot be split into words.  In a few critical spots the
assumption of "either we have a move or we can split into words" was
implicitly hard coded.

The fix turned into a small game of whack-a-mole and this patch takes the
approach of just spilling to memory if REGMODE_NATURAL_SIZE >
UNITS_PER_WORD.  In lower subreg we just don't decompose a register if
the same condition applies.

Even with these changes, it was still possible to make no
progress during expansion of a subreg-punned register like
 (subreg:TI (reg:V4SI))
where we cannot split the inner register nor move the subreg into a
register if the target has no TImode move pattern.

Rather than opening up the possibility of spilling in
emit_move_insn, the patch instead documents the requirement that each
target defining REGMODE_NATURAL_SIZE provide integer move patterns
up to (and including) the largest REGMODE_NATURAL_SIZE.

Bootstrapped and regtested on x86, power10, and aarch64.
Regtested on riscv64.

Regards
 Robin

Co-authored-by: Richard Sandiford <rdsandiford@googlemail.com>

	PR target/124996
	PR middle-end/125390

gcc/ChangeLog:

	* doc/tm.texi: Document int move requirements for
	REGMODE_NATURAL_SIZE.
	* doc/tm.texi.in: Likewise.
	* expmed.cc (store_bit_field_1): Spill unsplittable modes to
	memory.
	(store_integral_bit_field): Defer unsplittable modes to
	extract_bit_field.
	(extract_bit_field_1): Spill if necessary.
	* expr.cc (read_complex_part): Adjust assert.
	* lower-subreg.cc (interesting_mode_p): Don't consider
	unsplittable modes as interesting.
	(simple_move_operand): Don't split subregs whose outer mode
	can be split but the inner mode cannot.
	(simplify_subreg_concatn): Don't insist the outer mode is
	interesting but rather check that its size is constant.

gcc/testsuite/ChangeLog:

	* gcc.target/riscv/rvv/autovec/pr125390.c: New test.
---
 gcc/doc/tm.texi     |  4 +++
 gcc/doc/tm.texi.in  |  4 +++
 gcc/expmed.cc       | 61 +++++++++++++++++++++++++++++++++------------
 gcc/expr.cc         |  3 ---
 gcc/lower-subreg.cc | 30 ++++++++++++++++++----
 5 files changed, 78 insertions(+), 24 deletions(-)
diff mbox series

Patch

diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 2613b95775b..f1f2752e7b6 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -2215,6 +2215,10 @@  should give the natural size in bytes for the specified mode.  It is
 used by the register allocator to try to optimize its results.  This
 happens for example on SPARC 64-bit where the natural size of
 floating-point registers is still 32-bit.
+
+If a target specifies @code{REGMODE_NATURAL_SIZE}, it must provide
+move patterns for all scalar integer modes no larger than the
+largest @code{REGMODE_NATURAL_SIZE}.
 @end defmac
 
 @deftypefn {Target Hook} bool TARGET_HARD_REGNO_MODE_OK (unsigned int @var{regno}, machine_mode @var{mode})
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 0d2a76ec6b0..1163291f06a 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -1878,6 +1878,10 @@  should give the natural size in bytes for the specified mode.  It is
 used by the register allocator to try to optimize its results.  This
 happens for example on SPARC 64-bit where the natural size of
 floating-point registers is still 32-bit.
+
+If a target specifies @code{REGMODE_NATURAL_SIZE}, it must provide
+move patterns for all scalar integer modes no larger than the
+largest @code{REGMODE_NATURAL_SIZE}.
 @end defmac
 
 @hook TARGET_HARD_REGNO_MODE_OK
diff --git a/gcc/expmed.cc b/gcc/expmed.cc
index 9c70e48e311..0e11103a150 100644
--- a/gcc/expmed.cc
+++ b/gcc/expmed.cc
@@ -865,12 +865,16 @@  store_bit_field_1 (rtx str_rtx, poly_uint64 bitsize, poly_uint64 bitnum,
      valid for integral modes.  */
   opt_scalar_int_mode op0_mode = int_mode_for_mode (GET_MODE (op0));
   scalar_int_mode imode;
+  bool need_stack_p = false;
   if (!op0_mode.exists (&imode) || imode != GET_MODE (op0))
     {
       if (MEM_P (op0))
 	op0 = adjust_bitfield_address_size (op0, op0_mode.else_blk (),
 					    0, MEM_SIZE (op0));
-      else if (!op0_mode.exists ())
+      else if (!op0_mode.exists ()
+	       || maybe_lt
+	       ((unsigned) UNITS_PER_WORD,
+		(poly_uint64) REGMODE_NATURAL_SIZE (GET_MODE (op0))))
 	{
 	  if (ibitnum == 0
 	      && known_eq (ibitsize, GET_MODE_BITSIZE (GET_MODE (op0)))
@@ -883,17 +887,29 @@  store_bit_field_1 (rtx str_rtx, poly_uint64 bitsize, poly_uint64 bitnum,
 	    }
 	  if (!fallback_p)
 	    return false;
-	  rtx temp = assign_stack_temp (GET_MODE (op0),
-					GET_MODE_SIZE (GET_MODE (op0)));
-	  emit_move_insn (temp, op0);
-	  store_bit_field_1 (temp, bitsize, bitnum, 0, 0, fieldmode, value,
-			     reverse, fallback_p, undefined_p);
-	  emit_move_insn (op0, temp);
-	  return true;
+	  need_stack_p = true;
 	}
       else
 	op0 = gen_lowpart (op0_mode.require (), op0);
     }
+  else if (!MEM_P (op0)
+	   && maybe_lt ((unsigned) UNITS_PER_WORD,
+			(poly_uint64) REGMODE_NATURAL_SIZE (GET_MODE (op0))))
+    need_stack_p = true;
+
+  /* With or without punning we might be faced with a mode that we cannot
+     split into words.  If so, spill OP0 to the stack and recurse.
+     This happens at most once.  */
+  if (need_stack_p)
+    {
+      rtx temp = assign_stack_temp (GET_MODE (op0),
+				    GET_MODE_SIZE (GET_MODE (op0)));
+      emit_move_insn (temp, op0);
+      store_bit_field_1 (temp, bitsize, bitnum, 0, 0, fieldmode, value,
+			 reverse, fallback_p, undefined_p);
+      emit_move_insn (op0, temp);
+      return true;
+    }
 
   return store_integral_bit_field (op0, op0_mode, ibitsize, ibitnum,
 				   bitregion_start, bitregion_end,
@@ -1015,7 +1031,9 @@  store_integral_bit_field (rtx op0, opt_scalar_int_mode op0_mode,
 	      in BLKmode to handle unaligned memory references and to shift the
 	      last chunk right on big-endian machines if need be.  */
 	  rtx value_word
-	    = fieldmode == BLKmode
+	    = (fieldmode == BLKmode
+	       || maybe_lt ((unsigned) UNITS_PER_WORD,
+			    (poly_uint64) REGMODE_NATURAL_SIZE (value_mode)))
 	      ? extract_bit_field (value, new_bitsize, wordnum * BITS_PER_WORD,
 				   1, NULL_RTX, word_mode, word_mode, false,
 				   NULL)
@@ -1851,12 +1869,16 @@  extract_bit_field_1 (rtx str_rtx, poly_uint64 bitsize, poly_uint64 bitnum,
      if we aren't.  */
   opt_scalar_int_mode op0_mode = int_mode_for_mode (GET_MODE (op0));
   scalar_int_mode imode;
+  bool need_stack_p = false;
   if (!op0_mode.exists (&imode) || imode != GET_MODE (op0))
     {
       if (MEM_P (op0))
 	op0 = adjust_bitfield_address_size (op0, op0_mode.else_blk (),
 					    0, MEM_SIZE (op0));
-      else if (op0_mode.exists (&imode))
+      else if (op0_mode.exists (&imode)
+	       && known_ge
+	       ((unsigned) UNITS_PER_WORD,
+		(poly_uint64) REGMODE_NATURAL_SIZE (GET_MODE (op0))))
 	{
 	  op0 = gen_lowpart (imode, op0);
 
@@ -1866,12 +1888,19 @@  extract_bit_field_1 (rtx str_rtx, poly_uint64 bitsize, poly_uint64 bitnum,
 	    op0 = force_reg (imode, op0);
 	}
       else
-	{
-	  poly_int64 size = GET_MODE_SIZE (GET_MODE (op0));
-	  rtx mem = assign_stack_temp (GET_MODE (op0), size);
-	  emit_move_insn (mem, op0);
-	  op0 = adjust_bitfield_address_size (mem, BLKmode, 0, size);
-	}
+	need_stack_p = true;
+    }
+  else if (!MEM_P (op0)
+	   && maybe_lt ((unsigned) UNITS_PER_WORD,
+			(poly_uint64) REGMODE_NATURAL_SIZE (GET_MODE (op0))))
+    need_stack_p = true;
+
+  if (need_stack_p)
+    {
+      poly_int64 size = GET_MODE_SIZE (GET_MODE (op0));
+      rtx mem = assign_stack_temp (GET_MODE (op0), size);
+      emit_move_insn (mem, op0);
+      op0 = adjust_bitfield_address_size (mem, BLKmode, 0, size);
     }
 
   /* ??? We currently assume TARGET is at least as big as BITSIZE.
diff --git a/gcc/expr.cc b/gcc/expr.cc
index 0a7013e3a25..7397446047d 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -4199,9 +4199,6 @@  read_complex_part (rtx cplx, bool imag_p)
 				     imag_p ? GET_MODE_SIZE (imode) : 0);
       if (ret)
         return ret;
-      else
-	/* simplify_gen_subreg may fail for sub-word MEMs.  */
-	gcc_assert (MEM_P (cplx) && ibitsize < BITS_PER_WORD);
     }
 
   return extract_bit_field (cplx, ibitsize, imag_p ? ibitsize : 0,
diff --git a/gcc/lower-subreg.cc b/gcc/lower-subreg.cc
index 5dee6a0b646..548f1b0d4b8 100644
--- a/gcc/lower-subreg.cc
+++ b/gcc/lower-subreg.cc
@@ -30,6 +30,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "tm_p.h"
 #include "expmed.h"
+#include "regs.h"
 #include "insn-config.h"
 #include "emit-rtl.h"
 #include "recog.h"
@@ -113,6 +114,9 @@  interesting_mode_p (machine_mode mode, unsigned int *bytes,
 {
   if (!GET_MODE_SIZE (mode).is_constant (bytes))
     return false;
+  if (maybe_lt ((unsigned) UNITS_PER_WORD,
+		(poly_uint64) REGMODE_NATURAL_SIZE (mode)))
+    return false;
   *words = CEIL (*bytes, UNITS_PER_WORD);
   return true;
 }
@@ -302,7 +306,20 @@  static bool
 simple_move_operand (rtx x)
 {
   if (GET_CODE (x) == SUBREG)
-    x = SUBREG_REG (x);
+    {
+      /* Exclude subregs whose outer mode can be split into multiple words
+	 but whose inner mode cannot.  Attempting to split such a subreg
+	 would mean trying to split the unsplittable inner register.
+
+	 If instead the subreg occupies a single word, we can keep it as-is,
+	 regardless of what the SUBREG_REG is.  If the outer mode cannot be
+	 split then the subreg makes things no worse than they already are.  */
+      unsigned int factor, size;
+      if (interesting_mode_p (GET_MODE (x), &size, &factor) && factor > 1
+	  && !interesting_mode_p (GET_MODE (SUBREG_REG (x)), &size, &factor))
+	return false;
+      x = SUBREG_REG (x);
+    }
 
   if (!OBJECT_P (x))
     return false;
@@ -649,18 +666,21 @@  decompose_register (unsigned int regno)
 static rtx
 simplify_subreg_concatn (machine_mode outermode, rtx op, poly_uint64 orig_byte)
 {
-  unsigned int outer_size, outer_words, inner_size, inner_words;
+  unsigned int outer_size, inner_size, inner_words;
   machine_mode innermode, partmode;
   rtx part;
   unsigned int final_offset;
   unsigned int byte;
 
   innermode = GET_MODE (op);
-  if (!interesting_mode_p (outermode, &outer_size, &outer_words)
-      || !interesting_mode_p (innermode, &inner_size, &inner_words))
+
+  if (!interesting_mode_p (innermode, &inner_size, &inner_words))
     gcc_unreachable ();
 
-  /* Must be constant if interesting_mode_p passes.  */
+  if (!GET_MODE_SIZE (outermode).is_constant (&outer_size))
+    return NULL_RTX;
+
+  /* Must be constant if outer_size is.  */
   byte = orig_byte.to_constant ();
   gcc_assert (GET_CODE (op) == CONCATN);
   gcc_assert (byte % outer_size == 0);