Hi Tamar,
> On 20 May 2026, at 20:30, Tamar Christina <[email protected]> wrote:
>
> The optimization added in g:210d06502f22964c7214586c54f8eb54a6965bfd has an
> implementation bug which makes it generate bogus code.
>
> The optimization was support to convert SVE loads with a known predicate into
> Adv. SIMD loads without the predicate.
>
> The current implementation is done at expansion time where the predicate is
> still clearly available.
>
> It does this by rewriting the loads to an Adv. SIMD load and then taking a
> paradoxical subreg of the result into an SVE vector.
>
> i.e. (subreg:VNx16QI (reg:QI 111) 0) for a byte load with a VL1 predicate.
>
> The issue is that the SVE loads were UNSPEC before and they didn't get
> optimized
> by passes like forwprop and cse. Adv. SIMD loads are.
>
> as such in cases where you have such a pattern:
>
> char[] p = {1,2,3,3};
> load (p, VL1)
>
> we used to generate
>
> mov w0, 1
> strb w0, [x19]
> ptrue p7.b, vl1
> ld1b z30.b, p7/z, [x19]
>
> which was dumb, but valid and the above optimization now gets the load
> eliminated and the constants folded. However, in particular for scalars,
> AArch64 has an optimization that's been a long for ages in which scalar FPR
> constants are created using vector broadcasting operations. It assumes
> scalars
> are accessed as scalars (as in, in the mode that created them).
>
> So the above gets optimized to
>
> movi v30.8b, 0x1
>
> which is invalid. The original load requires the inactive elements to be
> zero,
> where-as by using the paradoxical subreg it's relying on the implicit (as in,
> not modelled in RTL) assumption that the load zeros the top bits, but doesn't
> keep in mind that the load can be optimized away.
>
> This patch fixes it by creating a full SVE vector of 0s and writing only the
> values we want to set using an INSR. (i.e. using VL2 of bytes writes a short).
>
> It then provides patterns to optimize this:
>
> 1. if it's still following a load, just emit the load.
> 2. if it's not, then optimize it to a zero'ing operation. so e.g. HI mode
> issues an fmov h0, h0 and so clears the top bits to zero.
>
> I choose this representation because even without the above operations it is
> semantically valid and will generate correct code.
>
> The alternative would be to delay this optimization to e.g. combine however we
> have two problems there:
>
> 1. It's quite late, so the above constant cases for instance don't get
> optimized
> and we keep the pointless store and loads.
> 2. Our RTX costs don't model predicates. and so it may not accept the
> combination since the replacement is more expensive.
>
> So I chose to keep the optimization early, but just replace the paradoxical
> subreg with a zero-extend.
>
> Bootstrapped Regtested on aarch64-none-linux-gnu and no issues.
>
> Ok for master?
>
> Thanks,
> Tamar
>
> gcc/ChangeLog:
>
> PR target/125148
> * config/aarch64/aarch64-sve.md
> (*aarch64_vec_shl_insert_into_zero_<mode>,
> *aarch64_vec_shl_insert_into_zero_vnx16qi,
> *aarch64_vec_shl_insert_from_load_<mode>): New.
> * config/aarch64/aarch64.cc (aarch64_emit_load_store_through_mode):
> Replace paradoxical subreg with zero-extend.
>
> gcc/testsuite/ChangeLog:
>
> PR target/125148
> * gcc.target/aarch64/sve/highway_run.c: New test.
>
> ---
> diff --git a/gcc/config/aarch64/aarch64-sve.md
> b/gcc/config/aarch64/aarch64-sve.md
> index
> 019630eb8d21941b0ca718838ae6ef23e8aaedab..fc98f47d746019959baa6a8aa29d6b996d8b4822
> 100644
> --- a/gcc/config/aarch64/aarch64-sve.md
> +++ b/gcc/config/aarch64/aarch64-sve.md
> @@ -3004,6 +3004,51 @@ (define_insn "vec_shl_insert_<mode>"
> [(set_attr "sve_type" "sve_int_general")]
> )
>
> +;; Shift an SVE vector left and insert a scalar into element 0 of a zero
> +;; register.
> +(define_insn "*aarch64_vec_shl_insert_into_zero_<mode>"
> + [(set (match_operand:SVE_FULL_HSD 0 "register_operand")
> + (unspec:SVE_FULL_HSD
> + [(match_operand:SVE_FULL_HSD 1 "aarch64_simd_imm_zero")
> + (match_operand:<VEL> 2 "register_operand")]
> + UNSPEC_INSR))]
> + "TARGET_SVE"
> + {@ [ cons: =0 , 1 , 2 ; attrs: movprfx ]
> + [ w , Dz , w ; * ] fmov\t%<Vetype>0, %<Vetype>2
> + }
> + [(set_attr "type" "neon_move")]
> +)
There’s no need for the movprfx attribute on insns that don’t have a movprfx
alternative.
> +
> +;; Shift an SVE vector left and insert a scalar into element 0 of a zero
> +;; register for bytes.
> +(define_insn "*aarch64_vec_shl_insert_into_zero_vnx16qi"
> + [(set (match_operand:VNx16QI 0 "register_operand")
> + (unspec:VNx16QI
> + [(match_operand:VNx16QI 1 "aarch64_simd_imm_zero")
> + (match_operand:QI 2 "register_operand")]
> + UNSPEC_INSR))]
> + "TARGET_SVE"
> + {@ [ cons: =0 , 1 , 2 ; attrs: movprfx ]
> + [ w , Dz , w ; * ] fmov\t%h0, %h2\;and\t%0.h, %0.h,
> #0xff
> + }
> + [(set_attr "type" "neon_move")]
> +)
This should have the length attribute set to 8 to make sure branch range
computations remain valid.
That said, wouldn’t this be better as an insn_and_split so that the “and”
operation can be optimized and scheduled separately?
> +
> +;; Shift an SVE vector left and insert a scalar into element 0 from a memory
> +;; load.
> +(define_insn "*aarch64_vec_shl_insert_from_load_<mode>"
> + [(set (match_operand:SVE_FULL 0 "register_operand")
> + (unspec:SVE_FULL
> + [(match_operand:SVE_FULL 1 "aarch64_simd_imm_zero")
> + (match_operand:<VEL> 2 "memory_operand")]
> + UNSPEC_INSR))]
> + "TARGET_SVE"
> + {@ [ cons: =0 , 1 , 2 ; attrs: movprfx ]
> + [ w , Dz , m; * ] ldr\t%<Vetype>0, %2
> + }
> + [(set_attr "type" "neon_move")]
> +)
Same comment on movprfx being not needed.
Looks ok to me otherwise.
Thanks,
Kyrill
> +
> ;; -------------------------------------------------------------------------
> ;; ---- [INT] Linear series
> ;; -------------------------------------------------------------------------
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index
> 619c2a6d2265aba2db837559646fbf51e571c00e..1a6737c74b4189afa29b902d815be3d50dfec084
> 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -6887,29 +6887,6 @@ aarch64_stack_protect_canary_mem (machine_mode mode,
> rtx decl_rtl,
> return gen_rtx_MEM (mode, force_reg (Pmode, addr));
> }
>
> -/* Emit a load/store from a subreg of SRC to a subreg of DEST.
> - The subregs have mode NEW_MODE. Use only for reg<->mem moves. */
> -void
> -aarch64_emit_load_store_through_mode (rtx dest, rtx src, machine_mode
> new_mode)
> -{
> - gcc_assert ((MEM_P (dest) && register_operand (src, VOIDmode))
> - || (MEM_P (src) && register_operand (dest, VOIDmode)));
> - auto mode = GET_MODE (dest);
> - auto int_mode = aarch64_sve_int_mode (mode);
> - if (MEM_P (src))
> - {
> - rtx tmp = force_reg (new_mode, adjust_address (src, new_mode, 0));
> - tmp = force_lowpart_subreg (int_mode, tmp, new_mode);
> - emit_move_insn (dest, force_lowpart_subreg (mode, tmp, int_mode));
> - }
> - else
> - {
> - src = force_lowpart_subreg (int_mode, src, mode);
> - emit_move_insn (adjust_address (dest, new_mode, 0),
> - force_lowpart_subreg (new_mode, src, int_mode));
> - }
> -}
> -
> /* PRED is a predicate that is known to contain PTRUE.
> For 128-bit VLS loads/stores, emit LDR/STR.
> Else, emit an SVE predicated move from SRC to DEST. */
> @@ -26157,6 +26134,47 @@ aarch64_sve_expand_vector_init_subvector (rtx
> target, rtx vals)
> return;
> }
>
> +/* Emit a load/store from a subreg of SRC to a subreg of DEST.
> + The subregs have mode NEW_MODE. Use only for reg<->mem moves. */
> +void
> +aarch64_emit_load_store_through_mode (rtx dest, rtx src, machine_mode
> new_mode)
> +{
> + gcc_assert ((MEM_P (dest) && register_operand (src, VOIDmode))
> + || (MEM_P (src) && register_operand (dest, VOIDmode)));
> + auto mode = GET_MODE (dest);
> + auto int_mode = aarch64_sve_int_mode (mode);
> + rtx tmp_reg;
> + if (MEM_P (src))
> + {
> + rtx tmp = force_reg (new_mode, adjust_address (src, new_mode, 0));
> + if (!VECTOR_MODE_P (new_mode))
> + {
> + machine_mode full_mode = int_mode;
> + auto vmode = aarch64_classify_vector_mode (int_mode);
> + /* Partial vectors have to go through a full mode insert since we
> + don't support inserting an partial vectors. */
> + if (GET_MODE_INNER (int_mode) != new_mode || (vmode & VEC_PARTIAL))
> + full_mode
> + = aarch64_full_sve_mode (as_a <scalar_mode> (new_mode)).require ();
> +
> + /* Create an SVE register with the top bits explicitly zero'd. */
> + tmp_reg = force_reg (full_mode, CONST0_RTX (full_mode));
> + emit_insr (tmp_reg, tmp);
> + if (full_mode != int_mode)
> + tmp_reg = force_lowpart_subreg (int_mode, tmp_reg, full_mode);
> + }
> + else
> + tmp_reg = force_lowpart_subreg (int_mode, tmp, new_mode);
> + emit_move_insn (dest, force_lowpart_subreg (mode, tmp_reg, int_mode));
> + }
> + else
> + {
> + src = force_lowpart_subreg (int_mode, src, mode);
> + emit_move_insn (adjust_address (dest, new_mode, 0),
> + force_lowpart_subreg (new_mode, src, int_mode));
> + }
> +}
> +
> /* Check whether VALUE is a vector constant in which every element
> is either a power of 2 or a negated power of 2. If so, return
> a constant vector of log2s, and flip CODE between PLUS and MINUS
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/highway_run.c
> b/gcc/testsuite/gcc.target/aarch64/sve/highway_run.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..b73fd51c63bed6f9b4e710546924ca60736d10e5
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/highway_run.c
> @@ -0,0 +1,55 @@
> +/* { dg-do run { target aarch64_sve_hw } } */
> +/* { dg-require-effective-target lp64 } */
> +/* { dg-options "-O2" } */
> +
> +#include <arm_sve.h>
> +
> +extern void abort (void) __attribute__ ((noreturn));
> +
> +volatile int cond = 1;
> +
> +int __attribute__ ((noipa))
> +a (void)
> +{
> + return cond;
> +}
> +
> +#define TEST_LOAD(TYPE, NAME, BITS) \
> + int __attribute__ ((noipa)) \
> + test_##NAME (void) \
> + { \
> + TYPE *g = __builtin_malloc (sizeof (TYPE)); \
> + int c = 0; \
> + if (!g) \
> + abort (); \
> + g[0] = 0; \
> + if (a ()) \
> + { \
> + g[0] = 1; \
> + c = 2; \
> + } \
> + svint##BITS##_t d \
> + = svld1_s##BITS (svptrue_pat_b##BITS (SV_VL1), g); \
> + svbool_t e = svcmpgt_s##BITS (svptrue_b##BITS (), d, \
> + svdup_n_s##BITS (0)); \
> + int f = svptest_any (svptrue_pat_b##BITS (SV_VL1), e); \
> + if (f && c != 2) \
> + abort (); \
> + return f; \
> + }
> +
> +TEST_LOAD (signed char, byte, 8)
> +TEST_LOAD (short, short, 16)
> +TEST_LOAD (int, int, 32)
> +TEST_LOAD (long, long, 64)
> +
> +int
> +main (void)
> +{
> + if (!test_byte ()
> + || !test_short ()
> + || !test_int ()
> + || !test_long ())
> + abort ();
> + return 0;
> +}
>
>
> --
> <rb20550.patch>