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

commit r17-1954-gd00acdc3ecff20ce0591103364be86505c382af6
Author: Kyrylo Tkachov <[email protected]>
Date:   Wed Jun 17 07:25:31 2026 -0700

    LRA: reload non-representable subregs of hard registers through memory 
[PR105116]
    
    PR target/105116 is an aarch64 ICE in lra_split_hard_reg_for ("unable to
    find a register to spill").  A wide vector value (OImode, a pair of Q
    registers) is pinned to FP registers via asm register variables and an
    operation with no Advanced SIMD form (an integer vector divide or modulo) is
    scalarised by vector lowering.  The scalar pieces read non-lowpart scalar
    subregs of the OImode register, e.g. (subreg:DI (reg:OI v2) 8), the high 64
    bits of the first Q register.
    
    Such a subreg is not representable as a hard register (subreg_get_info
    returns representable_p == false), but the hard-register branch of
    simplify_operand_subreg only called alter_subreg, which resolves it to the
    wrong part of the register.  For GENERAL_REGS uses, curr_insn_transform then
    tried to reload the whole OImode inner register into GENERAL_REGS, which is
    impossible (aarch64_hard_regno_mode_ok is false for OImode in GENERAL_REGS)
    and gave the ICE; for FP-context uses the bad subreg survived to final and
    silently read the wrong bytes (a latent wrong-code bug).
    
    Handle this in simplify_operand_subreg: when a narrowing subreg of a hard
    register is not representable, reload the inner register through memory
    (NO_REGS) so that the correct bytes are accessed, mirroring the existing
    handling for pseudos.  The frame, arg and stack pointers are left alone, as
    simplify_subreg_regno can reject them merely because reload is not finished.
    This only affects the already non-representable case; representable subregs
    are unchanged.
    
    Bootstrapped and tested on aarch64-none-linux-gnu and x86_64-linux.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>
    
    gcc/ChangeLog:
    
            PR target/105116
            * lra-constraints.cc (simplify_operand_subreg): Reload a
            non-representable narrowing subreg of a hard register through
            memory instead of resolving it lossily.
    
    gcc/testsuite/ChangeLog:
    
            PR target/105116
            * gcc.target/aarch64/sve/pr105116.c: New test.
            * gcc.target/aarch64/sve/pr105116-run.c: New test.

Diff:
---
 gcc/lra-constraints.cc                             | 30 ++++++++++++++
 .../gcc.target/aarch64/sve/pr105116-run.c          | 48 ++++++++++++++++++++++
 gcc/testsuite/gcc.target/aarch64/sve/pr105116.c    | 32 +++++++++++++++
 3 files changed, 110 insertions(+)

