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