Make distributedBarrier::stillNeed atomic and use release/acquire
ordering for distributed barrier gather arrival flags.
The old volatile stillNeed flag did not synchronize an arriving thread's
pre-barrier writes with the thread that observed its arrival. On weakly
ordered architectures, a group leader could observe stillNeed == 0
before the arriving thread's pre-barrier writes were visible. This
allowed another thread to pass the barrier and read stale data written
before the barrier.
Use release stores when publishing stillNeed == 0. Keep the spin loops
on relaxed loads, then perform one acquire fence after all expected zero
values have been observed. This connects the arriving threads'
pre-barrier writes to the observer through the standard release/acquire
happens-before chain, without using acquire loads on every poll.
The same pattern is used when a group leader publishes its own stillNeed
== 0 after the in-group gather completes. The existing go-flag
release/acquire path then carries the ordering to threads leaving the
barrier.
Initialization and next-iteration reset stores remain relaxed because
they do not publish pre-barrier data.
On x86, this does not change the generated instruction sequence for the
affected stillNeed polling/arrival code, since TSO already provides the
needed ordering for ordinary loads and stores.
Reproducer:
I used the following stress test to reproduce the distributed-barrier
visibility issue on AArch64 (Grace).
`test.c`:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <omp.h>
int main() {
int nt;
#pragma omp parallel
#pragma omp master
nt = omp_get_num_threads();
int n = 1024;
int *p = aligned_alloc(64, nt * n * sizeof(int));
memset(p, 0, nt * n * sizeof(int));
#pragma omp parallel
{
int t = omp_get_thread_num();
int *pt = p + t * n;
int *pt2 = p + ((t + 1) % nt) * n;
for (int it = 1; it <= 100000; it++) {
for (int i = 0; i < n; i++)
pt[i] = it;
#pragma omp barrier
for (int i = 0; i < n; i++) {
if (pt2[i] != it) {
printf("thread #%d observes pt2[%d] != %d\n", t, i, it);
abort();
}
}
#pragma omp barrier
}
}
printf("ok\n");
return 0;
}
```
`noise.c`:
```c
// background memory-bandwidth noise
#include <stdlib.h>
#include <omp.h>
int main() {
long N = 1024L*1024;
#pragma omp parallel
{
long *b = malloc(sizeof(long)*N);
for (long j=0; j<1000; j++)
for (long i = 0; i < N; i += 8)
b[i] += 1;
}
return 0;
}
```
Build and run:
```bash
clang test.c -O -fopenmp -o test
clang noise.c -O -fopenmp -o noise
./noise &
KMP_PLAIN_BARRIER_PATTERN=dist,dist \
KMP_FORKJOIN_BARRIER_PATTERN=dist,dist \
KMP_REDUCTION_BARRIER_PATTERN=dist,dist \
OMP_NUM_THREADS=10 \
./test
```
With the old runtime, this can fail as follows:
```text
thread #5 observes pt2[112] != 343
Aborted (core dumped)
```
The same test passes with the patched runtime.
(cherry picked from commit 0fa6182e280d36dc73472f9a79dcb1b82d548893)