Ping

On 8/24/26 17:25, [email protected] wrote:
From: Matthew Malcomson <[email protected]>

N.b. I do not fully understand each of the testcases here, nor have I
manually verified that they cover something materially different to what
is existing in the testsuite.  They were generated by AI during a
bug-hunting session and it seemed like useful work to retain.
Due to this I'm honestly not sure whether this patch should go into GCC
or not -- sending as a separate patch so that decision can be made
separately.

The linux/futex_waitv target changes several protocols beyond a direct
GOMP_barrier call.  Its tree barrier flushes data transitively, its
final barrier can retain secondary threads between teams, and split
work-share barriers now carry a logical thread ID.  Cancellable generations
also have a short, independent bit field.  The tests added with the target
cover direct generation arithmetic and primary task handling, but do not
exercise several of these materially different paths.

Add twelve focused executable tests distilled from an automated AI
barrier verification search.  They use public OpenMP interfaces and
source-level correctness oracles; exhaustive affinity matrices, tracing,
sleeps, and long stress counts from the campaign are deliberately
omitted.

The individual tests cover the following behavior:

* barrier-tree-flush.c checks that ordinary non-atomic data is flushed through
   partial, exact-boundary, and over-boundary radix-four trees with 15, 16,
   and 17 threads.  Consecutive regions also check that data is flushed when a
   retained team is released for its next use.

* barrier-tree-task.c gates logical thread 4 into the child-gather wait
   while its children and the primary remain in user code.  It verifies that
   this secondary coordinator drains the sole task at both an ordinary
   barrier and an implicit final barrier.

* team-held-pause.c leaves a flat-barrier team retained across a one-thread
   region, then checks that omp_pause_resource_all releases and joins the old
   worker pool.

* team-held-pthread-exit.c creates the same retained-team shape with a tree
   barrier inside a foreign initial pthread.  Joining that pthread verifies
   that the libgomp TLS destructor releases the old barrier and frees the
   complete pool.

* artificial-team-pause.c creates a level-zero artificial team with a
   target-nowait construct, waits for its task, and performs a hard pause.  A
   following two-thread region checks that the runtime recreates the thread
   pool rather than dereferencing the null pool left beside the artificial
   team.  This inherited bug remains unfixed, so execution is XFAILed on all
   targets; host fallback makes the test independent of offload hardware.

* barrier-nested-team-id.c makes outer logical thread 1 the primary of an
   active nested team.  It exercises direct, work-share, copyprivate, tasking,
   and final barriers as nested logical thread 0, then checks restoration of
   outer logical ID 1.

* cancel-late-arrival-1.c and cancel-late-arrival-2.c share a controlled
   schedule in which one thread starts a cancellable barrier only after
   cancellation has been committed.  Forced-flat and forced-tree variants
   verify the late-return path, then use a plain barrier to validate local
   generation state and team reuse.

* cancel-next-epoch-wrap.c performs 255 successful cancellable barriers,
   completes the wrap-sensitive 256th use, and immediately cancels the next
   epoch.  A secondary completing the successful epoch must not attribute the
   new BAR_CANCELLED flag to the preceding generation or roll its local state
   back.

* barrier-workshare-task.c queues tasks before a dynamically scheduled
   loop and verifies that task payload data is flushed through
   GOMP_loop_end's split barrier.  A second work-share checks reclamation
   while repeated
   2 -> 4 -> 1 -> 3 team sizes exercise retained-worker reuse.

* barrier-copyprivate-task.c forces selected logical IDs to execute
   GOMP_single_copy_end while their peers use GOMP_single_copy_start.  It
   verifies that copied data and task payload data are flushed, rotates the
   winner for a second generation, and repeats the representative team-size
   transitions.

* barrier-task-reduction.c creates in_reduction tasks in a work-sharing
   loop and verifies the result after the additional barrier in
   GOMP_workshare_task_reduction_unregister, again across representative team
   transitions.

Restrict the futex-waitv implementation tests to the linux_futex_waitv
effective target.  Force the barrier type where a particular flat or tree
protocol is under test, enable OMP_CANCELLATION only for cancellation tests,
and request two active levels for the nested-ID test.  Run the artificial-team
test on all targets with OMP_TARGET_OFFLOAD=DISABLED and an all-target
dg-xfail-run-if selector.  This makes each DejaGNU directive select the
configuration described by its testcase rather than relying on the host's
automatic barrier or offload choice.

Targeted testing on x86_64-pc-linux-gnu produced 22 expected DejaGNU passes
against the in-tree futex_waitv library.  Verbose DejaGNU output confirmed
the effective target, per-test environment settings, and build-tree library
path; the tree-flush test is unsupported in a legacy Linux build whose
config_path omits linux/futex_waitv.  All eleven executables also passed
against a _LIBGOMP_CHECKING_ coverage build.  The five most scheduling- or
lifecycle-sensitive executables passed ten additional checking-runtime runs
each.  A separate targeted run of artificial-team-pause.c produced one
expected compilation pass and one expected execution failure against the
in-tree runtime, confirming its XFAIL selector.  Strict warning compilation
and git diff --check also pass.

