if (ffr_mask_83 == { -1, ... })
goto <bb 14>; [99.95%]
else
goto <bb 15>; [0.05%]
<bb 15> [local count: 10146860]:
.SET_FAULT_STATE ({ -1, ... });
_84 = ~ffr_mask_83;
ffr_loop_mask_85 = loop_mask_48 & ffr_mask_83;
<bb 14> [local count: 1014686025]:
# ffr_loop_mask_68 = PHI <loop_mask_48(3), ffr_loop_mask_85(15)>
# ffr_num_iters_36 = PHI <POLY_INT_CST [4, 4](3), 0(15)>
# next_mask_ffr_80 = PHI <{ -1, ... }(3), _84(15)>
vect__7.15_66 = vect__5.10_55 + vect__6.14_65;
mask_patt_28.16_67 = vect__7.15_66 == { 0, ... };
vec_mask_and_69 = mask_patt_28.16_67 & ffr_loop_mask_68;
if (vec_mask_and_69 != { 0, ... })
goto <bb 9>; [5.50%]
else
goto <bb 4>; [94.50%]
<bb 9> [local count: 55807731]:
.SET_FAULT_STATE (ffr_preservation_82);
goto <bb 5>; [100.00%]
<bb 4> [local count: 958878295]:
_47 = ffr_num_iters_36 * 2;
vectp_src1.7_46 = vectp_src1.7_45 + _47;
vectp_src2.11_61 = vectp_src2.11_60 + _47;
ivtmp_73 = ivtmp_72 + ffr_num_iters_36;
next_mask_79 = .WHILE_ULT (ivtmp_73, _74, { 0, ... });
next_mask_ffr_81 = next_mask_79 & next_mask_ffr_80;
if (next_mask_ffr_81 != { 0, ... })
goto <bb 7>; [94.50%]
else
goto <bb 12>; [5.50%]
<bb 12> [local count: 52738306]:
.SET_FAULT_STATE (ffr_preservation_82);
goto <bb 5>; [100.00%]
<bb 7> [local count: 906139989]:
goto <bb 3>; [100.00%]
<bb 5> [local count: 114863531]:
# _10 = PHI <1(9), 0(12), 0(2)>
return _10;
Or final assembly:
.L5:
add x4, x4, x3
whilelo p7.s, x4, x2
add x0, x0, x3, lsl 1
and p7.b, p7/z, p14.b, p14.b
add x1, x1, x3, lsl 1
ptest p15, p7.b
b.none .L7
.L6:
ldff1h z31.s, p7/z, [x0]
ldff1h z30.s, p7/z, [x1]
cntw x3
rdffr p14.b
nots p13.b, p15/z, p14.b
b.any .L11
.L4:
add z31.s, z31.s, z30.s
cmpeq p7.s, p7/z, z31.s, #0
b.none .L5
mov w0, 1
ret
== Notes ==
- When there is a "partial read", instead of treating that as a partial
iteration
and advancing by the number of loaded elements, we intead advance by 0
iterations ard repeat the same iteration with the previously processed
elements masked out. This preserves alignment with the starting position
and avoids having to do anything awkward such as possibly rotating
invariant vectors.
So the advantage is that we never need a scalar fallback? But ISTR
there's other reasons why we might need that for early break still?
- This prioritises the "good" case, by trying to keep
the "full read" path as tight as possible, and adding
a fixup branch to handle the case where there is a partial read.
- We preserve the state of the FFR register over the vectoried loop.
This is to prevent code written with intrinsics breaking by clobbering
their value in the FFR register. However, as the FFR is not preserved by
the AAPCS, this is nearly always optimized out.
- One downside of this approach is I dont think it will translate cleanly for
len based loop vectorization (riscv). However, as I understand it, the
riscv equivalent feature never does partial loads unless there is a genuine
fault, so we will not need to worry about recovering back to
vectorized code after a partial read (as it will either take the early break
or the fault). So no fixup should be needed and can use a subset of this.
== Remaining work to do ==
- Versioning
As FFR introduces overhead, it will always be slower than a mutually
aligned loop. So my ideal code generation for a vector with two pointers
requiring safe speculative reads is:
At O2, where we do not want to incur the code size cost of versioning,
instead just use FFR to vectorize the loop (if profitable).
At O3, version to create a mutually aligned non-FFR case and a (current
code gen)
if the pointers are mutually misaligned.
This "FFR versioning" is not yet implemented.
- Costing
I haven't done anything to cost FFR yet, we will want accurate costing of
FFR
vs scalar and vs non-FFR vectorized to make the decisions on versioning and
what to use.
- Optimization of the generated code
The generated code has room for improvement, primarily using rdffrs
would
be a performance gain, and reducing some of the moves within the hot
section of the loop.
== Feedback wanted ==
- Overall design of this
- What to call this within GCC (it's not really "first fault reads", maybe
"hardware safe speculative reads" (HSSR)?)
- How we can sensibly cost this to allow the choice to do scalar over
FFR.
When a load is partial you need to consider the FFR for all computations
that require masking by loop masking. Your example is simplified
to not have any, but consider a division - you show an else value
of zero, so before any use of loop_mask on an operation that is
dependent on the FFR affecting load value you need to read the FFR
and combine it with the loop mask, right?
Sorry for the essay!
No need to sorry, it's very helpful.
ISTR AVX10.x introduces some non-faulting loads but I have to check.
Richard.
Bootstrapped and reg tested for aarch64, x68-64, arm32hf, riscv.
I also ran vect.exp with --param=vect-ffr-usage=2 and -msve-vector-
bits=128
with no errors.
Thoughts?
King regards,
Alfie
Alfie Richards (8):
vect: Add internal functions and optabs for first fault loads
vect: Add SLP_NODE argument to vect_get_loop_mask.
vect: Make vect_maybe_permute_loop_masks optional and retargetable
vect: Add EXCLUDE_VIRTUALS argument to _slp_tree::push_vec_def.
vect: Add vect-ffr-usage param.
vect: Add FFR analysis
vect: Add FFR transformation
vect: Enable FFR
.../aarch64/aarch64-sve-builtins-base.cc | 78 +++--
gcc/config/aarch64/aarch64-sve-builtins.cc | 24 ++
gcc/config/aarch64/aarch64-sve-builtins.h | 1 +
gcc/config/aarch64/aarch64-sve.md | 201 +++++++++--
gcc/config/aarch64/aarch64.cc | 10 +
gcc/config/aarch64/iterators.md | 2 +
gcc/internal-fn.cc | 24 ++
gcc/internal-fn.def | 18 +
gcc/optabs-tree.cc | 57 +++
gcc/optabs-tree.h | 2 +
gcc/optabs.def | 5 +
gcc/params.opt | 4 +
gcc/testsuite/gcc.target/aarch64/sve/ffr_1.c | 16 +
gcc/testsuite/gcc.target/aarch64/sve/ffr_2.c | 35 ++
gcc/testsuite/gcc.target/aarch64/sve/ffr_3.c | 42 +++
gcc/testsuite/gcc.target/aarch64/sve/ffr_4.c | 25 ++
gcc/testsuite/gcc.target/aarch64/sve/ffr_5.c | 25 ++
gcc/testsuite/gcc.target/aarch64/sve/ffr_6.c | 43 +++
.../gcc.target/aarch64/sve/ffr_6_run.c | 81 +++++
gcc/testsuite/gcc.target/aarch64/sve/ffr_7.c | 16 +
gcc/testsuite/gcc.target/aarch64/sve/ffr_8.c | 16 +
gcc/testsuite/gcc.target/aarch64/sve/ffr_9.c | 18 +
.../gcc.target/aarch64/sve/noeffect11.c | 2 +-
.../gcc.target/aarch64/sve/peel_ind_12.c | 2 +-
.../gcc.target/aarch64/sve/peel_ind_12_run.c | 2 +-
.../gcc.target/aarch64/sve/pfalse-load.c | 6 +-
gcc/tree-data-ref.cc | 4 +
gcc/tree-ssa-alias.cc | 2 +
gcc/tree-ssa-loop-ivopts.cc | 2 +
gcc/tree-vect-data-refs.cc | 3 +-
gcc/tree-vect-loop-manip.cc | 328 +++++++++++++++++-
gcc/tree-vect-loop.cc | 264 +++++++++++++-
gcc/tree-vect-slp.cc | 32 +-
gcc/tree-vect-stmts.cc | 165 +++++++--
gcc/tree-vectorizer.h | 63 +++-
35 files changed, 1496 insertions(+), 122 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_2.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_3.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_4.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_5.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_6.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_6_run.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_7.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_8.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/ffr_9.c
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich; (HRB 36809, AG
Nuernberg)