On Wed, 24 Jun 2026, Tamar Christina wrote:

> > -----Original Message-----
> > From: Richard Biener <[email protected]>
> > Sent: 24 June 2026 13:02
> > To: Alfie Richards <[email protected]>
> > Cc: [email protected]; Tamar Christina <[email protected]>;
> > [email protected]; [email protected]; [email protected]
> > Subject: Re: [PATCH v1 0/8] RFC: vect: Add first faulting read support
> > 
> > On Tue, 23 Jun 2026, Alfie Richards wrote:
> > 
> > > Hi All,
> > >
> > > This is my first attempt at an implementation of FFR.
> > 
> > I'm replying (late) to this mail before reading all the followup
> > discussion.  Sorry if parts are answered already.
> > 
> > > I think this needs some background and explanation
> > > (probably should be a comment at some point)
> > >
> > > == AArch64 first faulting read ==
> > >
> > > AArch64 first faulting reads are safe to execute speculatively because the
> > > hardware is able to return less than a full vector read.
> > >
> > > The loads (both "first faulting" (FF) and "non-faulting" (NF)) set a 
> > > predicate
> > > register (first fault register) with a mask of loaded elements.
> > >
> > > The FF variant will fault only if the first active element to be loaded
> > > causes a fault. This guarantees forwards progress.
> > >
> > > The NF variant will never cause a fault, at the expense of possibly
> > > loading no elements.
> > >
> > > Architecturally, these loads are guaranteed to never cause a fault unless
> > > it is a FF load and there is a fault for the first element.
> > >
> > > However, the hardware can also do partial loads whenever it wants.
> > > For instance this could happen at page boundaries or cache faults.
> > >
> > > This means an FFR vectorized loop needs to be able to recover from partial
> > > loads back to vectoried code.
> > > (this seems to not be the case for riscv's equivalent feature).
> > 
> > Alternatively such "bad" hardware could be forced to fall to a scalar
> > epilog.
> > 
> > > Also of note, the first fault register starts as all 1's, and each 
> > > subsequent
> > > load updates it if they fail to load any elements by diabling the bits of
> > > elements not loaded and all bits afterwards.
> > >
> > > For instance, it may start as
> > > [1,1,1,1,1,1,1,1]
> > > then a load is executed and could change that to
> > > [1,1,1,1,1,1,0,0]
> > >
> > > But then the next load, even if it loads all elements, will not re-enable
> > > the last two bits.
> > >
> > > Additionally, you never get a state like
> > > [1,1,1,1,1,0,0,1]
> > > where there are inactive elements followed by an active element.
> > >
> > > == Vectorized code structure ==
> > >
> > > The use case currently is for loops with mutually misaligned pointers, 
> > > both
> > > of which require safe speculative reads, where we cannot peel for 
> > > alignment,
> > or
> > > would not want to incur the code size cost of versioning to check if we 
> > > can
> > > peel.
> > >
> > > For aarch64 this is only for early break as there aren't any other 
> > > situations
> > > where we need safe speculative reads.
> > >
> > > Then for a loop such as
> > >
> > > int
> > > foo_no_vect (uint16_t *const restrict src1,
> > >              uint16_t *const restrict src2,
> > >              unsigned int n)
> > > {
> > >   for (int i = 0; i < n; i++)
> > >     {
> > >       uint16_t v1 = src1[i];
> > >       uint16_t v2 = src2[i];
> > >       if (v1 + v2 == 0)
> > >         return 1;
> > >     }
> > >   return 0;
> > > }
> > >
> > > We generate the following:
> > >
> > > GIMPLE (after dce):
> > >
> > >   _78 = max_mask_75 & _77;
> > >   ffr_preservation_82 = .READ_FAULT_STATE ();
> > >   .SET_FAULT_STATE ({ -1, ... });
> > >
> > >   <bb 3> [local count: 1014686025]:
> > >   # vectp_src1.7_45 = PHI <vectp_src1.7_46(7), vectp_src1.8_41(13)>
> > >   # vectp_src2.11_60 = PHI <vectp_src2.11_61(7), vectp_src2.12_56(13)>
> > >   # ivtmp_72 = PHI <ivtmp_73(7), 0(13)>
> > >   # loop_mask_48 = PHI <next_mask_ffr_81(7), _78(13)>
> > >   vect_v1_14.9_49 = .MASK_FIRSTFAULT_LOAD (vectp_src1.7_45, 64B,
> > loop_mask_48, { 0, ... });
> > >   vect__5.10_55 = (vector([4,4]) int) vect_v1_14.9_49;
> > >   vect_v2_16.13_63 = .MASK_FIRSTFAULT_LOAD (vectp_src2.11_60, 64B,
> > loop_mask_48, { 0, ... });
> > >   vect__6.14_65 = (vector([4,4]) int) vect_v2_16.13_63;
> > >   ffr_mask_83 = .READ_FAULT_STATE ();
> > 
> > There seems to be no data dependence between .MASK_FIRSTFAULT_LOAD
> > and .READ_FAULT_STATE which IMO is bad design (see all the issues
> > we have with FPU state accesses).  You supposedly do not want to
> > have a dependence (due to the FFR) between the two
> > .MASK_FIRSTFAULT_LOAD
> > so an extra inout argument to that isn't intended.  So the
> > alternative is to have separate virtual outputs and have
> > .READ_FAULT_STATE merge them via having N inputs?
> 
> I think the above isn't a verbose dump, there should be a dependency on
> The VOPS chain to prevent both the re-ordering between fault groups and
> stores.
> 
> This mirrors how the intrinsics are modelled so we kept the same dependency
> so that we can tell when interacting with intrinsics code.

Hmm.  How does that translate to RTL then?  Is this UNSPEC_VOLATILE
and we treat that as a scheduling barrier (huh? do we?)?

> At least that was the idea. I haven't looked at the implementation yet but I 
> am
> expecting a virtual use chain.

That of course means that .MASK_FIRSTFAULT_LOAD are actually stores
to GIMPLE.  Maybe that isn't bad and we hopefully do not need any
followup optimizations to such code anywhere ...

Richard.

> 
> Thanks,
> Tamar
> > 
> > >   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)
> 

-- 
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)

Reply via email to