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 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.
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 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.
>>>> [...]
>>>> diff --git a/gcc/rtl.h b/gcc/rtl.h
>>>> index 77a71e5bbec..e523517884d 100644
>>>> --- a/gcc/rtl.h
>>>> +++ b/gcc/rtl.h
>>>> @@ -2653,6 +2653,11 @@ do { \
>>>> #define MEM_READONLY_P(RTX) \
>>>> (RTL_FLAG_CHECK1 ("MEM_READONLY_P", (RTX), MEM)->unchanging)
>>>>
>>>> +/* 1 if RTX is a mem whose vector elements are written to memory
>>>> + independently, not necessarily left-to-right. */
>>
>> I think this should be updated along the lines of the above.
>
> How about this?
>
> ---------------------------------------------------------------------
> /* 1 is RTX is a mem whose address vector elements all point to
> independent memory locations, without overlapping each other. */
> ---------------------------------------------------------------------
SGTM.
Thanks,
Richard