On Thu, Jun 25, 2026 at 12:20 PM Andrew Stubbs <[email protected]> wrote:
>
> On 25/06/2026 10:05, Richard Biener wrote:
> > On Thu, Jun 25, 2026 at 10:52 AM Andrew Stubbs <[email protected]> wrote:
> >>
> >> On 23/06/2026 13:28, Michael Matz wrote:
> >>> Hello,
> >>>
> >>> On Mon, 22 Jun 2026, Andrew Stubbs wrote:
> >>>
> >>>>> TARGET_POINTER_MODE and TARGET_ADDR_SPACE_POINTER mode only return one 
> >>>>> mode,
> >>>>> but now we have multiple valid modes: DImode, V2DImode, V4DImode, etc. 
> >>>>> They
> >>>>> also return scalar_int_mode so I can't even invent a new address space 
> >>>>> for
> >>>>> the vector pointers.
> >>>>>
> >>>>> Same for TARGET_ADDR_SPACE_ADDRESS_MODE.
> >>>>>
> >>>>> TARGET_VALID_POINTER_MODE and TARGET_ADDR_SPACE_VALID_POINTER_MODE only
> >>>>> accept scalar_int_mode, so automatically exclude vector pointers.
> >>>>>
> >>>>> These can be patched to use plain machine_mode, perhaps, or keep them 
> >>>>> all
> >>>>> scalar and add a new TARGET_ADDR_SPACE_ALLOWS_VECTOR_POINTERS to fix the
> >>>>> problem at the call sites, which might make more sense?
> >>>>
> >>>> I've been working on this proposal some more. I've solved the above 
> >>>> problem by
> >>>> keeping all the hooks returning pointer/address types scalar, but 
> >>>> changing
> >>>> TARGET_ADDR_SPACE_VALID_POINTER_MODE to accept vector types, so the 
> >>>> backend
> >>>> can approve them. When the target independent code encounters a vector of
> >>>> addresses it checks that the inner mode matches the appropriate hook 
> >>>> values.
> >>>>
> >>>> I was hoping to post a patch series this week, but I've hit against 
> >>>> another
> >>>> issue.....
> >>>>
> >>>> I have an insn that looks like this:
> >>>>
> >>>>     (set (reg:V64SI 123)
> >>>>          (vec_merge:V64SI
> >>>>             (mem:V64SI (reg:V64DI 456))
> >>>>             (reg:V64SI 123)
> >>>>             (reg:DI 789)))
> >>>
> >>> How would anything in RTL-land see that this load is masked?  There's no
> >>> info anywhere that makes it differ from
> >>>
> >>>      (set (reg:V64SI 123)
> >>>           (vec_merge:V64SI
> >>>              (mem:V64SI (reg:V64DI 456))
> >>>              (reg:V64SI 123)
> >>>              (reg:DI 789)))
> >>>
> >>> with me saying that this is a vector merge operation with a memory operand
> >>> that is loaded completely.  It's a verbatim copy of your RTL pattern.  See
> >>> my point? :)
> >>
> >> I'm pretty sure you just restated my point. ;-)
> >>
> >>>> I can workaround the "reload" in the backend by using unspecs (exactly
> >>>> what I was trying to avoid), but does anyone have a better suggestion?
> >>>
> >>> (a) unspec
> >>> (b) teaching RTL-land generally that vec_merge(...(mem)) is "special" and
> >>>       its operands cannot just be lifted out (nah)
> >>> (c) biting the bullet and introduce and set a flag on (mem)s that it is
> >>>       "special"/partial
> >>> (c') similar flag on the vec_merge
> >>> (d) biting a different bullet and introduce a new top-level RTL expression
> >>>       (partial_mem:mode (addr) (mask))  (with either target-defined 
> >>> methods
> >>>       to specify content of unselected bits/bytes, or with a third operand
> >>>       to specify them (which then makes this just a different vec_merge)
> >>>
> >>> I think (d) is most elegant and most involved, (c) or (c') are the "best"
> >>> on a cost/benefit basis, (b) the most hacky, but may be fine if done via a
> >>> target hook and (a) the easiest but most unelegant.  (a) has subcases:
> >>> where to put the unspec: around the (mem), around the (vec_merge), around
> >>> the whole (set).  IMHO, around the vec_merge makes "most sense", whatever
> >>> that means for unspecs, but, well, still unspecs :-/
> >>
> >> I've been working to implement an unspec solution in the backend, for
> >> now, so that I can move on with this and get the actually working bits
> >> proposed.
> >>
> >> Putting an unspec around the whole pattern doesn't help. Nor does
> >> replacing the vec_merge with an unspec. In both cases the register
> >> allocator happily tries to break out the MEM, just as before.
> >>
> >> I tried creating an unspec that matches a custom memory constraint (the
> >> idea being that it will drop into the existing vec_merge patterns), but
> >> the register allocator now breaks out the unspec into another step.  If
> >> I use a non-memory-specific constraint then the same thing happens.  If
> >> I place the mask inside the unspec and have a specific instruction
> >> handle that, then it works, but now I have a masked load feeding into a
> >> masked move for no good reason.
> >>
> >> Here's what it looks like now:
> >>
> >>       (set (reg:V64SI 123)
> >>            (vec_merge:V64SI
> >>               (mem:V64SI (unspec:V64DI [(reg:V64DI 456) (reg:DI 789)]
> >>                                       UNSPEC_PARTIAL_ADDRVEC))
> >>               (reg:V64SI 123)
> >>               (reg:DI 789)))
> >>
> >> Note that the mask register, r789, now also appears within the MEM. When
> >> reload tries to break it out, gen_movv64si sees the new unspec and
> >> expands to a masked load.  The new instruction is basically identical to
> >> the one that it came from, but reload seems happy now, and my program
> >> runs correctly without the memory fault.
> >
> > x86 gets away with
> >
> > (define_insn "*<avx512>_load<mode>_mask"
> >    [(set (match_operand:V48_AVX512VL 0 "register_operand" "=v")
> >      (vec_merge:V48_AVX512VL
> >        (unspec:V48_AVX512VL
> >          [(match_operand:V48_AVX512VL 1 "memory_operand" "m")]
> >          UNSPEC_MASKLOAD)
> >        (match_operand:V48_AVX512VL 2 "nonimm_or_0_operand" "0C")
> >        (match_operand:<avx512fmaskmode> 3 "register_operand" "Yk")))]
> >
> > so the (unspec ...) is wrapped around the MEM.
>
> I tried that; it just tried to reload the entire unspec.

Ah...  aarch64 has

(define_insn "*aarch64_maskload<mode><vpred>"
  [(set (match_operand:SVE_ALL 0 "register_operand" "=w")
    (unspec:SVE_ALL
      [(match_operand:<VPRED> 2 "register_operand" "Upl")
       (match_operand:SVE_ALL 1 "memory_operand" "m")
       (match_operand:SVE_ALL 3 "aarch64_maskload_else_operand")]
      UNSPEC_LD1_SVE))]
  "TARGET_SVE"
  "ld1<Vesize>\t%0.<Vctype>, %2/z, %1"
  [(set_attr "sve_type" "sve_load_1reg")]
)

so they wrap the whole SET_RHS.

I think we should document some canonical way to represent the
masking side-effect given there's no native RTL construct.

> Either way
> around would work, for loads, but I needed to record the mask in order
> to reassemble the load safely.
>
> For stores, I have not observed the same reloads breaking things, but as
> Michael said there also isn't anything in the pattern to say that the
> set can't be separated from the vec_merge, so I wanted to do the same
> fixup there too. In that case, I believe unspec is not a valid set
> destination (or at least, I've failed to get that working in the past),
> so putting the unspec inside the MEM was convenient. I've not done a
> full testrun yet, but it seems to work, so far.
>
> >> Obviously, I had to handle the new unspec when validating and printing
> >> the MEM, but that has been fairly painless.
> >>
> >> BTW, the root cause of the problem transformation here seems to be that
> >> my load patterns must have early-clobber on the destination (to handle
> >> the unlikely case that only some of the lanes encounter a page fault and
> >> the instruction needs to be restarted for all of them), and if it so
> >> happens that the compiler wants to allocate the same register to both
> >> input and output then LRA attempts to honor the attribute by pulling out
> >> the MEM.  (I'm a little unclear how that ultimately helps, given that
> >> the new load has the exact same constraints as the original, but somehow
> >> it comes right.)
>
> Probably the difference is that x86 doesn't have this early-clobber
> constraint?
>
> >>
> >> Andrew
>

Reply via email to