A vCPU can get preempted at any time on a multi-tenant and oversubscribed host.
Phantom Tracker 1. monitors vCPU preemptions for target VMs using eBPF,
2. computes and exposes phantom_average, a time-weighted and paravirtualized
scheduling metric representing the number of guest vCPUs that are unable to
make progress because of scheduling decisions on the host in a given scheduler
tick, and 3. exposes this metric to guest libgomp through eBPF-compatible
paravirtualized shared memory exposed to target VMs using a QEMU ivshmem PCI
device.

This patch introduces phantom_average to libgomp barrier wait policy implemented
with the help of GOMP_WAIT_POLICY, a new libgomp environment variable, which
when set to "pvsched" uses phantom_average to dynamically decide whether to spin
or block on a per-barrier and per-thread basis at runtime.

With the default libgomp wait policy, a thread spins for a fixed number of
iterations before blocking. This fixed spincount is resolved at the beginning
of the application and guest libgomp currently does not take the host's runtime
scheduling decisions into account, which might impact the state (phantom vs
viable) of target VM's vCPUs.

The pv-barrier-sync policy changes this behavior by exploiting as many spin
iterations as possible as long as phantoms are not detected (i.e.
phantom_average == 0). At each barrier, a waiting thread continuously checks
the latest value of phantom_average reported by the Phantom Tracker by a simple
shared-memory read operation. As long as phantom_average is zero, the thread
continues spinning. When phantom_average becomes positive, indicating that the
vCPUs of the target VM are experiencing host-side scheduling contention, the
waiting thread blocks immediately. This approach allows guest OpenMP 
applications
to exploit spinning during non-contention phases on the host while safely 
falling
back to blocking as soon as contention is detected.

Thus, rather than using a fixed spin threshold, pv-barrier-sync adapts the
waiting behavior to the current scheduling state of the VM.

Support for pv-barrier-sync is implemented using Userspace Statically Defined
Tracing (USDT) probes [2]. The patch adds USDT probes into existing libgomp
functions do_wait and gomp_thread_start, and introduces a new function
read_latest_phantom_average with its own USDT probe. These probes give Phantom
Tracker a stable, minimal interface for detecting OpenMP threads and when they
start waiting at barriers.

libgomp/ChangeLog:

        * config/linux/wait.h (pv_spin): New function, split out of
        do_spin.  On each spin iteration, read phantom_average via
        read_latest_phantom_average and return and block if positive.
        (do_spin): Untouched spin loop logic.
        (do_wait): Add USDT probe.  Use pv_spin when
        gomp_pv_wait_policy == 2, otherwise use do_spin
        * env.c (parse_pv_wait_policy): New function.  Parse
        GOMP_WAIT_POLICY (active/passive/pvsched);
        (gomp_pvsched_init): New function.  No longer validates the host
        page size against a hardcoded PVSCHED_IVSHMEM_PAGE_SIZE constant;
        instead maps the ivshmem header from /dev/guest_ivshmem first to
        read the H2G page offset and size at runtime, then maps the H2G
        page itself at that offset; fall back to default OMP_WAIT_POLICY
        with warning on failure.
        (initialize_env): Call parse_pv_wait_policy and gomp_pvsched_init.
        Prioritize OMP_WAIT_POLICY and handle conflicts with fallback message.
        (omp_display_env): Display GOMP_WAIT_POLICY value.
        * libgomp.h (gomp_pv_wait_policy): Declare.
        * pvsched.h: New file.  Declare guest_ivshmem_fd, guest_ivshmem_h2g,
        and read_latest_phantom_average, with GPLv3 header and Phantom
        Tracker reference.
        * team.c: Include pvsched.h.  Add gomp_thread_start USDT probe with
        explanatory comment.

[1] https://lpc.events/event/20/contributions/2429/
[2] https://docs.ebpf.io/linux/concepts/usdt/
---
 libgomp/config/linux/wait.h |  35 ++++++++-
 libgomp/env.c               | 143 +++++++++++++++++++++++++++++++++++-
 libgomp/libgomp.h           |   1 +
 libgomp/pvsched.h           |  81 ++++++++++++++++++++
 libgomp/team.c              |   4 +
 5 files changed, 259 insertions(+), 5 deletions(-)
 create mode 100644 libgomp/pvsched.h

diff --git a/libgomp/config/linux/wait.h b/libgomp/config/linux/wait.h
index c15e035dd56..fa6c80c67f7 100644
--- a/libgomp/config/linux/wait.h
+++ b/libgomp/config/linux/wait.h
@@ -31,6 +31,7 @@
 #define GOMP_WAIT_H 1
 
 #include "libgomp.h"
