https://gcc.gnu.org/g:ec58d736cd572bb01cfba986f090ab42f195a464

commit r17-3907-gec58d736cd572bb01cfba986f090ab42f195a464
Author: Robin Dapp <[email protected]>
Date:   Fri Jul 17 12:41:26 2026 +0200

    expand: Handle non-word addressable modes.
    
    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.
    
    Co-authored-by: Richard Sandiford <[email protected]>
    
            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.

Diff:
---
 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 --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index a319634ea4db..ca68bdc3e615 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 dbc090360da3..532e95ef216f 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 9c70e48e3117..0e11103a150d 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 3d99be8472f8..feaa8e904703 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 5dee6a0b6460..548f1b0d4b86 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);

Reply via email to