The AVX-512 masked epilogue emits a masked load (UNSPEC_MASKLOAD under
a VEC_MERGE) feeding a masked binary op; the wrapper hides the memory
operand from combine, so it is not folded into the operation.

Add define_insn_and_split patterns that strip the wrapper.  The masked
off lanes of the load are zero, so the correctness of a fold depends on
the value of the result lanes that are masked off:

 - When an outer mask is present, the whole result is wrapped in
   vec_merge (op (...), const0, mask), so the off lanes are forced to
   zero regardless of the operator.  Both a commutative and a
   non-commutative pattern are provided for this case and accept every
   operator.

 - When there is no outer mask, the fold forces the result off lanes to
   zero, which only matches the operator when 0 op x == 0.  This holds
   for mult, and and umin, so the unmasked commutative pattern is
   restricted to those via the new zero_absorbing_operator
   predicate.  No unmasked non-commutative pattern is provided: for
   minus x - 0 == x and for div x / 0 is undefined, so neither is safe.

Iterator V1248FH_AVX512VLBW covers all maskable modes.

gcc/ChangeLog:

        PR target/123997
        * config/i386/predicates.md (sub_div_operator):
        New predicate.
        (zero_absorbing_operator): New predicate.
        * config/i386/sse.md (V1248FH_AVX512VLBW): New mode iterator.
        (*comm_maskload_fold_unmasked_op): New pattern.
        (*comm_maskload_fold_masked_op): New pattern.
        (*noncomm_maskload_fold_masked_op): New pattern.

gcc/testsuite/ChangeLog:

        PR target/123997
        * gcc.target/i386/avx512-maskload-fold-1.c: New test.
        * gcc.target/i386/avx512-maskload-fold-2.c: New test.

Co-authored-by: Venkataramanan Kumar <[email protected]>
Signed-off-by: Sarvesh Chandra <[email protected]>
---

Hi Hongtao,

Post our discussion at the Cauldron, where we discussed how vec_merge
(op ((mem), reg), 0, k) already has matching patterns in sse.md, doesn't
need a vec_merge (op (UNSPEC(mem), reg), 0, k) and a mem->reg conversion
would require a load so the match would always succeed, and safety is
guaranteed by the EVEX standard for these select instructions, I feel
this is the minimal patch that enforces both correctness and compactness
without an explosion of patterns. The more complex cases like vpavgb I
feel could be handled in GCC 18, through a combiner patch.

Regards,
Sarvesh