libgomp/ChangeLog:

        * testsuite/libgomp.c/artificial-team-pause.c: New test.
        * testsuite/libgomp.c/barrier-copyprivate-task.c: New test.
        * testsuite/libgomp.c/barrier-nested-team-id.c: New test.
        * testsuite/libgomp.c/barrier-task-reduction.c: New test.
        * testsuite/libgomp.c/barrier-tree-flush.c: New test.
        * testsuite/libgomp.c/barrier-tree-task.c: New test.
        * testsuite/libgomp.c/barrier-workshare-task.c: New test.
        * testsuite/libgomp.c/cancel-late-arrival-1.c: New test.
        * testsuite/libgomp.c/cancel-late-arrival-2.c: New test.
        * testsuite/libgomp.c/cancel-late-arrival.h: Header for new tests.
        * testsuite/libgomp.c/cancel-next-epoch-wrap.c: New test.
        * testsuite/libgomp.c/team-held-pause.c: New test.
        * testsuite/libgomp.c/team-held-pthread-exit.c: New test.

Assisted-by: Codex
Signed-off-by: Matthew Malcomson <[email protected]>
---
  ...ized.c => gomp-barrier-type-env-mixed-1.c} |   0
  ...ized.c => gomp-barrier-type-env-mixed-2.c} |   0
  ...flat.c => gomp-barrier-type-env-mixed-3.c} |   0
  ...tree.c => gomp-barrier-type-env-mixed-4.c} |   0
  .../libgomp.c/artificial-team-pause.c         |  45 ++++++
  .../libgomp.c/barrier-copyprivate-task.c      | 152 ++++++++++++++++++
  .../libgomp.c/barrier-nested-team-id.c        |  76 +++++++++
  .../libgomp.c/barrier-task-reduction.c        |  62 +++++++
  .../testsuite/libgomp.c/barrier-tree-flush.c  |  58 +++++++
  .../testsuite/libgomp.c/barrier-tree-task.c   | 133 +++++++++++++++
  .../libgomp.c/barrier-workshare-task.c        | 101 ++++++++++++
  .../libgomp.c/cancel-late-arrival-1.c         |   8 +
  .../libgomp.c/cancel-late-arrival-2.c         |   8 +
  .../testsuite/libgomp.c/cancel-late-arrival.h | 126 +++++++++++++++
  .../libgomp.c/cancel-next-epoch-wrap.c        |  86 ++++++++++
  libgomp/testsuite/libgomp.c/team-held-pause.c |  43 +++++
  .../libgomp.c/team-held-pthread-exit.c        |  57 +++++++
  17 files changed, 955 insertions(+)
  rename 
libgomp/testsuite/libgomp.c-c++-common/{gomp-barrier-type-env-flat-centralized.c 
=> gomp-barrier-type-env-mixed-1.c} (100%)
  rename 
libgomp/testsuite/libgomp.c-c++-common/{gomp-barrier-type-env-tree-centralized.c 
=> gomp-barrier-type-env-mixed-2.c} (100%)
  rename 
libgomp/testsuite/libgomp.c-c++-common/{gomp-barrier-type-env-centralized-flat.c 
=> gomp-barrier-type-env-mixed-3.c} (100%)
  rename 
libgomp/testsuite/libgomp.c-c++-common/{gomp-barrier-type-env-centralized-tree.c 
=> gomp-barrier-type-env-mixed-4.c} (100%)
  create mode 100644 libgomp/testsuite/libgomp.c/artificial-team-pause.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-copyprivate-task.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-nested-team-id.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-task-reduction.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-tree-flush.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-tree-task.c
  create mode 100644 libgomp/testsuite/libgomp.c/barrier-workshare-task.c
  create mode 100644 libgomp/testsuite/libgomp.c/cancel-late-arrival-1.c
  create mode 100644 libgomp/testsuite/libgomp.c/cancel-late-arrival-2.c
  create mode 100644 libgomp/testsuite/libgomp.c/cancel-late-arrival.h
  create mode 100644 libgomp/testsuite/libgomp.c/cancel-next-epoch-wrap.c
  create mode 100644 libgomp/testsuite/libgomp.c/team-held-pause.c
  create mode 100644 libgomp/testsuite/libgomp.c/team-held-pthread-exit.c

diff --git 
a/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-flat-centralized.c
 b/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-1.c
similarity index 100%
rename from 
libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-flat-centralized.c
rename to libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-1.c
diff --git 
a/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-tree-centralized.c
 b/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-2.c
similarity index 100%
rename from 
libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-tree-centralized.c
rename to libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-2.c
diff --git 
a/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-centralized-flat.c
 b/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-3.c
