On Thu, Jan 15, 2026 at 4:11 AM Xin Wang <[email protected]> wrote:
>
> From: Xin Wang <[email protected]>
>
> This patch centralizes the delicate handling of MEM_REF offsets and base
> pointer equality into a new helper function im_compare_access_position_and
> _size, which is now used by both mem_ref_hasher::equal and is_self_write.
>
> gcc/ChangeLog:
> * tree-ssa-loop-im.cc (im_compare_access_position_and_size): New helper
> function to compare ao_ref position and size. (mem_ref_hasher::equal):
> Use the new helper for position and size comparison, keeping additional
> hash table specific checks. (is_self_write): Likewise, using the
> centralized helper after checking ref_decomposed.
>
> Signed-off-by: Xin Wang <[email protected]>
> ---
> gcc/tree-ssa-loop-im.cc | 117 ++++++++++++++++++++++++++--------------
> 1 file changed, 76 insertions(+), 41 deletions(-)
>
> diff --git a/gcc/tree-ssa-loop-im.cc b/gcc/tree-ssa-loop-im.cc
> index b9b1d92b518..bf9ae4a5fd9 100644
> --- a/gcc/tree-ssa-loop-im.cc
> +++ b/gcc/tree-ssa-loop-im.cc
> @@ -180,6 +180,9 @@ query_loop_dependence (class loop *loop, im_mem_ref *ref,
> dep_kind kind)
> return dep_unknown;
> }
>
> +/* Forward declaration. */
> +static bool im_compare_access_position_and_size (const ao_ref *, const
> ao_ref *);
Why not move this function up and not add the forward declaration? It
does not depend on anything defined in this file.
That is the only thing I have to add here as the TODO was added for
Richard to review it later. Plus we are in stage 4 so this
might have to wait until stage 1 of GCC 17.
Thanks,
Andrew
> +
> /* Mem_ref hashtable helpers. */
>
> struct mem_ref_hasher : nofree_ptr_hash <im_mem_ref>
> @@ -204,31 +207,30 @@ inline bool
> mem_ref_hasher::equal (const im_mem_ref *mem1, const ao_ref *obj2)
> {
> if (obj2->max_size_known_p ())
> - return (mem1->ref_decomposed
> - && ((TREE_CODE (mem1->mem.base) == MEM_REF
> - && TREE_CODE (obj2->base) == MEM_REF
> - && operand_equal_p (TREE_OPERAND (mem1->mem.base, 0),
> - TREE_OPERAND (obj2->base, 0), 0)
> - && known_eq (mem_ref_offset (mem1->mem.base) * BITS_PER_UNIT
> + mem1->mem.offset,
> - mem_ref_offset (obj2->base) * BITS_PER_UNIT +
> obj2->offset))
> - || (operand_equal_p (mem1->mem.base, obj2->base, 0)
> - && known_eq (mem1->mem.offset, obj2->offset)))
> - && known_eq (mem1->mem.size, obj2->size)
> - && known_eq (mem1->mem.max_size, obj2->max_size)
> - && mem1->mem.volatile_p == obj2->volatile_p
> - && (mem1->mem.ref_alias_set == obj2->ref_alias_set
> - /* We are not canonicalizing alias-sets but for the
> - special-case we didn't canonicalize yet and the
> - incoming ref is a alias-set zero MEM we pick
> - the correct one already. */
> - || (!mem1->ref_canonical
> - && (TREE_CODE (obj2->ref) == MEM_REF
> - || TREE_CODE (obj2->ref) == TARGET_MEM_REF)
> - && obj2->ref_alias_set == 0)
> - /* Likewise if there's a canonical ref with alias-set zero.
> */
> - || (mem1->ref_canonical && mem1->mem.ref_alias_set == 0))
> - && types_compatible_p (TREE_TYPE (mem1->mem.ref),
> - TREE_TYPE (obj2->ref)));
> + {
> + if (!mem1->ref_decomposed)
> + return false;
> +
> + /* Use the centralized helper for position and size comparison. */
> + if (!im_compare_access_position_and_size (&mem1->mem, obj2))
> + return false;
> +
> + /* Additional checks specific to hash table lookup. */
> + return (mem1->mem.volatile_p == obj2->volatile_p
> + && (mem1->mem.ref_alias_set == obj2->ref_alias_set
> + /* We are not canonicalizing alias-sets but for the
> + special-case we didn't canonicalize yet and the
> + incoming ref is a alias-set zero MEM we pick
> + the correct one already. */
> + || (!mem1->ref_canonical
> + && (TREE_CODE (obj2->ref) == MEM_REF
> + || TREE_CODE (obj2->ref) == TARGET_MEM_REF)
> + && obj2->ref_alias_set == 0)
> + /* Likewise if there's a canonical ref with alias-set zero.
> */
> + || (mem1->ref_canonical && mem1->mem.ref_alias_set == 0))
> + && types_compatible_p (TREE_TYPE (mem1->mem.ref),
> + TREE_TYPE (obj2->ref)));
> + }
> else
> return operand_equal_p (mem1->mem.ref, obj2->ref, 0);
> }
> @@ -3148,6 +3150,50 @@ ref_always_accessed_p (class loop *loop, im_mem_ref
> *ref, bool stored_p)
> ref_always_accessed (loop, stored_p));
> }
>
> +/* Returns true if the positions and sizes of two ao_ref structures are
> + equal. This handles MEM_REF offsets specially by merging the offset
> + from the MEM_REF base with the ao_ref offset.
> +
> + This helper centralizes the delicate handling of memory reference
> + comparison, used by both mem_ref_hasher::equal and is_self_write. */
> +
> +static bool
> +im_compare_access_position_and_size (const ao_ref *ref1, const ao_ref *ref2)
> +{
> + /* Check base pointer equality. For MEM_REF types, we need to
> + compare the underlying pointer operands and merge the offsets. */
> + if (TREE_CODE (ref1->base) == MEM_REF && TREE_CODE (ref2->base) == MEM_REF)
> + {
> + if (!operand_equal_p (TREE_OPERAND (ref1->base, 0),
> + TREE_OPERAND (ref2->base, 0), 0))
> + return false;
> +
> + /* Both are MEM_REF - compare merged offsets from base and offset. */
> + if (!known_eq (mem_ref_offset (ref1->base) * BITS_PER_UNIT
> + + ref1->offset,
> + mem_ref_offset (ref2->base) * BITS_PER_UNIT
> + + ref2->offset))
> + return false;
> + }
> + else if (!operand_equal_p (ref1->base, ref2->base, 0)
> + || !known_eq (ref1->offset, ref2->offset))
> + return false;
> +
> + /* Compare sizes. */
> + if (!known_eq (ref1->size, ref2->size))
> + return false;
> +
> + /* If both max_sizes are known, they must agree to ensure
> + no partial overlaps are possible. */
> + if (ref1->max_size_known_p () && ref2->max_size_known_p ())
> + {
> + if (!known_eq (ref1->max_size, ref2->max_size))
> + return false;
> + }
> +
> + return true;
> +}
> +
> /* Returns true if LOAD_REF and STORE_REF form a "self write" pattern
> where the stored value comes from the loaded value via SSA.
> Example: a[i] = a[0] is safe to hoist a[0] even when i==0. */
> @@ -3177,23 +3223,12 @@ is_self_write (im_mem_ref *load_ref, im_mem_ref
> *store_ref)
> if (stored_val != loaded_val)
> return false;
>
> + /* They may alias. Verify exact same location.
> + Use the centralized helper to handle MEM_REF offsets properly. */
> + if (!load_ref->ref_decomposed || !store_ref->ref_decomposed)
> + return operand_equal_p (load_ref->mem.ref, store_ref->mem.ref, 0);
>
> - /* TODO: Try to factor this out with mem_ref_hasher::equal
> - into im_compare_access_position_and_size
> - or a similar helper to centralize this delicate handling
> - complete for MEM_REF offsets and base pointer equality.
> -
> - TODO: Also ensure max_size_known_p agrees or resort to
> - alignment considerations to rule out partial overlaps.
> -
> - See:
> - https://gcc.gnu.org/pipermail/gcc-patches/2025-December/704155.html
> - For more context on TODOs above. */
> -
> - /* They may alias. Verify exact same location. */
> - return (operand_equal_p (load_ref->mem.base, store_ref->mem.base, 0)
> - && known_eq (load_ref->mem.size, store_ref->mem.size)
> - && known_eq (load_ref->mem.offset, store_ref->mem.offset));
> + return im_compare_access_position_and_size (&load_ref->mem,
> &store_ref->mem);
>
> }
>
> --
> 2.34.1
>