> Rather than describe what I want, let me show it.  Below is a two-pattern
> demo I built and tested on trunk.  It is FP add only -- deliberately
> minimal, not a patch -- but it demonstrates the structure I would like v2
> to follow: keep UNSPEC_MASKLOAD, no target hook, no op-specific pattern
> explosion.
>
> The key point is that you do not need to strip the UNSPEC to fold the
> memory operand.  Combine already hands recog the folded form; all that is
> missing is a define_insn of that shape.
>
> 1. The form the PR needs.
> --------------------------
>
> The MEM sits *inside* the UNSPEC and carries the "m" constraint, exactly
> as *<avx512>_load<mode>_mask already does.  That is what makes the RA
> question disappear: LRA never sees a bare MEM in an operator slot, so
> there is nothing for it to reload, and the UNSPEC still records that the
> access is masked so nothing downstream can widen it.
>
> (define_insn "*add<mode>3_maskload_fold_maskz"
>   [(set (match_operand:VFH_AVX512VL 0 "register_operand" "=v")
>     (vec_merge:VFH_AVX512VL
>       (plus:VFH_AVX512VL
>         (unspec:VFH_AVX512VL
>           [(match_operand:VFH_AVX512VL 1 "memory_operand" "m")]
>           UNSPEC_MASKLOAD)
>         (match_operand:VFH_AVX512VL 2 "register_operand" "v"))
>       (match_operand:VFH_AVX512VL 3 "const0_operand")
>       (match_operand:<avx512fmaskmode> 4 "register_operand" "Yk")))]
>   "TARGET_AVX512F"
>   "vadd<ssemodesuffix>\t{%1, %2, %0%{%4%}%{z%}|%0%{%4%}%{z%}, %2, %1}"
>   [(set_attr "type" "sseadd")
>    (set_attr "prefix" "evex")
>    (set_attr "mode" "<MODE>")])
>
> 2. Bridging what combine actually produces.
> --------------------------------------------
>
> Combine does not hand us the flat form above.  It hands us the nested one,
> with the inner vec_merge still in place, because simplify_merge_mask
> refuses to strip it for a trapping MEM.  From the -1.c dump on trunk:
>
>   Failed to match this instruction:
>   (set (reg:V8DF 133)
>       (vec_merge:V8DF (plus:V8DF (vec_merge:V8DF (unspec:V8DF [
>                           (mem:V8DF (plus:DI (mult:DI (reg:DI 129)
>                                       (const_int 8))
>                                   (reg/v/f:DI 120 [ a ])) [1  S64 A64])
>                       ] UNSPEC_MASKLOAD)
>                   (const_vector:V8DF [(const_double 0.0) repeated x8])
>                   (reg:QI 118))
>               (reg:V8DF 134))
>           (const_vector:V8DF [(const_double 0.0) repeated x8])
>           (reg:QI 118)))
>
> Since match_dup 3 forces both masks to be the same register, the outer
> mask already zeroes the off lanes and the inner merge is redundant.  So
> match that shape and split to the pattern above:
>
> (define_insn_and_split "*add<mode>3_maskload_fold_nested"
>   [(set (match_operand:VFH_AVX512VL 0 "register_operand")
>     (vec_merge:VFH_AVX512VL
>       (plus:VFH_AVX512VL
>         (vec_merge:VFH_AVX512VL
>           (unspec:VFH_AVX512VL
>         [(match_operand:VFH_AVX512VL 1 "memory_operand")]
>         UNSPEC_MASKLOAD)
>           (match_operand:VFH_AVX512VL 2 "const0_operand")
>           (match_operand:<avx512fmaskmode> 3 "register_operand"))
>         (match_operand:VFH_AVX512VL 4 "register_operand"))
>       (match_operand:VFH_AVX512VL 5 "const0_operand")
>       (match_dup 3)))]
>   "TARGET_AVX512F && ix86_pre_reload_split ()"
>   "#"
>   "&& 1"
>   [(set (match_dup 0)
>     (vec_merge:VFH_AVX512VL
>       (plus:VFH_AVX512VL
>         (unspec:VFH_AVX512VL [(match_dup 1)] UNSPEC_MASKLOAD)
>         (match_dup 4))
>       (match_dup 5)
>       (match_dup 3)))])
>
> Note the split *keeps* the UNSPEC.  That is the whole difference from your
> version: we remove the redundant inner mask, not the record that the load
> is masked.
>
> 3. Result.
> -----------
>
> 45 lines, no other changes.  avx512-maskload-fold-1.c:
>
>   before:  vmovupd (%r9,%rax,8), %zmm2{%k1}{z}
>            vmovupd (%r8,%rax,8), %zmm1{%k1}{z}
>            vaddpd  %zmm1, %zmm2, %zmm0{%k1}{z}
>
>   after:   vmovupd (%r9,%rax,8), %zmm0{%k1}{z}
>            vaddpd  (%r8,%rax,8), %zmm0, %zmm0{%k1}{z}
>
> pr97642-1.c still emits no k regs, and a maskz-load + maskz-add built from
> intrinsics is unaffected.
>
> And op can be generalized to other operations, similar like your masked 
> patterns.
>
> But for the those unmasked cases, GCC doesn't have per-lane analysis in the 
> rtl to decide if the transformation for the unmasked patterns are valid.
> Maybe in the vectorizer always generates MASKED_LOAD + COND_OP + MASKED_STORE 
> instead MASKED_LOAD + OP + MASKED_STORE. Then the masked define_insn and 
> define_insn_and_split can automatically matches that.
>
> BR,
> Hongtao

 gcc/config/i386/predicates.md                 | 10 ++
 gcc/config/i386/sse.md                        | 96 +++++++++++++++++++
 .../gcc.target/i386/avx512-maskload-fold-1.c  | 32 +++++++
 .../gcc.target/i386/avx512-maskload-fold-2.c  |  8 ++
 4 files changed, 151 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/i386/avx512-maskload-fold-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/avx512-maskload-fold-2.c

diff --git a/gcc/config/i386/predicates.md b/gcc/config/i386/predicates.md
index cf3a68a9fe8..c44eba44417 100644
--- a/gcc/config/i386/predicates.md
+++ b/gcc/config/i386/predicates.md
@@ -1740,6 +1740,16 @@
 (define_predicate "commutative_operator"
   (match_code "plus,mult,and,ior,xor,smin,smax,umin,umax"))
 
