Andrew Stubbs <[email protected]> writes:
> On 16/09/2026 18:35, Richard Sandiford wrote:
>> Andrew Stubbs <[email protected]> writes:
>>> On 16/09/2026 14:44, Richard Sandiford wrote:
>>>> Richard Biener <[email protected]> writes:
>>>>> On Thu, Sep 3, 2026 at 3:48 PM Andrew Stubbs <[email protected]> wrote:
>>>>>>
>>>>>> This patch makes the middle-end adjustments needed to allow a MEM
>>>>>> to take a
>>>>>> vector of addresses in order to read/write a non-contiguous vector
>>>>>> of data.
>>>>>> Mostly the changes don't have to do anything besides letting the
>>>>> vectors pass.
>>>>>>
>>>>>> There's a new MEM flag: MEM_INDEPENDENT_P that describes the store
>>>>>> semantics
>>>>>> of vectors with conflicting addresses. Nothing uses it, yet, but
>>>>>> it will
>>>>>> allow future optimizations that rely on left-to-right semantics to do
>>>>> the right
>>>>>> thing. GCN does not have such a guarantee, so it sets the flag.
>>>>>>
>>>>>> These changes, alone, are not enough to actually allow such MEMs, since
>>>>>> it's
>>>>>> unlikely that any back-end reports the vector modes "legitimate", so
>>>>>> there
>>>>>> should be no change of behaviour on any existing target (unconfirmed).
>>>>>>
>>>>>> I've modified the type of only the TARGET_ADDR_SPACE_VALID_POINTER_MODE
>>>>>> hook,
>>>>>> which is currently not being used by any target (amdgcn will use it in
>>>>>> the
>>>>>> following patch), and left the TARGET_VALID_POINTER_MODE alone so as not
>>>>>> to
>>>>>> have to touch all the other back-ends. If another back-end wants to use
>>>>>> vectors of addresses then I propose that either
>>>>>> TARGET_VALID_POINTER_MODE can
>>>>>> be fixed up at that time, or that backend simply switch to using
>>>>>> TARGET_ADDR_SPACE_VALID_POINTER_MODE.
>>>>>>
>>>>>> I have not made any changes to LRA code. I believe this is not
>>>>>> necessary as
>>>>>> long as the target uses "define_special_memory_constraint". I failed to
>>>>>> figure out how to make the regular memory constraints work, and as it's
>>>>>> not
>>>>>> necessary for my use case I'm loath to invest any more time into that.
>>>>>>
>>>>>> Hopefully the documentation patches should describe the intended
>>>>>> semantics.
>>>>>> I've tried to keep it very simple.
>>>>>>
>>>>>> There's one "TODO" in the code; I'm intentionally applying the principle
>>>>>> of
>>>>>> "don't add it if you don't need it" here (not least because I don't know
>>>>>> how
>>>>>> to test it). There's an assert to make sure it never bites anyone
>>>>>> silently.
>>>>>>
>>>>>> gcc/ChangeLog:
>>>>>>
>>>>>> * doc/rtl.texi: Document MEM_INDEPENDENT_P.
>>>>>> Document new MEM semantics.
>>>>>> * doc/tm.texi: Likewise.
>>>>>> * emit-rtl.cc (gen_int_mode): Extend to DTRT for vector modes.
>>>>>> (adjust_address_1): Permit vectors of addresses.
>>>>>> * explow.cc (convert_memory_address_addr_space_1): Permit
>>>>>> vectors of
>>>>>> addresses, for no-op conversions only.
>>>>>> * recog.cc (address_operand): Permit vectors of addresses.
>>>>>> * rtl.h (MEM_INDEPENDENT_P): New macro.
>>>>>> (get_address_mode): Change return type to machine_mode.
>>>>>> * rtlanal.cc (get_address_mode): Likewise.
>>>>>> * simplify-rtx.cc (simplify_context::simplify_subreg): Handle
>>>>>> vectors
>>>>>> of addresses.
>>>>>> * target.def (pointer_mode): Document vectors of addresses.
>>>>>> (address_mode): Likewise.
>>>>>> (valid_pointer_mode): Change scalar_int_mode to machine_mode.
>>>>>> * targhooks.cc (default_addr_space_valid_pointer_mode): Change
>>>>>> scalar_int_mode to machine_mode.
>>>>>> * targhooks.h (default_addr_space_valid_pointer_mode):
>>>>>> Likewise.
>>>>>> ---
>>>>>> gcc/doc/rtl.texi | 23 +++++++++++++++++++++++
>>>>>> gcc/doc/tm.texi | 13 ++++++++++++-
>>>>>> gcc/emit-rtl.cc | 30 ++++++++++++++++++++++++------
>>>>>> gcc/explow.cc | 12 ++++++++++--
>>>>>> gcc/recog.cc | 4 +++-
>>>>>> gcc/rtl.h | 7 ++++++-
>>>>>> gcc/rtlanal.cc | 4 ++--
>>>>>> gcc/simplify-rtx.cc | 5 ++++-
>>>>>> gcc/target.def | 13 ++++++++++---
>>>>>> gcc/targhooks.cc | 4 ++--
>>>>>> gcc/targhooks.h | 3 +--
>>>>>> 11 files changed, 97 insertions(+), 21 deletions(-)
>>>>>>
>>>>>> diff --git a/gcc/doc/rtl.texi b/gcc/doc/rtl.texi
>>>>>> index f606b97389f..a2af57233f4 100644
>>>>>> --- a/gcc/doc/rtl.texi
>>>>>> +++ b/gcc/doc/rtl.texi
>>>>>> @@ -703,6 +703,21 @@ application, this memory will never be
>>>>> subsequently modified.
>>>>>>
>>>>>> Stored in the @code{unchanging} field and printed as @samp{/u}.
>>>>>>
>>>>>> +@findex MEM_INDEPENDENT_P
>>>>>> +@cindex @code{mem} and @samp{/i}
>>>>>> +@item MEM_INDEPENDENT_P (@var{x})
>>>>>> +Nonzero in a @code{mem}, if the elements in a vector are written to
>>>>> memory as
>>>>>> +independent operations, not necessarily completing in left-to-right
>>>>> order. In
>>>>>> +such a store, the result of two vector elements writing to the same
>>>>> address, or
>>>>>> +overlapping addresses, is undefined. Otherwise, when the flag is
>>>>>> zero, the
>>>>>> +compiler can assume that the vector elements are known to write
>>>>> predictably and
>>>>>> +in order.
>>>>>> +
>>>>>> +This applies primarily when the write address is a vector of
>>>>>> addresses, e.g.
>>>>>> +@samp{(mem:V4SI/i (reg:V4DI r123))}.
>>>>>
>>>>> I'd fold this into the above and "if the elements in a vector are
>>>>> written to memory
>>>>> specified as a vector of addresses as independent operations", because
>>>>> otherwise
>>>>> it doesn't make sense(?)
>>>>
>>>> Yeah. And IMO the flag should be about the addresses rather than
>>>> the operation. I.e. something like:
>>>>
>>>> --------------------------------------------------------------------------
>>>> Nonzero in a @code{mem}, if the memory address is a vector and if that
>>>> vector must specify independent, non-overlapping memory locations.
>>>> If the vector fails to do this, the behavior is undefined.
>>>>
>>>> When writing to a @code{mem} that has a vector address, an instruction
>>>> must write to the elements as if in left-to-right order.
>>>> @code{MEM_INDEPENDENT_P} indicates that left-to-right order is
>>>> equivalent to any other order and so an instruction can write to each
>>>> element independently.
>>>> --------------------------------------------------------------------------
>>>
>>> I'm struggling to understand what this implies for code transformations,
>>> etc., and what happens when the flag is unset. How about this version?
>>>
>>> -------------------------------------------------------------------------
>>> In a @code{mem}, nonzero if the memory address is a vector and if that
>>> vector must specify independent, non-overlapping memory locations, to be
>>> used safely in a store. If the vector fails to do this, the behavior is
>>> undefined.
>>>
>>> In general, when writing to a @code{mem} that has a vector address, an
>>> instruction must write to the elements as if in left-to-right order.
>>> @code{MEM_INDEPENDENT_P} indicates that left-to-right order is
>>> equivalent to any other order and so an instruction can safely write to
>>> each element independently.
>>> --------------------------------------------------------------------------
>>
>> So I suppose the differences are:
>>
>> (1) adding "to be used safely in a store". But to me that implies that
>> !MEM_INDEPENDENT_P stores are unsafe, whereas they would be perfectly
>> safe on a target like AVX or SVE.
>
> I thought that was the point. Without the flag, writes are safe and well
> defined because the behaviour of the instructions matches the
> left-to-right semantics of the the RTL, and all is well with the world.
>
> With the flag set, machine instructions that do not operate strictly
> left-to-right can still be used because whatever generated the MEM is
> promising that the addresses are safe and "independent".
But to me, this isn't an issue of whether something is "safe" in a
general, target-independent sense, but instead whether a target supports
a particular operation. AVX and SVE support writes to vector mem
addresses with or without the flag, whereas GCN only supports them
with the flag.
> (Right now, GCN can assume that such operations are safe where the
> sources of the address vectors are gcn_expand_scalar_to_vector_address,
> that generates a series of contiguous addresses from a base address and
> mode, or the new SIMT vectorizer where loop iterations are declared
> "independent" by the use of OpenMP direfctives. In theory, the SIMD
> gather/scatter is not safe, but we have been making the assumption that
> it is since forever, so .... hmmmm.)
>
> The thought strikes me that I should probably be adding a
> MEM_INDEPENDENT_P check to my memory constraints.
Ah, yeah, I was assuming you were doing that.
> > I think the flag also makes logical sense in non-store contexts,
> > as optimisation information.
> >
>> (2) Adding "in general", which implies there are exceptions. What I was
>> trying to say was that the RTL semantics of storing to a mem are the
>> same for all targets, but that this flag allows a different
>> implementation of those semantics.
>
> I wanted to add that because it wasn't clear to me whether the second
> paragraph was logically linked to the first one, or not. Anyway ....
>
>> How about:
>>
>> --------------------------------------------------------------------------
>> This flag only applies to @code{mem}s that have vector addresses (that is,
>> if the address itself has a vector mode). The flag is nonzero if the
>> address vector is known to specify independent, non-overlapping memory
>> locations, so that the @code{mem} never refers to the same byte twice.
>>
>> Here, ``known to'' and ``never'' mean that the behavior of the program
>> is undefined if the addresses do overlap, rather than that overlap is
>> numerically impossible.
>>
>> This information can be useful when writing to a @code{mem} that has
>> a vector address. RTL semantics require such writes to behave
>> @emph{as if} they processed the vector in left-to-right order,
>> with later writes winning over earlier writes. However, when
>> @code{MEM_INDEPENDENT_P} is true, the order of the writes does
>> not matter for RTL semantics and so an instruction could write
>> to the elements in any order, or in parallel.
>> --------------------------------------------------------------------------
>>
>> Is that better in terms of code transformations etc? The idea is that
>> the flag is an assertion about the address values, rather than a
>> description of how the mem is used.
>
> I like the word "assertion" but it does not appear in the text.
>
> The flag is nonzero if the creator asserts that the address vector
> specifies independent, non-overlapping memory locations, ....
>
> This means that we don't need to define "known to" and "never". WDYT?
Yeah, I agree that's better. But I still think we should add the caveat
about UB. The assertion made by this flag seems weaker than the one made
by something like MEM_READONLY_P. If we have (mem/u X), we know that
X points to static, readonly memory as soon as X is available (unless
I'm forgetting a case). But if we have (mem/i V), we only know that V
contains non-overlapping addresses at points that are post-dominated by
the mem (taking exceptional control flow into account).
So how about something like:
--------------------------------------------------------------------------
This flag only applies to @code{mem}s that have vector addresses (that is,
to @code{mem}s whose addresses have a vector mode). The flag is nonzero
if the creator asserts that the @code{mem}'s address vector specifies
independent, non-overlapping memory locations, so that the @code{mem}
does not refer to the same byte twice.
This assertion does not mean that overlap is numerically impossible.
It instead means that, if the instruction containing the @code{mem}
is reached, the semantics of the source program require the addresses
to be independent. The behavior in other cases is undefined.
RTL semantics apply the following restrictions:
@itemize
@item
If the assertion made by @code{MEM_INDEPENDENT_P} does not hold at runtime,
reading from the @code{mem} will return an undefined value, but will have
no side effects other than reading from the specified bytes.
@item
Similarly, writing to such a @code{mem} will set the specified bytes
to undefined values, but will have no side effects other than that.
@end itemize
@code{MEM_INDEPENDENT_P} can be useful when writing to a @code{mem}
that has a vector address. RTL semantics require such writes to behave
@emph{as if} they processed the vector in left-to-right order, with later
writes winning over earlier writes. However, when @code{MEM_INDEPENDENT_P}
is true, an instruction can adhere to these semantics by writing to the
bytes in any order, or in parallel.
--------------------------------------------------------------------------
It's getting quite long and painful, but I think it's worth trying to
get right.
> The last paragraph looks good to me.
>
>> I suppose this means that, when MEM_INDEPENDENT_P is true:
>>
>> local var;
>>
>> if (cond)
>> write to (mem (vector referring to var))
>>
>> cannot be speculatively moved outside of the "if" unless we can prove that
>> the addresses really are independent, since otherwise we're speculating UB.
>> But I think that's true however we define this.
>
> How can memory writes be done speculatively anyway?
For a local variable like this, a store can be done speculatively under
certain conditions. For example:
void
bar ()
{
int x;
if (cond)
x = 1;
foo (&x);
}
can become:
void
bar ()
{
int x;
x = 1;
foo (&x);
}
> Anyway, doing anything with vectors speculatively is likely to be
> expensive.
Agreed. But still, GCC does things today that would have seemed unlikely
20 years ago :)
Thanks,
Richard