diff --git a/gcc/lra-constraints.cc b/gcc/lra-constraints.cc
index df8f1b8b450c..a5e67eaae215 100644
--- a/gcc/lra-constraints.cc
+++ b/gcc/lra-constraints.cc
@@ -1855,6 +1855,36 @@ simplify_operand_subreg (int nop, machine_mode reg_mode)
     }
   else if (REG_P (reg) && REGNO (reg) < FIRST_PSEUDO_REGISTER)
     {
+      /* A narrowing subreg of a hard register that is not representable as a
+        hard register (its offset does not fall on a register boundary) cannot
+        be turned into one, and would otherwise be resolved to the wrong part
+        of the register.  Reload it through memory so that the correct bytes
+        are accessed, as is done for pseudos below.  Leave the frame, arg and
+        stack pointers alone: simplify_subreg_regno can reject them simply
+        because reload is not finished yet.  */
+      if (partial_subreg_p (mode, innermode)
+         && REGNO (reg) != FRAME_POINTER_REGNUM
+         && REGNO (reg) != ARG_POINTER_REGNUM
+         && REGNO (reg) != STACK_POINTER_REGNUM
+         && simplify_subreg_regno (REGNO (reg), innermode,
+                                   SUBREG_BYTE (operand), mode) < 0)
+       {
+         if (get_reload_reg (type, innermode, reg, NO_REGS, NULL,
+                             true, false, "non-representable subreg", 
&new_reg))
+           {
+             bitmap_set_bit (&lra_subreg_reload_pseudos, REGNO (new_reg));
+             bool insert_before = (type != OP_OUT
+                                   || read_modify_subreg_p (operand));
+             bool insert_after = (type != OP_IN);
+             insert_move_for_subreg (insert_before ? &before : NULL,
+                                     insert_after ? &after : NULL,
+                                     reg, new_reg);
+           }
+         SUBREG_REG (operand) = new_reg;
+         lra_process_new_insns (curr_insn, before, after,
+                                "Inserting non-representable subreg reload");
+         return true;
+       }
       alter_subreg (curr_id->operand_loc[nop], false);
       return true;
     }
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/pr105116-run.c 
b/gcc/testsuite/gcc.target/aarch64/sve/pr105116-run.c
new file mode 100644
index 000000000000..1a5798d48733
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/pr105116-run.c
@@ -0,0 +1,48 @@
+/* PR target/105116 */
+/* { dg-do run { target aarch64_sve_hw } } */
+/* { dg-options "-O2 -fno-split-wide-types" } */
+
+/* Besides the ICE, the non-representable high-lane subregs used to be
+   resolved lossily (silently reading lane 0), miscompiling the upper lanes.
+   Check the scalarised divide produces the correct result for every lane.  */
+
+typedef signed char vnx16qi __attribute__((vector_size (32)));
+
+__attribute__((noipa)) void
+vdiv_vnx16qi (vnx16qi *x, vnx16qi y, vnx16qi z)
+{
+  register vnx16qi dst  asm ("z0");
+  register vnx16qi src1 asm ("z2");
+  register vnx16qi src2 asm ("z4");
+  dst = *x;
+  src1 = y;
+  src2 = z;
+  asm volatile ("" :: "w" (dst), "w" (src1), "w" (src2));
+  dst = src2 - (dst / src1);
+  asm volatile ("" :: "w" (dst));
+  *x = dst;
+}
+
+int
+main (void)
+{
+  vnx16qi X, Y, Z;
+  signed char xv[32], yv[32], zv[32];
+  for (int i = 0; i < 32; i++)
+    {
+      xv[i] = (signed char) (i * 7 - 50);
+      yv[i] = (signed char) (((i % 5) + 1) * (i & 1 ? 1 : -1));
+      zv[i] = (signed char) (100 - i * 3);
+    }
+  __builtin_memcpy (&X, xv, 32);
+  __builtin_memcpy (&Y, yv, 32);
+  __builtin_memcpy (&Z, zv, 32);
+  vdiv_vnx16qi (&X, Y, Z);
+  signed char got[32];
+  __builtin_memcpy (got, &X, 32);
+  for (int i = 0; i < 32; i++)
+    if (got[i]
+       != (signed char) (zv[i] - (signed char) ((int) xv[i] / (int) yv[i])))
+      __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/pr105116.c 
b/gcc/testsuite/gcc.target/aarch64/sve/pr105116.c
new file mode 100644
index 000000000000..84912356755e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/pr105116.c
@@ -0,0 +1,32 @@
+/* PR target/105116 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-split-wide-types" } */
+
+/* Wide GNU vectors pinned to SVE z registers and then scalarised by vector
+   lowering used to ICE in lra_split_hard_reg_for ("unable to find a register
+   to spill"): LRA reloaded a non-representable scalar subreg of the OImode
+   register pair into GENERAL_REGS, which cannot hold OImode.  Integer vector
+   divide and modulo have no Advanced SIMD form, so they are still scalarised
+   and exercise the reload path.  */
+
+typedef signed char vnx16qi __attribute__((vector_size (32)));
+
+#define TEST(NAME, OP)                                         \
+  void                                                         \
+  NAME (vnx16qi *x, vnx16qi y, vnx16qi z)                      \
+  {                                                            \
+    register vnx16qi dst  asm ("z0");                          \
+    register vnx16qi src1 asm ("z2");                          \
+    register vnx16qi src2 asm ("z4");                          \
+    dst = *x;                                                  \
+    src1 = y;                                                  \
+    src2 = z;                                                  \
+    asm volatile ("" :: "w" (dst), "w" (src1), "w" (src2));    \
+    dst = src2 - (OP);                                         \
+    asm volatile ("" :: "w" (dst));                            \
+    *x = dst;                                                  \
+  }
+
+TEST (vmul_vnx16qi, dst * src1)
+TEST (vdiv_vnx16qi, dst / src1)
+TEST (vmod_vnx16qi, dst % src1)

Reply via email to