similarity index 100%
rename from 
libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-centralized-flat.c
rename to libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-3.c
diff --git 
a/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-centralized-tree.c
 b/libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-4.c
similarity index 100%
rename from 
libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-centralized-tree.c
rename to libgomp/testsuite/libgomp.c-c++-common/gomp-barrier-type-env-mixed-4.c
diff --git a/libgomp/testsuite/libgomp.c/artificial-team-pause.c 
b/libgomp/testsuite/libgomp.c/artificial-team-pause.c
new file mode 100644
index 00000000000..5fa3be4eb17
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/artificial-team-pause.c
@@ -0,0 +1,45 @@
+/* Verify that hard pause does not leave a level-zero artificial team without
+   a thread pool.  A target-nowait construct outside a parallel region creates
+   such an artificial team.  After its task completes, hard pause may release
+   runtime resources, but the following parallel region must recreate any
+   thread pool it needs rather than dereferencing a stale null pool.
+
+   Force host fallback so this checks the artificial-team lifecycle without
+   requiring an offload device.  This is XFAILed until that lifecycle bug is
+   fixed.  */
+/* { dg-do run } */
+/* { dg-xfail-run-if "artificial-team pause" { *-*-* } } */
+/* { dg-set-target-env-var OMP_TARGET_OFFLOAD "DISABLED" } */
+
+#include <omp.h>
+#include <stdlib.h>
+
+int
+main (void)
+{
+  int target_value = 0;
+  int participants = 0;
+
+  omp_set_dynamic (0);
+
+#pragma omp target nowait map(tofrom : target_value)
+  target_value = 1;
+
+#pragma omp taskwait
+  if (target_value != 1)
+    abort ();
+
+  if (omp_in_parallel ())
+    abort ();
+
+  if (omp_pause_resource_all (omp_pause_hard) != 0)
+    abort ();
+
+#pragma omp parallel num_threads(2) reduction(+ : participants)
+  participants += 1;
+
+  if (participants != 2)
+    abort ();
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-copyprivate-task.c 
b/libgomp/testsuite/libgomp.c/barrier-copyprivate-task.c
new file mode 100644
index 00000000000..c90de7e5c31
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-copyprivate-task.c
@@ -0,0 +1,152 @@
+/* Verify task handling in the barrier used by single-copyprivate.  A selected
+   logical thread queues tasks and is forced to win the first single region;
+   every other thread therefore waits in GOMP_single_copy_start while the
+   winner completes through GOMP_single_copy_end.  The copied value, task
+   payload, and task count must all have been flushed when that barrier
+   returns.
+
+   A second copyprivate region rotates the winner to exercise reclamation of
+   the first work-share.  Repeated 2 -> 4 -> 1 -> 3 transitions check the
+   logical-ID argument while retained tree-barrier workers are reused.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <sched.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum { tasks = 4, rounds = 8 };
+
+struct state
+{
+  int queued;
+  int gate_first;
+  int gate_second;
+  int completed;
+  int first_blocks;
+  int second_blocks;
+  int after_first;
+  int after_second;
+  uint32_t payload[tasks];
+};
+
+static int
+read_atomic (int *value)
+{
+  int result;
+#pragma omp atomic read
+  result = *value;
+  return result;
+}
+
+static void
+wait_for (int *value)
+{
+  while (!read_atomic (value))
+    sched_yield ();
+}
+
+static uint32_t
+task_payload (int task, int epoch)
+{
+  uint32_t value = (uint32_t) (epoch * tasks + task + 1);
+  for (unsigned int i = 0; i < 128; ++i)
+    value = value * 1103515245u + 12345u + i;
+  return value;
+}
+
+static void
+run_region (int nthreads, int epoch)
+{
+  struct state state;
+  int first_winner = epoch % nthreads;
+  int second_winner = (first_winner + 1) % nthreads;
+  long long first_value = 0x12340000LL + epoch;
+  long long second_value = 0x56780000LL + epoch;
+  memset (&state, 0, sizeof (state));
+
+#pragma omp parallel num_threads(nthreads)                              \
+  shared(state, nthreads, epoch, first_winner, second_winner,            \
+        first_value, second_value)
+  {
+    int tid = omp_get_thread_num ();
+    long long copied = -1;
+
+    if (tid == first_winner)
+      {
+       for (int task = 0; task < tasks; ++task)
+         {
+#pragma omp task firstprivate(task) shared(state, epoch)
+           {
+             state.payload[task] = task_payload (task, epoch);
+#pragma omp atomic update
+             state.completed++;
+           }
+         }
+#pragma omp atomic write
+       state.queued = 1;
+      }
+    else
+      wait_for (&state.queued);
+
+    if (tid != first_winner)
+      wait_for (&state.gate_first);
+#pragma omp single copyprivate(copied)
+    {
+      if (tid != first_winner)
+       abort ();
+      copied = first_value;
+#pragma omp atomic update
+      state.first_blocks++;
+#pragma omp atomic write
+      state.gate_first = 1;
+    }
+
+    if (copied != first_value || state.completed != tasks)
+      abort ();
+    for (int task = 0; task < tasks; ++task)
+      if (state.payload[task] != task_payload (task, epoch))
+       abort ();
+#pragma omp atomic update
+    state.after_first++;
+
+    copied = -1;
+    if (tid != second_winner)
+      wait_for (&state.gate_second);
+#pragma omp single copyprivate(copied)
+    {
+      if (tid != second_winner)
+       abort ();
+      copied = second_value;
+#pragma omp atomic update
+      state.second_blocks++;
+#pragma omp atomic write
+      state.gate_second = 1;
+    }
+
+    if (copied != second_value)
+      abort ();
+#pragma omp atomic update
+    state.after_second++;
+  }
+
+  if (!state.queued || state.completed != tasks || state.first_blocks != 1
+      || state.second_blocks != 1 || state.after_first != nthreads
+      || state.after_second != nthreads)
+    abort ();
+}
+
+int
+main (void)
+{
+  static const int sizes[] = { 2, 4, 1, 3 };
+  int epoch = 0;
+
+  omp_set_dynamic (0);
+  for (int round = 0; round < rounds; ++round)
+    for (int i = 0; i < 4; ++i)
+      run_region (sizes[i], ++epoch);
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-nested-team-id.c 
b/libgomp/testsuite/libgomp.c/barrier-nested-team-id.c
new file mode 100644
index 00000000000..2e6d8a5544e
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-nested-team-id.c
@@ -0,0 +1,76 @@
+/* Verify logical barrier IDs while an outer worker is primary of a nested
+   team.  Outer logical thread 1 becomes logical thread 0 in the nested team,
+   exercises direct, work-share, copyprivate, tasking, and final barriers,
+   then must regain outer ID 1 before using the outer barrier again.
+
+   The test covers the ID arguments added to the barrier interfaces and the
+   restoration of per-thread team state across active nesting.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+/* { dg-set-target-env-var OMP_MAX_ACTIVE_LEVELS "2" } */
+
+#include <omp.h>
+#include <stdlib.h>
+
+enum { rounds = 24 };
+
+int
+main (void)
+{
+  omp_set_dynamic (0);
+  omp_set_max_active_levels (2);
+
+  for (int round = 0; round < rounds; ++round)
+    {
+      int loop_total = 0;
+      int task_total = 0;
+
+#pragma omp parallel num_threads(3) shared(loop_total, task_total, round)
+      {
+       int outer_id = omp_get_thread_num ();
+
+       if (outer_id == 1)
+         {
+           int inner_threads = 2 + round % 4;
+
+#pragma omp parallel num_threads(inner_threads)                         \
+  shared(loop_total, task_total, inner_threads)
+           {
+             int copied = -1;
+
+             if (omp_get_num_threads () != inner_threads)
+               abort ();
+
+#pragma omp barrier
+#pragma omp for reduction(+:loop_total)
+             for (int i = 0; i < inner_threads; ++i)
+               loop_total++;
+
+#pragma omp single copyprivate(copied)
+             copied = 0x1234;
+             if (copied != 0x1234)
+               abort ();
+
+#pragma omp single nowait
+#pragma omp task shared(task_total)
+             {
+#pragma omp atomic update
+               task_total++;
+             }
+           }
+
+           if (omp_get_thread_num () != outer_id)
+             abort ();
+         }
+
+#pragma omp barrier
+       if (omp_get_thread_num () != outer_id)
+         abort ();
+      }
+
+      if (loop_total != 2 + round % 4 || task_total != 1)
+       abort ();
+    }
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-task-reduction.c 
b/libgomp/testsuite/libgomp.c/barrier-task-reduction.c
new file mode 100644
index 00000000000..c3bd56a4e2c
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-task-reduction.c
@@ -0,0 +1,62 @@
+/* Verify the additional team barrier used when a work-share task reduction
+   is unregistered.  Each loop iteration creates an in_reduction task, so the
+   loop-end scheduling point must finish every task and reduction before
+   GOMP_workshare_task_reduction_unregister synchronizes all logical IDs.
+
+   Repeated 2 -> 4 -> 1 -> 3 transitions exercise task-reduction allocation,
+   unregister, and retained-team reuse with the tree barrier.  This is a
+   focused complement to broad task-reduction language tests.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <stdlib.h>
+
+enum { rounds = 8 };
+
+static long long reduction_sum;
+static int completed;
+
+static void
+run_region (int nthreads, int epoch)
+{
+  long long expected = (long long) nthreads * (nthreads + 1) / 2 + epoch;
+  reduction_sum = 0;
+  completed = 0;
+
+#pragma omp parallel num_threads(nthreads) shared(nthreads, epoch, expected)
+  {
+#pragma omp for schedule(static, 1) reduction(task, + : reduction_sum)
+    for (int i = 0; i < nthreads; ++i)
+      {
+       long long contribution = i + 1;
+       if (i == 0)
+         contribution += epoch;
+#pragma omp task firstprivate(contribution) in_reduction(+ : reduction_sum)
+       {
+         reduction_sum += contribution;
+#pragma omp atomic update
+         completed++;
+       }
+      }
+
+    if (reduction_sum != expected || completed != nthreads)
+      abort ();
+  }
+
+  if (reduction_sum != expected || completed != nthreads)
+    abort ();
+}
+
+int
+main (void)
+{
+  static const int sizes[] = { 2, 4, 1, 3 };
+  int epoch = 0;
+
+  omp_set_dynamic (0);
+  for (int round = 0; round < rounds; ++round)
+    for (int i = 0; i < 4; ++i)
+      run_region (sizes[i], ++epoch);
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-tree-flush.c 
b/libgomp/testsuite/libgomp.c/barrier-tree-flush.c
new file mode 100644
index 00000000000..ba9685cbf45
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-tree-flush.c
@@ -0,0 +1,58 @@
+/* Verify that data is flushed through the radix-four tree barrier and through
+   release of a retained team.  Team sizes 15, 16, and 17 exercise a partial
+   second tree level, its exact boundary, and the first node beyond that
+   boundary.
+
+   Each thread writes ordinary, non-atomic data before an explicit barrier and
+   every thread verifies that the complete payload has been flushed
+   afterwards.  Consecutive parallel regions also verify that the primary
+   thread's writes are flushed when workers held at the preceding final
+   barrier are released for their next team.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <stdlib.h>
+
+enum { max_threads = 17, rounds = 24 };
+
+static int slots[max_threads];
+static int next_epoch;
+
+static void
+run_size (int nthreads, int epoch)
+{
+  next_epoch = epoch;
+  for (int i = 0; i < nthreads; ++i)
+    slots[i] = 0;
+
+#pragma omp parallel num_threads(nthreads) shared(nthreads, epoch)
+  {
+    int tid = omp_get_thread_num ();
+
+    if (next_epoch != epoch)
+      abort ();
+
+    slots[tid] = epoch;
+#pragma omp barrier
+
+    for (int i = 0; i < nthreads; ++i)
+      if (slots[i] != epoch)
+       abort ();
+  }
+
+  for (int i = 0; i < nthreads; ++i)
+    if (slots[i] != epoch)
+      abort ();
+}
+
+int
+main (void)
+{
+  static const int sizes[] = { 15, 16, 17, 16, 15, 17 };
+
+  omp_set_dynamic (0);
+  for (int round = 0; round < rounds; ++round)
+    run_size (sizes[round % 6], round + 1);
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-tree-task.c 
b/libgomp/testsuite/libgomp.c/barrier-tree-task.c
new file mode 100644
index 00000000000..bcc6a9efbe3
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-tree-task.c
@@ -0,0 +1,133 @@
+/* Verify that a secondary tree coordinator drains tasks while gathering its
+   children at both ordinary and final barriers.  In libgomp's radix-four
+   topology logical thread 4 gathers threads 5 through 7.  Those children and
+   thread 0 remain in user code until the queued task reports that thread 4
+   ran it, making thread 4's child-gather wait the only runtime path that can
+   execute the task.
+
+   The ordinary case checks completion and release of every participant.  The
+   final case additionally exercises setting BAR_HOLDING_SECONDARIES after
+   child gathering and task draining have both completed.  This is an
+   implementation regression test and is deliberately restricted to the new
+   tree backend.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <sched.h>
+#include <stdlib.h>
+
+enum { rounds = 32 };
+
+static void
+wait_for (int *value)
+{
+  int observed;
+  do
+    {
+#pragma omp atomic read
+      observed = *value;
+      if (!observed)
+       sched_yield ();
+    }
+  while (!observed);
+}
+
+static void
+run_ordinary (void)
+{
+  int release_coordinator = 0;
+  int release_others = 0;
+  int task_ran = 0;
+  int after_barrier = 0;
+
+#pragma omp parallel num_threads(8)                                      \
+  shared(release_coordinator, release_others, task_ran, after_barrier)
+  {
+    int tid = omp_get_thread_num ();
+
+    if (tid == 0)
+      {
+#pragma omp task shared(task_ran)
+       {
+         if (omp_get_thread_num () != 4)
+           abort ();
+#pragma omp atomic write
+         task_ran = 1;
+       }
+
+#pragma omp atomic write
+       release_coordinator = 1;
+       wait_for (&task_ran);
+#pragma omp atomic write
+       release_others = 1;
+      }
+    else if (tid == 4)
+      wait_for (&release_coordinator);
+    else
+      wait_for (&release_others);
+
+#pragma omp barrier
+
+    int observed;
+#pragma omp atomic read
+    observed = task_ran;
+    if (!observed)
+      abort ();
+#pragma omp atomic update
+    after_barrier++;
+  }
+
+  if (task_ran != 1 || after_barrier != 8)
+    abort ();
+}
+
+static void
+run_final (void)
+{
+  int release_coordinator = 0;
+  int release_others = 0;
+  int task_ran = 0;
+
+#pragma omp parallel num_threads(8) \
+  shared(release_coordinator, release_others, task_ran)
+  {
+    int tid = omp_get_thread_num ();
+
+    if (tid == 0)
+      {
+#pragma omp task shared(task_ran)
+       {
+         if (omp_get_thread_num () != 4)
+           abort ();
+#pragma omp atomic write
+         task_ran = 1;
+       }
+
+#pragma omp atomic write
+       release_coordinator = 1;
+       wait_for (&task_ran);
+#pragma omp atomic write
+       release_others = 1;
+      }
+    else if (tid == 4)
+      wait_for (&release_coordinator);
+    else
+      wait_for (&release_others);
+  }
+
+  if (task_ran != 1)
+    abort ();
+}
+
+int
+main (void)
+{
+  omp_set_dynamic (0);
+  for (int round = 0; round < rounds; ++round)
+    {
+      run_ordinary ();
+      run_final ();
+    }
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/barrier-workshare-task.c 
b/libgomp/testsuite/libgomp.c/barrier-workshare-task.c
new file mode 100644
index 00000000000..ee5b09560ad
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/barrier-workshare-task.c
@@ -0,0 +1,101 @@
+/* Verify the task-aware split barrier used at the end of a work-sharing loop.
+   Tasks are queued immediately before a dynamically scheduled loop.  The
+   loop's implicit GOMP_loop_end barrier must drain all team tasks, flush their
+   payloads, and release every logical ID before a second work-share is
+   allocated and reclaimed.
+
+   Repeated 2 -> 4 -> 1 -> 3 team-size transitions cover retained-worker
+   reuse without the campaign's exhaustive affinity and size matrix.  This
+   test selects the flat implementation; tree split-barrier callers are
+   covered by the copyprivate and task-reduction tests.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "flat" } */
+
+#include <omp.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum { tasks = 4, rounds = 8 };
+
+struct state
+{
+  int completed;
+  int first_iterations;
+  int second_iterations;
+  int after_first;
+  uint32_t payload[tasks];
+};
+
+static uint32_t
+task_payload (int task, int epoch)
+{
+  uint32_t value = (uint32_t) (epoch * tasks + task + 1);
+  for (unsigned int i = 0; i < 128; ++i)
+    value = value * 1664525u + 1013904223u + i;
+  return value;
+}
+
+static void
+run_region (int nthreads, int epoch)
+{
+  struct state state;
+  memset (&state, 0, sizeof (state));
+
+#pragma omp parallel num_threads(nthreads) shared(state, nthreads, epoch)
+  {
+#pragma omp single nowait
+    for (int task = 0; task < tasks; ++task)
+      {
+#pragma omp task firstprivate(task) shared(state, epoch)
+       {
+         state.payload[task] = task_payload (task, epoch);
+#pragma omp atomic update
+         state.completed++;
+       }
+      }
+
+    /* A runtime schedule uses gomp_work_share_end's split start/end barrier
+       rather than lowering directly to GOMP_barrier.  */
+#pragma omp for schedule(dynamic, 1)
+    for (int i = 0; i < nthreads * 3 + 1; ++i)
+      {
+#pragma omp atomic update
+       state.first_iterations++;
+      }
+
+    if (state.completed != tasks)
+      abort ();
+    for (int task = 0; task < tasks; ++task)
+      if (state.payload[task] != task_payload (task, epoch))
+       abort ();
+#pragma omp atomic update
+    state.after_first++;
+
+#pragma omp for schedule(guided, 1)
+    for (int i = 0; i < nthreads * 2 + 3; ++i)
+      {
+#pragma omp atomic update
+       state.second_iterations++;
+      }
+  }
+
+  if (state.completed != tasks
+      || state.first_iterations != nthreads * 3 + 1
+      || state.second_iterations != nthreads * 2 + 3
+      || state.after_first != nthreads)
+    abort ();
+}
+
+int
+main (void)
+{
+  static const int sizes[] = { 2, 4, 1, 3 };
+  int epoch = 0;
+
+  omp_set_dynamic (0);
+  for (int round = 0; round < rounds; ++round)
+    for (int i = 0; i < 4; ++i)
+      run_region (sizes[i], ++epoch);
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/cancel-late-arrival-1.c 
b/libgomp/testsuite/libgomp.c/cancel-late-arrival-1.c
new file mode 100644
index 00000000000..f61c4dc6195
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/cancel-late-arrival-1.c
@@ -0,0 +1,8 @@
+/* Verify late arrival at an already-cancelled flat barrier.  The shared
+   implementation also follows each cancellation with a plain barrier so a
+   checking runtime validates the rolled-forward local generations.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var OMP_CANCELLATION "true" } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "flat" } */
+
+#include "cancel-late-arrival.h"
diff --git a/libgomp/testsuite/libgomp.c/cancel-late-arrival-2.c 
b/libgomp/testsuite/libgomp.c/cancel-late-arrival-2.c
new file mode 100644
index 00000000000..b41bdda938d
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/cancel-late-arrival-2.c
@@ -0,0 +1,8 @@
+/* Verify late arrival at an already-cancelled tree barrier.  This runs the
+   same controlled cancellation and checking-barrier sequence as the flat
+   variant while selecting the tree arrival protocol.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var OMP_CANCELLATION "true" } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include "cancel-late-arrival.h"
diff --git a/libgomp/testsuite/libgomp.c/cancel-late-arrival.h 
b/libgomp/testsuite/libgomp.c/cancel-late-arrival.h
new file mode 100644
index 00000000000..3ee71391ceb
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/cancel-late-arrival.h
@@ -0,0 +1,126 @@
+/* Common implementation for the forced-flat and forced-tree late-arrival
+   cancellation tests.  Thread 0 is released toward the barrier before
+   cancellation is requested, while thread 2 waits until thread 1 has
+   committed cancellation before starting the same barrier.  The late waiter
+   must return cancelled without recording an arrival for the cancelled
+   generation.  A following plain barrier makes a checking runtime scan each
+   thread-local generation and also verifies that team reuse remains live.  */
+
+#include <omp.h>
+#include <sched.h>
+#include <stdlib.h>
+
+enum { rounds = 64 };
+
+static int early_ready;
+static int late_ready;
+static int cancel_committed;
+static int escaped;
+
+static int
+read_atomic (int *value)
+{
+  int result;
+#pragma omp atomic read
+  result = *value;
+  return result;
+}
+
+static void
+wait_for (int *value)
+{
+  while (!read_atomic (value))
+    sched_yield ();
+}
+
+static void
+mark_committed_cancel (int *active)
+{
+  if (*active)
+    {
+#pragma omp atomic write
+      cancel_committed = 1;
+    }
+}
+
+static void
+run_cancelled_region (void)
+{
+  early_ready = 0;
+  late_ready = 0;
+  cancel_committed = 0;
+  escaped = 0;
+
+#pragma omp parallel num_threads(3)                                     \
+  shared(early_ready, late_ready, cancel_committed, escaped)
+  {
+    int tid = omp_get_thread_num ();
+
+    if (tid == 1)
+      {
+       int mark __attribute__ ((cleanup (mark_committed_cancel))) = 1;
+       wait_for (&early_ready);
+       wait_for (&late_ready);
+
+       /* Cleanup runs only after GOMP_cancel has committed BAR_CANCELLED
+          and transferred control out of this branch.  */
+#pragma omp cancel parallel
+       abort ();
+      }
+    else
+      {
+       if (tid == 0)
+         {
+#pragma omp atomic write
+           early_ready = 1;
+         }
+       else
+         {
+#pragma omp atomic write
+           late_ready = 1;
+           wait_for (&cancel_committed);
+         }
+
+#pragma omp barrier
+#pragma omp atomic update
+       escaped++;
+      }
+  }
+
+  if (!cancel_committed || escaped != 0)
+    abort ();
+}
+
+static void
+run_plain_control (void)
+{
+  int arrived = 0;
+
+#pragma omp parallel num_threads(3) shared(arrived)
+  {
+#pragma omp atomic update
+    arrived++;
+#pragma omp barrier
+    if (read_atomic (&arrived) != 3)
+      abort ();
+  }
+
+  if (arrived != 3)
+    abort ();
+}
+
+int
+main (void)
+{
+  if (!omp_get_cancellation ())
+    abort ();
+  omp_set_dynamic (0);
+
+  for (int round = 0; round < rounds; ++round)
+    {
+      run_cancelled_region ();
+      run_plain_control ();
+    }
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/cancel-next-epoch-wrap.c 
b/libgomp/testsuite/libgomp.c/cancel-next-epoch-wrap.c
new file mode 100644
index 00000000000..05435f025f5
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/cancel-next-epoch-wrap.c
@@ -0,0 +1,86 @@
+/* Verify cancellation attribution across cancellable-generation wrap.  After
+   255 successful uses, secondary threads arrive at use 256 before the
+   primary.  The primary completes that barrier, wrapping the eight-bit
+   cancellable generation to zero, and immediately cancels the next epoch.
+
+   A secondary still observing completion of use 256 must not treat the next
+   epoch's BAR_CANCELLED flag as cancellation of its successful barrier or
+   roll back its local generation.  Every secondary must pass the first
+   barrier and leave only at the following cancellation point.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var OMP_CANCELLATION "true" } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <sched.h>
+#include <stdlib.h>
+
+enum { threads = 2, prewrap_rounds = 255 };
+
+static int target_arrived;
+static int after_first;
+static int escaped_second;
+static int inactive_cancel;
+
+int
+main (void)
+{
+  if (!omp_get_cancellation ())
+    abort ();
+  omp_set_dynamic (0);
+
+#pragma omp parallel num_threads(threads)                                \
+  shared(target_arrived, after_first, escaped_second)
+  {
+    int tid = omp_get_thread_num ();
+
+    for (int round = 0; round < prewrap_rounds; ++round)
+      {
+#pragma omp cancel parallel if(inactive_cancel)
+#pragma omp barrier
+      }
+
+    if (tid != 0)
+      {
+#pragma omp atomic update
+       target_arrived++;
+      }
+    else
+      {
+       int seen;
+       do
+         {
+#pragma omp atomic read
+           seen = target_arrived;
+           if (seen != threads - 1)
+             sched_yield ();
+         }
+       while (seen != threads - 1);
+      }
+
+    /* This successful use wraps the cancellable generation to zero.  */
+#pragma omp barrier
+
+    if (tid == 0)
+      {
+       /* This cancellation belongs to the following barrier epoch.  */
+#pragma omp cancel parallel
+       abort ();
+      }
+    else
+      {
+#pragma omp atomic update
+       after_first++;
+
+#pragma omp barrier
+#pragma omp atomic update
+       escaped_second++;
+      }
+  }
+
+  if (target_arrived != threads - 1 || after_first != threads - 1
+      || escaped_second != 0)
+    abort ();
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/team-held-pause.c 
b/libgomp/testsuite/libgomp.c/team-held-pause.c
new file mode 100644
index 00000000000..903c4b0f2bb
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/team-held-pause.c
@@ -0,0 +1,43 @@
+/* Verify that a hard pause releases workers retained at a previous final
+   barrier even when a one-thread parallel region has since replaced the
+   current team.  The serial region does not need the retained workers, so
+   omp_pause_resource_all must find the older prev_barrier, release it, and
+   join the complete worker pool without hanging or using freed team state.
+   This variant exercises a retained flat barrier.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "flat" } */
+
+#include <omp.h>
+#include <stdlib.h>
+
+enum { threads = 4, rounds = 16 };
+
+int
+main (void)
+{
+  omp_set_dynamic (0);
+
+  for (int round = 0; round < rounds; ++round)
+    {
+      int participants = 0;
+
+#pragma omp parallel num_threads(threads) shared(participants)
+      {
+#pragma omp atomic update
+       participants++;
+      }
+      if (participants != threads)
+       abort ();
+
+#pragma omp parallel num_threads(1)
+      {
+       if (omp_get_num_threads () != 1 || omp_get_thread_num () != 0)
+         abort ();
+      }
+
+      if (omp_pause_resource_all (omp_pause_hard) != 0)
+       abort ();
+    }
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c/team-held-pthread-exit.c 
b/libgomp/testsuite/libgomp.c/team-held-pthread-exit.c
new file mode 100644
index 00000000000..a3aae088a71
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/team-held-pthread-exit.c
@@ -0,0 +1,57 @@
+/* Verify cleanup of a retained team when a foreign initial thread exits.
+   The pthread first leaves workers held at a multi-threaded final barrier and
+   then runs a one-thread parallel region, which leaves that older barrier in
+   the thread pool.  The pthread TLS destructor must release the retained tree
+   barrier, join all workers, and free the pool before pthread_join returns.
+   Repetition also checks that cleanup does not leak state into a later
+   foreign initial thread.  */
+/* { dg-do run { target linux_futex_waitv } } */
+/* { dg-set-target-env-var GOMP_BARRIER_TYPE "tree" } */
+
+#include <omp.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+enum { threads = 4, rounds = 12 };
+
+static void *
+run_regions (void *argument)
+{
+  int participants = 0;
+
+  omp_set_dynamic (0);
+#pragma omp parallel num_threads(threads) shared(participants)
+  {
+#pragma omp atomic update
+    participants++;
+  }
+  if (participants != threads)
+    abort ();
+
+#pragma omp parallel num_threads(1)
+  {
+    if (omp_get_num_threads () != 1 || omp_get_thread_num () != 0)
+      abort ();
+  }
+
+  return argument;
+}
+
+int
+main (void)
+{
+  for (intptr_t round = 1; round <= rounds; ++round)
+    {
+      pthread_t driver;
+      void *argument = (void *) round;
+      void *result;
+
+      if (pthread_create (&driver, NULL, run_regions, argument) != 0)
+       abort ();
+      if (pthread_join (driver, &result) != 0 || result != argument)
+       abort ();
+    }
+
+  return 0;
+}

Reply via email to