https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126595

            Bug ID: 126595
           Summary: Wrong code with loop distribution on SVE and
                    compute_alias_check_pairs
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: aarch64-sve, wrong-code
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: ktkachov at gcc dot gnu.org
  Target Milestone: ---
            Target: aarch64

/* tree-loop-distribution.cc:2739 -- compute_alias_check_pairs computes the
   runtime-alias segment length of a CONDITIONALLY executed data reference
   from number_of_latch_executions instead of the trip count, so seg_len is
   short by exactly one DR_STEP.  The versioning condition then declares
   "no alias" for two partitions that really do overlap in the last iteration
   and the distributed loops reorder the conflicting accesses.

   NOTE this is a loop-distribution defect, not a vectoriser defect.  The
   vectoriser is only the *committer*: tree-loop-distribution.cc sets
   "cancelable_p = flag_tree_loop_vectorize" and wraps the (wrong) alias
   check in IFN_LOOP_DIST_ALIAS, which the vectoriser folds to true when the
   distributed parallel partition vectorises (SVE masked store here).  With
   -fno-tree-vectorize the check is committed unconditionally, so the test
   also aborts with the vectoriser off -- the bug is strictly worse there.
   The correct control is -fno-tree-loop-distribution.

   ldist dump (-fdump-tree-ldist-details) shows the off-by-one-step:
     Version loop <1> with runtime alias check
     merged alias checks:
       reference:      *_8 vs. *_10          <- a[i]=1  vs  b[i]
       segment length: 120 vs. 124
   120 == 4 * (niters - 1) == 4 * 30, but a[i] runs at i = 0..31, so the
   guarded range must be 4 * 31 == 124 long.  The missing DR_STEP is exactly
   the a[N-1] element that aliases b[0]. */

int *a, *b, *c;

void __attribute__((noinline))
f (int n)
{
  for (int i = 0; i < n; i++)
    {
      if (c[i] > 0)             /* conditional: its bb does not dominate the
                                   block holding the do-while exit test, so
                                   latch_dominated_by_data_ref is false and the
                                   `else' arm at tree-loop-distribution.cc:2739
                                   gives seg_len = DR_STEP * (niters - 1) */
        a[i] = 1;
      b[i + 1] = b[i];          /* +1 self dependence => PTYPE_SEQUENTIAL, so
                                   break_alias_scc_partitions breaks the SCC
                                   with a runtime alias check */
    }
}

#define N 32
int arr[2 * N];
int cc[N];

int main (void)
{
  for (int i = 0; i < 2 * N; i++)
    arr[i] = 0;
  for (int i = 0; i < N; i++)
    cc[i] = 1;

  a = arr;
  b = arr + N - 1;              /* &b[0] == &a[N-1]: exactly one element of
                                   overlap, i.e. only a[]'s LAST iteration */
  c = cc;

  f (N);

  /* Scalar semantics: b[0] is read at iteration 0, before a[N-1] (== b[0])
     is written at iteration N-1, so arr[N..2N-1] must stay 0.  With the
     bogus "no alias" check the a-partition runs first and the copy chain
     propagates 1 through arr[N..2N-1].  */
  for (int i = N; i < 2 * N; i++)
    if (arr[i] != 0)
      __builtin_abort ();
  for (int i = 0; i < N; i++)
    if (arr[i] != 1)
      __builtin_abort ();
  return 0;
}

Aborts on aarch64 at -O3 -march=armv8.2-a+sve as far back as SVE is supported

Reply via email to