+#include "pvsched.h"
 #include <errno.h>
 
 #define FUTEX_WAIT     0
@@ -61,9 +62,39 @@ static inline int do_spin (int *addr, int val)
   return 1;
 }
 
+static inline int
+pv_spin (int *addr, int val)
+{
+  unsigned long long i, count = gomp_spin_count_var;
+  pvsched_u64 phantom_average;
+
+  if (__builtin_expect (__atomic_load_n (&gomp_managed_threads,
+                                         MEMMODEL_RELAXED)
+                        > gomp_available_cpus, 0))
+    count = gomp_throttled_spin_count_var;
+  for (i = 0; i < count; i++)
+    if (__builtin_expect (__atomic_load_n (addr, MEMMODEL_RELAXED) != val, 0))
+      return 0;
+    else
+      {
+       if (read_latest_phantom_average (&phantom_average) == 0
+           && phantom_average > 0)
+         return 1;
+       cpu_relax ();
+      }
+  return 1;
+}
+
 static inline void do_wait (int *addr, int val)
 {
-  if (do_spin (addr, val))
+  DTRACE_PROBE1 (gomp, do_wait, NULL);
+  int res;
+
+  if (gomp_pv_wait_policy == 2)
+    res = pv_spin (addr, val);
+  else
+    res = do_spin (addr, val);
+  if (res)
     futex_wait (addr, val);
 }
 
@@ -71,4 +102,4 @@ static inline void do_wait (int *addr, int val)
 # pragma GCC visibility pop
 #endif
 
-#endif /* GOMP_WAIT_H */
+#endif /* GOMP_WAIT_H */  
diff --git a/libgomp/env.c b/libgomp/env.c
index f48a95eff91..2a539c0221b 100644
--- a/libgomp/env.c
+++ b/libgomp/env.c
@@ -28,6 +28,7 @@
 
 #define _GNU_SOURCE
 #include "libgomp.h"
+#include "pvsched.h"
 #include "gomp-constants.h"
 #include <limits.h>
 #ifndef LIBGOMP_OFFLOADED_ONLY
@@ -130,6 +131,54 @@ int gomp_reverse_offload_threads = 8;  /* Reasonable 
default.  */
 
 static int wait_policy = -1;
 static unsigned long stacksize = GOMP_DEFAULT_STACKSIZE;
+int gomp_pv_wait_policy = -1;
+int guest_ivshmem_fd = -1;
+void *guest_ivshmem_h2g;
+struct ivshmem_header *pvsched_ivshmem_header;
+
+/* Parse the GOMP_WAIT_POLICY environment variable.  */
+static bool
+parse_pv_wait_policy (const char *name, int *pvalue)
+{
+  char *env;
+
+  env = getenv (name);
+  if (env == NULL)
+    return false;
+
+  while (isspace ((unsigned char) *env))
+    ++env;
+  if (*env == '\0')
+    goto invalid;
+  if (strncasecmp (env, "pvsched", 7) == 0)
+    {
+      *pvalue = 2;
+      env += 7;
+    }
+  else if (strncasecmp (env, "active", 6) == 0)
+    {
+      *pvalue = 1;
+      env += 6;
+    }
+  else if (strncasecmp (env, "passive", 7) == 0)
+    {
+      *pvalue = 0;
+      env += 7;
+    }
+  else
+    goto invalid;
+
+  while (isspace ((unsigned char) *env))
+    ++env;
+  if (*env != '\0')
+    goto invalid;
+  return true;
+
+ invalid:
+  gomp_error ("Invalid value for environment variable %s", name);
+  *pvalue = -1;
+  return false;
+}
 
 static void
 print_env_var_error (const char *env, const char *val)
@@ -631,7 +680,7 @@ parse_one_place (char **envp, bool *negatep, unsigned long 
*lenp,
        return false;
       env = end - 1;
       has_braces = false;
-      if (gomp_places_list
+      if (p
          && !gomp_affinity_add_cpus (p, this_num, 1, 1, false))
        return false;
     }
@@ -1967,6 +2016,8 @@ omp_display_env (int verbose)
       fprintf (stderr, "  [host] GOMP_SPINCOUNT = '%lu'\n",
               (unsigned long) gomp_spin_count_var);
 #endif
+      fprintf (stderr, "  [host] GOMP_WAIT_POLICY = '%d'\n",
+              gomp_pv_wait_policy);
     }
 
   fputs ("OPENMP DISPLAY ENVIRONMENT END\n", stderr);
@@ -2199,6 +2250,47 @@ cleanup_env (void)
     }
 }
 
