Re: RFC: Allow "(mem:<vectype> (reg:<vectype>))"

Andrew Stubbs <[email protected]> Thu, 25 Jun 2026 18:22:52 +0100
Newsgroups gmane.comp.gcc.devel
Message-ID <[email protected]>
On 25/06/2026 12:22, Richard Biener wrote:
> 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.

Again, I think this one only works if operand 1 never gets reloaded, 
which may be OK as there's no early clobber.

I had to modify my scheme though; it turns out (mem (unspec)) doesn't 
permit LRA to see the base/offset registers, so it only works if the 
registers within happen to get assigned to the right classes by other 
instructions.  However, there's provision in there to "add" a 
machine-specific unspec (known as "segment" in rtlanal.cc), so I can use 
that.  My addresses now look like this:

(mem:V64SI (plus:V64DI
              (unspec:V64DI [(reg:DI mask_reg)] UNSPEC_MASK)
              (plus:V64DI
                (reg:V64DI base_reg)
                (const_int offset))))

The mask_reg is only ever used to force new load instructions to use the 
vec_merge pattern, when something (a reload) tries to use the MEM out of 
context.

Andrew