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

Reply via email to