Hi Richard,

> On 7 Jul 2026, at 11:36, Richard Biener <[email protected]> wrote:
> 
> On Tue, 30 Jun 2026, [email protected] wrote:
> 
>> From: Kyrylo Tkachov <[email protected]>
>> 
>> A gimple register with no register mode (e.g. an oversized vector such as 
>> V16DI
>> on AArch64, which is BLKmode) may have several simultaneously live SSA 
>> versions
>> that out-of-SSA cannot coalesce, so they end up in separate partitions 
>> sharing
>> one base VAR_DECL.  When such partitions are spilled they are given distinct
>> stack slots, but set_mem_attributes gives every slot that one decl as its
>> MEM_EXPR at offset 0, so the slots all appear to be the same object.
>> 
>> This misleads MEM_EXPR-based disambiguation: the load/store pair-fusion pass
>> keys candidate accesses on (decl, offset) and fuses stores across the slots,
>> corrupting one and producing wrong code at -O2/-O3 (PR123625, and PR121957
>> where late-combine first reshapes the addressing into this form).
>> 
>> This is an alternative fix to the expand one posted at:
>> https://gcc.gnu.org/pipermail/gcc-patches/2026-June/721973.html
>> 
>> Fix it in remove_ssa_form after coalescing, give every partition
>> after the first of such a BLKmode decl its own artificial decl, so the slots
>> are distinguished.  The new decl carries a DECL_DEBUG_EXPR back to the user
>> variable so debug info still attributes the storage to it (as
>> create_access_replacement does in tree-sra.cc).  PARM_DECLs and RESULT_DECLs
>> are left alone.  They require a single partition holding the canonical RTL.  
>> The
>> split runs after find_replaceable_exprs so it does not perturb TER.
>> 
>> Bootstrapped and tested on aarch64-none-linux-gnu and x86_64-linux.
>> Is this preferable to the expand fix?
> 
> While I like this more I think the actual issue is in set_rtl.cc which
> does strange things, but notably
> 
>      tree next = skip ? cur : leader_merge (cur, SSAVAR (t) ? SSAVAR (t) 
> : t);
> 
>      if (cur != next)
>        {
>          if (MEM_P (x))
>            set_mem_attributes (x,
>                                next && TREE_CODE (next) == SSA_NAME
>                                ? TREE_TYPE (next)
>                                : next, true);
> 
> that is, it is responsible for turning in SSA_NAME_VAR to
> set_mem_attributes.  It shouldn't do that if there are multiple
> partitions for it.  In fact I'm not sure why we ever do all this
> singing and dancing.

This area isn’t exactly my strong suit but judging by the archaeology…
This is for >=01 debug info from PR64164 copyrename removal.
At -O0 it goes through the DECL_RTL recording.

> 
> IMO a simple fix(?) would be to drop all this and do
> 
>     tree next = skip ? cur : leader_merge (cur, t);
> 
> esp. when MEM_P (x).

That would fix the sharing, but it degrades every named memory partition to 
type-only attrs, losing MEM_EXPR, MEM_OFFSET and MEM_NOTRAP_P also in the case 
where a variable has exactly one partition.


>  There's similarly dubious code after this,
> but we already note the issue!
> 
>  if (TREE_CODE (t) == SSA_NAME)
>    {
>      int part = var_to_partition (SA.map, t);
>      if (part != NO_PARTITION)
>        {
>          if (SA.partition_to_pseudo[part])
>            gcc_assert (SA.partition_to_pseudo[part] == x);
>          else if (x != pc_rtx)
>            SA.partition_to_pseudo[part] = x;
>        }
>      /* For the benefit of debug information at -O0 (where
>         vartracking doesn't run) record the place also in the base
>         DECL.  For PARMs and RESULTs, do so only when setting the
>         default def.  */
>      if (x && x != pc_rtx && SSA_NAME_VAR (t)
>          && (VAR_P (SSA_NAME_VAR (t))
>              || SSA_NAME_IS_DEFAULT_DEF (t)))
>        {
>          tree var = SSA_NAME_VAR (t);
>          /* If we don't yet have something recorded, just record it now.  
> */
>          if (!DECL_RTL_SET_P (var))
>            SET_DECL_RTL (var, x);
>          /* If we have it set already to "multiple places" don't
>             change this.  */
>          else if (DECL_RTL (var) == pc_rtx)
>            ;
>          /* If we have something recorded and it's not the same place
>             as we want to record now, we have multiple partitions for the
>             same base variable, with different places.  We can't just
>             randomly chose one, hence we have to say that we don't know.
>             This only happens with optimization, and there var-tracking
>             will figure out the right thing.  */
>          else if (DECL_RTL (var) != x)
>            SET_DECL_RTL (var, pc_rtx);
> ^^^^^^^^^^^^^^^^^^
> 
> what seems to be lacking is direct info of whether a particular
> DECL has multiple SSA partitions.
> 
> Upthread we get here via expand_stack_vars -> expand_one_stack_var_at
> where there is the stack_vars[] meta where I can see for one of the
> testcases:
> 
> (gdb) p stack_vars[0]
> $19 = {decl = <ssa_name 0x7ffff672dd10 3>, size = {coeffs = {128, 0}}, 
>  alignb = 16, representative = 0, next = 4294967295, conflicts = 0x0}
> (gdb) p stack_vars[1]
> $20 = {decl = <ssa_name 0x7ffff6779318 10>, size = {coeffs = {128, 0}}, 
>  alignb = 16, representative = 1, next = 4294967295, conflicts = 0x0}
> (gdb) p debug_generic_expr (0x7ffff672dd10)
> v_3
> $21 = void
> (gdb) p debug_generic_expr (0x7ffff6779318)
> v_10
> 
> so there's two stack vars for the same SSA_NAME_VAR, exactly the
> situation above.  It should be possible to discover this case
> beforehand and mark the affected stack_vars[] entries as to
> skip using SSA_NAME_VAR here, possibly pass down the decl/type
> to be used to set_rtl directly.
> 
> Your out-of-SSA patch might go for preserving as much info
> as possible, so the last quoted piece above could be made
> an assert that this doesn't happen after it?

that would fire on valid code: with -ftree-coalesce-vars a variable's names can 
be coalesced into memory partitions of other variables, and then DECL_RTL (b) 
legitimately sees two different MEMs

>  The only thing
> is that keying on BLKmode might not be fully matching what
> we do later.

Yeah, I can fix that in the patch.

>  In principle the decl to be used could be
> created in expand_stack_vars as well?
> 
> It was Alex who at least last touched all this and most of
> set_rtl is attributed to ...

I can send a v2 with the above fix and more test cases for where the assert 
above you suggested would fire once I finish more testing while giving folks a 
chance to chime in.
Thanks,
Kyrill

> 
> Thanks,
> Richard.
> 
> 
> 
>> Thanks,
>> Kyrill
>> 
>> gcc/ChangeLog:
>> 
>> PR target/123625
>> PR target/121957
>> * tree-outof-ssa.cc (split_overlapping_partition_decls): New function.
>> (remove_ssa_form): Call it.
>> 
>> gcc/testsuite/ChangeLog:
>> 
>> PR target/123625
>> PR target/121957
>> * gcc.c-torture/execute/pr123625.c: New test.
>> * gcc.c-torture/execute/pr121957.c: New test.
>> 
>> Signed-off-by: Kyrylo Tkachov <[email protected]>
>> ---
>> .../gcc.c-torture/execute/pr121957.c          | 27 +++++++
>> .../gcc.c-torture/execute/pr123625.c          | 48 ++++++++++++
>> gcc/tree-outof-ssa.cc                         | 75 +++++++++++++++++++
>> 3 files changed, 150 insertions(+)
>> create mode 100644 gcc/testsuite/gcc.c-torture/execute/pr121957.c
>> create mode 100644 gcc/testsuite/gcc.c-torture/execute/pr123625.c
>> 
>> diff --git a/gcc/testsuite/gcc.c-torture/execute/pr121957.c 
>> b/gcc/testsuite/gcc.c-torture/execute/pr121957.c
>> new file mode 100644
>> index 00000000000..98969566c29
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.c-torture/execute/pr121957.c
>> @@ -0,0 +1,27 @@
>> +/* AArch64 wrong code at -O2/-O3.  An oversized vector
>> +   (V16DI, 128 bytes, with no register mode) is expanded into two distinct
>> +   stack slots that share the same MEM_EXPR.  The load/store pair-fusion 
>> pass
>> +   then discovers "adjacent" store pairs across the two slots via the shared
>> +   MEM_EXPR and fuses them, redirecting a store to the wrong slot and 
>> leaving
>> +   part of a slot uninitialised.  Self-checking: aborts if the result is
>> +   wrong.  */
>> +
>> +typedef long __attribute__((vector_size (16 * sizeof (long)))) v16di;
>> +
>> +int
>> +main (void)
>> +{
>> +  v16di v = {};
>> +  asm goto ("" : : : : L1);
>> +L2:
>> +  asm goto ("" : : : : L1);
>> +L0:
>> +  asm goto ("" : : : : L2);
>> +  v = (v16di){ -1 };
>> +  asm goto ("" : : : : L0);
>> +L1:
>> +  asm goto ("" : : : : L0);
>> +  if (v[3])
>> +    __builtin_abort ();
>> +  return 0;
>> +}
>> diff --git a/gcc/testsuite/gcc.c-torture/execute/pr123625.c 
>> b/gcc/testsuite/gcc.c-torture/execute/pr123625.c
>> new file mode 100644
>> index 00000000000..2b3b29b5e14
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.c-torture/execute/pr123625.c
>> @@ -0,0 +1,48 @@
>> +/* AArch64 wrong code at -O3.  An oversized vector
>> +   (V16DI, 128 bytes, no register mode) is expanded into two distinct
>> +   stack slots that shared the same MEM_EXPR.  The load/store pair fusion
>> +   pass then fused stores belonging to different slots, leaving part of a
>> +   slot uninitialized.  Self-checking: the checksum is target-independent.  
>> */
>> +
>> +#include <stdint.h>
>> +
>> +#define BS_VEC(type, num) type __attribute__((vector_size(num * 
>> sizeof(type))))
>> +uint64_t BS_CHECKSUM, g_284;
>> +struct U0
>> +{
>> +  int16_t f0;
>> +  int64_t f2;
>> +} g_205;
>> +int16_t g_8, func_2_BS_COND_1;
>> +uint8_t g_9[2][1];
>> +volatile struct U0 g_121[1];
>> +int64_t *g_565 = &g_284;
>> +
>> +int
>> +main (void)
>> +{
>> +  BS_VEC (int64_t, 16) BS_VAR_3 = { 1, 8096386231136, 9039249955151 };
>> +  uint64_t LOCAL_CHECKSUM = 0;
>> +  switch (func_2_BS_COND_1)
>> +    {
>> +    case 2: goto BS_LABEL_0;
>> +    case 4: goto BS_LABEL_0;
>> +    }
>> +  for (g_8 = 0; g_8 <= 0; g_8 += 1)
>> +    for (g_205.f2 = 0; g_205.f2 <= 1; g_205.f2 += 1)
>> +      {
>> + if (g_9[g_205.f2][0])
>> + BS_LABEL_0:
>> +   for (;;)
>> +     ;
>> + for (uint32_t BS_TEMP_371 = 0; BS_TEMP_371 < 16; BS_TEMP_371++)
>> +   LOCAL_CHECKSUM ^= BS_VAR_3[BS_TEMP_371] + 9
>> +     + (LOCAL_CHECKSUM << 6) + LOCAL_CHECKSUM
>> +     >> (*g_565 |= g_121[0].f0);
>> + BS_VAR_3 = (BS_VEC (int64_t, 16)){};
>> +      }
>> +  BS_CHECKSUM = LOCAL_CHECKSUM;
>> +  if (BS_CHECKSUM != 0x7237237237237237ULL)
>> +    __builtin_abort ();
>> +  return 0;
>> +}
>> diff --git a/gcc/tree-outof-ssa.cc b/gcc/tree-outof-ssa.cc
>> index f4bc4878bd6..78372d314c0 100644
>> --- a/gcc/tree-outof-ssa.cc
>> +++ b/gcc/tree-outof-ssa.cc
>> @@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
>> #include "rtl.h"
>> #include "tree.h"
>> #include "gimple.h"
>> +#include "gimple-expr.h"
>> #include "cfghooks.h"
>> #include "ssa.h"
>> #include "tree-ssa.h"
>> @@ -1049,6 +1050,75 @@ expand_phi_nodes (struct ssaexpand *sa)
>> }
>> 
>> 
>> +/* Out-of-SSA can leave several partitions sharing one base VAR_DECL, when
>> +   that variable's SSA versions are simultaneously live and so cannot all be
>> +   coalesced.  When such a partition is spilled (it has no register mode,
>> +   e.g. an oversized vector that is BLKmode), set_mem_attributes would give
>> +   every one of those slots that single decl as its MEM_EXPR at offset 0, so
>> +   the distinct slots appear to be the same object and mislead 
>> MEM_EXPR-based
>> +   disambiguation, and the load/store pair-fusion pass then fuses across the
>> +   slots and corrupts one.  Give every partition after the first of such a
>> +   decl its own artificial decl so the slots are distinguished at the 
>> source.
>> +   The new decl carries a DECL_DEBUG_EXPR back to the user variable so debug
>> +   info still attributes the storage to it (cf. create_access_replacement in
>> +   tree-sra.cc).  PARM_DECLs and RESULT_DECLs are left alone, as they 
>> require
>> +   a single partition holding the canonical RTL.  */
>> +
>> +static void
>> +split_overlapping_partition_decls (var_map map)
>> +{
>> +  unsigned n = num_var_partitions (map);
>> +  hash_set<tree> seen;
>> +  auto_vec<tree> new_decl;
>> +  new_decl.safe_grow_cleared (n);
>> +  bool any = false;
>> +
>> +  for (unsigned i = 0; i < n; i++)
>> +    {
>> +      tree repr = partition_to_var (map, i);
>> +      if (!repr)
>> + continue;
>> +      tree var = SSA_NAME_VAR (repr);
>> +      if (!var || !VAR_P (var))
>> + continue;
>> +      /* Only memory partitions (no register mode, e.g. an oversized vector)
>> +  can end up with a misleading shared MEM_EXPR.  */
>> +      if (TYPE_MODE (TREE_TYPE (var)) != BLKmode)
>> + continue;
>> +      /* The first partition of VAR keeps the user decl, the rest are 
>> split.  */
>> +      if (!seen.add (var))
>> + continue;
>> +
>> +      tree nvar = create_tmp_var_raw (TREE_TYPE (var));
>> +      DECL_CONTEXT (nvar) = DECL_CONTEXT (var);
>> +      DECL_SOURCE_LOCATION (nvar) = DECL_SOURCE_LOCATION (var);
>> +      SET_DECL_ALIGN (nvar, DECL_ALIGN (var));
>> +      if (!DECL_ARTIFICIAL (var) && DECL_NAME (var))
>> + {
>> +   SET_DECL_DEBUG_EXPR (nvar, var);
>> +   DECL_HAS_DEBUG_EXPR_P (nvar) = 1;
>> + }
>> +      copy_warning (nvar, var);
>> +      add_local_decl (cfun, nvar);
>> +      new_decl[i] = nvar;
>> +      any = true;
>> +    }
>> +
>> +  if (!any)
>> +    return;
>> +
>> +  unsigned ver;
>> +  tree name;
>> +  FOR_EACH_SSA_NAME (ver, name, cfun)
>> +    {
>> +      if (SSA_NAME_IS_DEFAULT_DEF (name))
>> + continue;
>> +      int p = var_to_partition (map, name);
>> +      if (p != NO_PARTITION && new_decl[p])
>> + SET_SSA_NAME_VAR_OR_IDENTIFIER (name, new_decl[p]);
>> +    }
>> +}
>> +
>> /* Remove the ssa-names in the current function and translate them into 
>> normal
>>    compiler variables.  PERFORM_TER is true if Temporary Expression 
>> Replacement
>>    should also be used.  */
>> @@ -1080,6 +1150,11 @@ remove_ssa_form (bool perform_ter, struct ssaexpand 
>> *sa)
>> dump_replaceable_exprs (dump_file, values);
>>     }
>> 
>> +  /* Distinct partitions of one decl must not share a MEM_EXPR once they are
>> +     spilled to separate stack slots.  Done after TER so reassigning
>> +     SSA_NAME_VAR does not perturb find_replaceable_exprs.  */
>> +  split_overlapping_partition_decls (map);
>> +
>>   rewrite_trees (map);
>> 
>>   sa->map = map;
>> 
> 
> -- 
> Richard Biener <[email protected]>
> SUSE Software Solutions Germany GmbH,
> Frankenstrasse 146, 90461 Nuernberg, Germany;
> GF: Jochen Jaser, Andrew McDonald; (HRB 36809, AG Nuernberg)


Reply via email to