Re: RFC: Allow "(mem:<vectype> (reg:<vectype>))"
Andrew Stubbs <[email protected]> Thu, 25 Jun 2026 11:20:27 +0100
| Newsgroups | gmane.comp.gcc.devel |
|---|---|
| Message-ID | <[email protected]> |
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. 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