+
+static int
+gomp_pvsched_init (void)
+{
+  void *base;
+  struct ivshmem_header *header;
+
+  guest_ivshmem_fd = open ("/dev/guest_ivshmem", O_RDONLY);
+  if (guest_ivshmem_fd < 0)
+    {
+      gomp_error ("failed to open /dev/guest_ivshmem: %s; falling back to "
+                 "the default OMP_WAIT_POLICY", strerror (errno));
+      return -1;
+    }
+
+  header = (struct ivshmem_header *) mmap (NULL, sizeof (struct 
ivshmem_header),
+                     PROT_READ, MAP_SHARED, guest_ivshmem_fd, 0);
+  if (header == MAP_FAILED)
+    {
+      gomp_error ("failed to read ivshmem header: %s; falling back to "
+                 "the default OMP_WAIT_POLICY", strerror (errno));
+      close (guest_ivshmem_fd);
+      guest_ivshmem_fd = -1;
+      return -1;
+    }
+  base = mmap (NULL, header->h2g_page_size, PROT_READ,
+                     MAP_SHARED, guest_ivshmem_fd, header->h2g_page_offset);
+  if (base == MAP_FAILED)
+    {
+      gomp_error ("failed to map guest_ivshmem H2G pages: %s; "
+                 "falling back to the default OMP_WAIT_POLICY", strerror 
(errno));
+      munmap (header, sizeof (struct ivshmem_header));
+      close (guest_ivshmem_fd);
+      guest_ivshmem_fd = -1;
+      return -1;
+    }
+  pvsched_ivshmem_header = header;
+  guest_ivshmem_h2g = base;
+  return 0;
+}
+
 static void __attribute__((constructor))
 initialize_env (void)
 {
@@ -2210,6 +2302,7 @@ initialize_env (void)
   uint32_t *flag_var_addr = NULL;
   unsigned pos;
   struct gomp_icv_list *all, *none;
+  int gomp_wait_policy_init = 0;
 
   /* Do a compile time check that mkomp_h.pl did good job.  */
   omp_check_defines ();
@@ -2422,12 +2515,19 @@ initialize_env (void)
     wait_policy = none->icvs.wait_policy;
   else if (all != NULL && gomp_get_icv_flag (all->flags, GOMP_ICV_WAIT_POLICY))
     wait_policy = all->icvs.wait_policy;
+  
+  if(!parse_pv_wait_policy("GOMP_WAIT_POLICY", &gomp_pv_wait_policy))
+    gomp_pv_wait_policy = -1;
+
+  if (gomp_pv_wait_policy == 2)
+    gomp_wait_policy_init = gomp_pvsched_init ();
+
 
   if (!parse_spincount ("GOMP_SPINCOUNT", &gomp_spin_count_var))
     {
       /* Using a rough estimation of 100000 spins per msec,
-        use 5 min blocking for OMP_WAIT_POLICY=active,
-        3 msec blocking when OMP_WAIT_POLICY is not specificed
+        use 5 min blocking for OMP_WAIT_POLICY=active or 
GOMP_WAIT_POLICY=active,
+        3 msec blocking when OMP_WAIT_POLICY is not specified
         and 0 when OMP_WAIT_POLICY=passive.
         Depending on the CPU speed, this can be e.g. 5 times longer
         or 5 times shorter.  */
@@ -2445,6 +2545,42 @@ initialize_env (void)
     gomp_throttled_spin_count_var = 1000LL;
   else if (wait_policy < 0)
     gomp_throttled_spin_count_var = 100LL;
+
+  if (gomp_pv_wait_policy >= 0 && wait_policy >= 0
+      && gomp_pv_wait_policy != wait_policy)
+    {
+      gomp_error ("Conflicting OMP_WAIT_POLICY and GOMP_WAIT_POLICY; "
+                 "Prioritizing OMP_WAIT_POLICY");
+      gomp_pv_wait_policy = -1;
+    }
+
+  if (gomp_pv_wait_policy == 2)
+    {
+      if (gomp_wait_policy_init < 0)
+       {
+         gomp_error ("Error initializing pvsched; falling back to default 
OMP_WAIT_POLICY");
+         gomp_pv_wait_policy = -1;
+         gomp_spin_count_var = 300000LL;
+         gomp_throttled_spin_count_var = 100LL;
+       }
+      else
+       {
+         gomp_spin_count_var = 30000000000LL;
+         gomp_throttled_spin_count_var = 1000LL;
+       }
+    }
+  else if (gomp_pv_wait_policy == 1)
+    {
+      gomp_spin_count_var = 30000000000LL;
+      gomp_throttled_spin_count_var = 1000LL;
+    }
+  else if (gomp_pv_wait_policy == 0)
+    gomp_spin_count_var = 0;
+  else if (gomp_pv_wait_policy < 0)
+    {
+      gomp_throttled_spin_count_var = 100LL;
+      gomp_spin_count_var = 300000LL;
+    }
   if (gomp_throttled_spin_count_var > gomp_spin_count_var)
     gomp_throttled_spin_count_var = gomp_spin_count_var;
 
@@ -2508,4 +2644,5 @@ initialize_env (void)
 
   goacc_profiling_initialize ();
 }
+
 #endif /* LIBGOMP_OFFLOADED_ONLY */
diff --git a/libgomp/libgomp.h b/libgomp/libgomp.h
index 54e1c962bb8..cb175fe9ee7 100644
--- a/libgomp/libgomp.h
+++ b/libgomp/libgomp.h
@@ -621,6 +621,7 @@ extern int goacc_device_num;
 extern char *goacc_device_type;
 extern int goacc_default_dims[GOMP_DIM_MAX];
 extern int gomp_reverse_offload_threads;
+extern int gomp_pv_wait_policy;
 
 enum gomp_task_kind
 {
diff --git a/libgomp/pvsched.h b/libgomp/pvsched.h
new file mode 100644
index 00000000000..9ceec7df7fd
--- /dev/null
+++ b/libgomp/pvsched.h
@@ -0,0 +1,81 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+   Contributed by NchangRoy <[email protected]>,
+   Himadri CS <[email protected]>,
+   Andrea Righi <[email protected]>,
+   Tobias Burnus <[email protected]>,
+   Thomas Schwinge <[email protected]>, as part of GSoC 2026.
+
+   This file is part of the GNU Offloading and Multi Processing Library
+   (libgomp).
+
+   Libgomp is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+   more details.
+
+   Under Section 7 of GPL version 3, you are granted additional
+   permissions described in the GCC Runtime Library Exception, version
+   3.1, as published by the Free Software Foundation.
+
+   You should have received a copy of the GNU General Public License and
+   a copy of the GCC Runtime Library Exception along with this program;
+   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Support for pv-barrier-sync, a paravirtualized, scheduling-guided
+   barrier synchronization mechanism for libgomp.
+
+   Policy inspired by: Himadri Chhaya-Shailesh, "Scheduler Guided OpenMP
+   Execution in Cloud VMs" (PhD thesis),
+   https://inria.hal.science/tel-05438117v2
+
+   This policy depends on an external component, Phantom Tracker:
+   https://github.com/phantom-tracker/phantom-tracker  */
+
+#ifndef PVSCHED_H
+#define PVSCHED_H
+
+#include "libgomp.h"
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <sys/sdt.h>
+#include <errno.h>
+
+typedef uint64_t pvsched_u64;
+
+struct hg_message {
+  pvsched_u64 msg;
+  pvsched_u64 padding [7];
+};
+
+struct ivshmem_header {
+       uint32_t magic;
+       uint32_t version;
+       uint32_t latest_slot;
+       uint64_t h2g_page_offset;
+       uint64_t h2g_page_size;
+};
+
+extern int guest_ivshmem_fd;
+extern void *guest_ivshmem_h2g;
+extern struct ivshmem_header *pvsched_ivshmem_header;
+
+static inline int
+read_latest_phantom_average (pvsched_u64 *avg)
+{
+  if (guest_ivshmem_h2g == NULL)
+    return -ENODEV;
+  *avg = __atomic_load_n (&((struct hg_message *) 
guest_ivshmem_h2g)[pvsched_ivshmem_header->latest_slot].msg,
+                          MEMMODEL_RELAXED);
+  DTRACE_PROBE1 (gomp, phantom_average, *avg);
+  return 0;
+}
+
+#endif
\ No newline at end of file
diff --git a/libgomp/team.c b/libgomp/team.c
index cb1d3235312..dec88633378 100644
--- a/libgomp/team.c
+++ b/libgomp/team.c
@@ -27,10 +27,12 @@
    creation and termination.  */
 
 #include "libgomp.h"
+#include "pvsched.h"
 #include "pool.h"
 #include <stdlib.h>
 #include <string.h>
 
+
 #ifdef LIBGOMP_USE_PTHREADS
 pthread_attr_t gomp_thread_attr;
 
@@ -69,6 +71,8 @@ struct gomp_thread_start_data
 static void *
 gomp_thread_start (void *xdata)
 {
+  /* USDT probe for pv-barrier-sync to trace worker thread creation.  */
+  DTRACE_PROBE1 (gomp, gomp_thread_start, NULL);
   struct gomp_thread_start_data *data = xdata;
   struct gomp_thread *thr;
   struct gomp_thread_pool *pool;
-- 
2.34.1

Reply via email to