+;; Return true for the non-commutative ops we fold (minus, div).
+(define_predicate "sub_div_operator"
+  (match_code "minus,div"))
+
+;; Return true for commutative operators that absorb zero, where
+;; 0 op x == 0 (mult, and, umin).  Used to fold a zero-masked load into
+;; the op when there is no outer mask: the masked-off lanes stay zero.
+(define_predicate "zero_absorbing_operator"
+  (match_code "mult,and,umin"))
+
 ;; Return true if OP is a binary operator that can be promoted to wider mode.
 (define_predicate "promotable_binary_operator"
   (ior (match_code "plus,minus,and,ior,xor,ashift")
diff --git a/gcc/config/i386/sse.md b/gcc/config/i386/sse.md
index 51d1e9b455a..039c8428c77 100644
--- a/gcc/config/i386/sse.md
+++ b/gcc/config/i386/sse.md
@@ -2792,6 +2792,102 @@
    (set_attr "btver2_decode" "direct,double")
    (set_attr "mode" "<MODE>")])
 
+;; AVX-512 modes that support masked loads.
+
+(define_mode_iterator V1248FH_AVX512VLBW
+  [V16SF (V8SF "TARGET_AVX512VL") (V4SF "TARGET_AVX512VL")
+   V8DF  (V4DF "TARGET_AVX512VL") (V2DF "TARGET_AVX512VL")
+   (V32HF "TARGET_AVX512FP16")
+   (V16HF "TARGET_AVX512FP16 && TARGET_AVX512VL")
+   (V8HF  "TARGET_AVX512FP16 && TARGET_AVX512VL")
+   V16SI (V8SI "TARGET_AVX512VL") (V4SI "TARGET_AVX512VL")
+   V8DI  (V4DI "TARGET_AVX512VL") (V2DI "TARGET_AVX512VL")
+   (V64QI "TARGET_AVX512BW")
+   (V32QI "TARGET_AVX512VL && TARGET_AVX512BW")
+   (V16QI "TARGET_AVX512VL && TARGET_AVX512BW")
+   (V32HI "TARGET_AVX512BW")
+   (V16HI "TARGET_AVX512VL && TARGET_AVX512BW")
+   (V8HI  "TARGET_AVX512VL && TARGET_AVX512BW")])
+
+;; Fold masked load into commutative op, no outer mask.  Only ops
+;; where 0 op x == 0 (mult, and, umin) are safe here: the masked-off
+;; lanes of the load are zero, and forcing the result lanes to zero
+;; matches the op only for these.  Other ops need the outer-mask form.
+(define_insn_and_split "*comm_maskload_fold_unmasked_op"
+  [(set (match_operand:V1248FH_AVX512VLBW 0 "register_operand")
+       (match_operator:V1248FH_AVX512VLBW 1 "zero_absorbing_operator"
+         [(vec_merge:V1248FH_AVX512VLBW
+            (unspec:V1248FH_AVX512VLBW
+               [(match_operand:V1248FH_AVX512VLBW 2 "memory_operand")]
+               UNSPEC_MASKLOAD)
+            (match_operand:V1248FH_AVX512VLBW 3 "const0_operand")
+            (match_operand:<avx512fmaskmode> 4 "register_operand"))
+          (match_operand:V1248FH_AVX512VLBW 5 "register_operand")]))]
+  "TARGET_AVX512F"
+  "#"
+  "&& 1"
+  [(const_int 0)]
+{
+  rtx op = gen_rtx_fmt_ee (GET_CODE (operands[1]), <MODE>mode,
+                          operands[5], operands[2]);
+  rtx vm = gen_rtx_VEC_MERGE (<MODE>mode, op, operands[3], operands[4]);
+  emit_insn (gen_rtx_SET (operands[0], vm));
+  DONE;
+})
+
+;; Fold masked load into commutative op, outer mask.
+(define_insn_and_split "*comm_maskload_fold_masked_op"
+  [(set (match_operand:V1248FH_AVX512VLBW 0 "register_operand")
+       (vec_merge:V1248FH_AVX512VLBW
+         (match_operator:V1248FH_AVX512VLBW 1 "commutative_operator"
+           [(vec_merge:V1248FH_AVX512VLBW
+               (unspec:V1248FH_AVX512VLBW
+                 [(match_operand:V1248FH_AVX512VLBW 2 "memory_operand")]
+                 UNSPEC_MASKLOAD)
+               (match_operand:V1248FH_AVX512VLBW 3 "const0_operand")
+               (match_operand:<avx512fmaskmode> 4 "register_operand"))
+            (match_operand:V1248FH_AVX512VLBW 5 "register_operand")])
+         (match_operand:V1248FH_AVX512VLBW 6 "const0_operand")
+         (match_dup 4)))]
+  "TARGET_AVX512F"
+  "#"
+  "&& 1"
+  [(const_int 0)]
+{
+  rtx op = gen_rtx_fmt_ee (GET_CODE (operands[1]), <MODE>mode,
+                          operands[5], operands[2]);
+  rtx vm = gen_rtx_VEC_MERGE (<MODE>mode, op, operands[6], operands[4]);
+  emit_insn (gen_rtx_SET (operands[0], vm));
+  DONE;
+})
+
+;; Fold masked load into non-commutative op, outer mask.
+(define_insn_and_split "*noncomm_maskload_fold_masked_op"
+  [(set (match_operand:V1248FH_AVX512VLBW 0 "register_operand")
+       (vec_merge:V1248FH_AVX512VLBW
+         (match_operator:V1248FH_AVX512VLBW 1 "sub_div_operator"
+           [(match_operand:V1248FH_AVX512VLBW 5 "register_operand")
+            (vec_merge:V1248FH_AVX512VLBW
+               (unspec:V1248FH_AVX512VLBW
+                 [(match_operand:V1248FH_AVX512VLBW 2 "memory_operand")]
+                 UNSPEC_MASKLOAD)
+               (match_operand:V1248FH_AVX512VLBW 3 "const0_operand")
+               (match_operand:<avx512fmaskmode> 4 "register_operand"))])
+         (match_operand:V1248FH_AVX512VLBW 6 "const0_operand")
+         (match_dup 4)))]
+  "TARGET_AVX512F"
+  "#"
+  "&& 1"
+  [(const_int 0)]
+{
+  rtx op = gen_rtx_fmt_ee (GET_CODE (operands[1]), <MODE>mode,
+                          operands[5], operands[2]);
+  rtx vm = gen_rtx_VEC_MERGE (<MODE>mode, op, operands[6], operands[4]);
+  emit_insn (gen_rtx_SET (operands[0], vm));
+  DONE;
+})
+
+
 ;; Standard scalar operation patterns which preserve the rest of the
 ;; vector for combiner.
 (define_insn "*<sse>_vm<multdiv_mnemonic><mode>3"
diff --git a/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-1.c 
b/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-1.c
new file mode 100644
index 00000000000..9024eba79b5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-1.c
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=x86-64-v4 --param vect-partial-vector-usage=1" } */
+
+/* { dg-final { scan-assembler-times "vaddpd\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+/* { dg-final { scan-assembler-times "vmulpd\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+/* { dg-final { scan-assembler-times "vpaddd\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+/* { dg-final { scan-assembler-times "vpmulld\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+/* { dg-final { scan-assembler-times "vsubps\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+/* { dg-final { scan-assembler-times "vpsubq\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" 1 } } */
+
+void vector_add_fp (double *__restrict dst, const double *__restrict a,
+                    const double *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] + b[i]; }
+
+void vector_mul_fp (double *__restrict dst, const double *__restrict a,
+                    const double *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] * b[i]; }
+
+void vector_add_int (int *__restrict dst, const int *__restrict a,
+                     const int *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] + b[i]; }
+
+void vector_mul_int (int *__restrict dst, const int *__restrict a,
+                     const int *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] * b[i]; }
+
+void vector_sub_fp (float *__restrict dst, const float *__restrict a,
+                    const float *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] - b[i]; }
+
+void vector_sub_int64 (long long *__restrict dst, const long long *__restrict 
a,
+                       const long long *__restrict b, int n)
+{ for (int i = 0; i < n; ++i) dst[i] = a[i] - b[i]; }
diff --git a/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-2.c 
b/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-2.c
new file mode 100644
index 00000000000..6d69cc646d4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/avx512-maskload-fold-2.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=x86-64-v4 --param vect-partial-vector-usage=1" } */
+
+/* { dg-final { scan-assembler-not "vsubps\[ 
\\t\]+\[^\n\]*\\(\[^\n\]*%zmm\[0-9\]+\{%k\[1-7\]\}\{z\}" } } */
+
+void vector_sub_no_fold (float *__restrict dst, const float *__restrict a,
+                         float b, int n)
+{ for (int i = 0; i < n; i++) dst[i] = a[i] - b; }
-- 
2.34.1

Reply via email to