Files
Yuta Mukai ce68f28703 [OpenMP][libomp] Fix dist barrier arrival synchronization (#213845)
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)
2026-09-05 09:12:01 +02:00
..