[gcc/devel/omp/gcc-16] libgomp: Runtime USM

Gio T via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:7ad97ed9a70d18f518a2623af3113178fa6f8d79

commit 7ad97ed9a70d18f518a2623af3113178fa6f8d79
Author: supers1ngular <[email protected]>
Date:   Mon Jul 13 21:37:12 2026 -0700

    libgomp: Runtime USM
    
    This patch introduces support for runtime USM, meaning that libgomp
    can now automatically detect and set Unified Shared Memory capabilities.
    It can be controlled via a new environment variable, 'GOMP_RUNTIME_USM',
    which can be set to 'enabled', 'auto', or 'disabled'.  Documentation
    for the feature has also been added.
    
    libgomp/ChangeLog:
    
            * env.c (enum gomp_runtime_usm_t): New environment variable.
            (parse_rt_usm): Parse new environment variable.
            (initialize_env): Behavior handling for new variable.
            * libgomp-plugin.h (GOMP_OFFLOAD_CAP_AUTO_USM): New capability.
            (GOMP_OFFLOAD_get_dev_caps): New function for enumeration.
            * libgomp.h (enum gomp_device_num): Fix whitespace.
            (enum gomp_runtime_usm_t): Define.
            (struct gomp_device_descr): Add 'get_dev_caps_func'.
            * libgomp.texi: Update documentation.
            * plugin/plugin-gcn.c (get_memory_region): Fix whitespace.
            (process_reverse_offload): Ditto.
            (gcn_exec): Remove preprocessor elision.
            (is_apu_with_xnack): New function.
            (GCN_DEVICE): Query XNACK support.
            (GOMP_OFFLOAD_get_dev_caps): New function.
            * plugin/plugin-nvptx.c (GOMP_OFFLOAD_get_dev_caps): Ditto.
            * target.c (gomp_init_device): Logic for deciding USM behavior.
            (gomp_load_plugin_for_device): Get symbol.
            (gomp_target_init): New comment, and fix style.

Diff:
---
 libgomp/env.c                 | 71 ++++++++++++++++++++++++++++++++++++++++++-
 libgomp/libgomp-plugin.h      |  2 ++
 libgomp/libgomp.h             | 13 ++++++--
 libgomp/libgomp.texi          | 20 ++++++++++++
 libgomp/plugin/plugin-gcn.c   | 56 +++++++++++++++++++++++++++++++---
 libgomp/plugin/plugin-nvptx.c | 19 ++++++++++++
 libgomp/target.c              | 29 +++++++++++++++---
 7 files changed, 199 insertions(+), 11 deletions(-)

diff --git a/libgomp/env.c b/libgomp/env.c
index 07de99b48977..3e5f557c5a29 100644
--- a/libgomp/env.c
+++ b/libgomp/env.c
@@ -121,6 +121,8 @@ int gomp_teams_thread_limit_var;
 bool gomp_display_affinity_var;
 char *gomp_affinity_format_var = "level %L thread %i affinity %A";
 size_t gomp_affinity_format_len;
+enum gomp_runtime_usm_t gomp_runtime_usm_var
+  = GOMP_RUNTIME_USM_DISABLED;
 char *goacc_device_type;
 int goacc_device_num;
 int goacc_default_dims[GOMP_DIM_MAX];
@@ -1057,6 +1059,53 @@ parse_spincount (const char *name, unsigned long long *pvalue)
   return false;
 }
 
+static bool
+parse_rt_usm (const char *name, enum gomp_runtime_usm_t *val)
+{
+  char *env, *end;
+  env = getenv (name);
+  if (env == NULL)
+    return false;
+  end = env;
+  while (isspace ((unsigned char) *env))
+    ++env;
+  if (*env == '\0')
+  {
+    gomp_error ("Invalid value for environment variable %s", name);
+    return false;
+  }
+  enum gomp_runtime_usm_t store_state = GOMP_RUNTIME_USM_DISABLED;
+  if (strncasecmp (env, "disabled", 8) == 0)
+    {
+      store_state = GOMP_RUNTIME_USM_DISABLED;
+      end += 8;
+    }
+  else if (strncasecmp (env, "auto", 4) == 0)
+    {
+      store_state = GOMP_RUNTIME_USM_AUTO;
+      end += 4;
+    }
+  else if (strncasecmp (env, "enabled", 7) == 0)
+    {
+      store_state = GOMP_RUNTIME_USM_ENABLED;
+      end += 7;
+    }
+  else
+    {
+      gomp_error ("Invalid value for environment variable %s", name);
+      return false;
+    }
+  while (isspace ((unsigned char) *end))
+    ++end;
+  if (*end != '\0')
+    {
+      gomp_error ("Invalid value for environment variable %s", name);
+      return false;
+    }
+  *val = store_state;
+  return true;
+}
+
 /* Parse a boolean value for environment variable NAME and store the
    result in VALUE.  Return true if one was present and it was
    successfully parsed.  */
@@ -1975,8 +2024,21 @@ omp_display_env (int verbose)
       fprintf (stderr, "  [host] GOMP_SPINCOUNT = '%lu'\n",
 	       (unsigned long) gomp_spin_count_var);
 #endif
+      fputs ("  [device] GOMP_RUNTIME_USM = '", stderr);
+      switch (gomp_runtime_usm_var)
+	{
+	case GOMP_RUNTIME_USM_DISABLED:
+	  fputs ("DISABLED", stderr);
+	  break;
+	case GOMP_RUNTIME_USM_AUTO:
+	  fputs ("AUTO", stderr);
+	  break;
+	case GOMP_RUNTIME_USM_ENABLED:
+	  fputs ("ENABLED", stderr);
+	  break;
+	}
+      fputs ("'\n", stderr);
     }
-
   fputs ("OPENMP DISPLAY ENVIRONMENT END\n", stderr);
 }
 ialias (omp_display_env)
@@ -2456,6 +2518,13 @@ initialize_env (void)
   if (gomp_throttled_spin_count_var > gomp_spin_count_var)
     gomp_throttled_spin_count_var = gomp_spin_count_var;
 
+  /* If we fail, we currently default to 'disabled', both here and on
+     initialization of 'gomp_runtime_usm_var'.  Eventually, it may
+     bear consideration if we wish to rather default to 'auto' - but
+     until then, we do not wish to surprise.  */
+  if (!parse_rt_usm ("GOMP_RUNTIME_USM", &gomp_runtime_usm_var))
+    gomp_runtime_usm_var = GOMP_RUNTIME_USM_DISABLED;
+
   /* Not strictly environment related, but ordering constructors is tricky.  */
   pthread_attr_init (&gomp_thread_attr);
 
diff --git a/libgomp/libgomp-plugin.h b/libgomp/libgomp-plugin.h
index 5c770c80202e..051666617874 100644
--- a/libgomp/libgomp-plugin.h
+++ b/libgomp/libgomp-plugin.h
@@ -50,6 +50,7 @@ extern "C" {
 #define GOMP_OFFLOAD_CAP_NATIVE_EXEC	(1 << 1)
 #define GOMP_OFFLOAD_CAP_OPENMP_400	(1 << 2)
 #define GOMP_OFFLOAD_CAP_OPENACC_200	(1 << 3)
+#define GOMP_OFFLOAD_CAP_AUTO_USM	(1 << 4)
 
 /* Type of offload target device.  Keep in sync with include/gomp-constants.h.  */
 enum offload_target_type
@@ -164,6 +165,7 @@ extern int GOMP_OFFLOAD_supported_teams_dim (int, int);
 extern int GOMP_OFFLOAD_supported_threads_dim (int, int);
 
 extern unsigned int GOMP_OFFLOAD_get_caps (void);
+extern unsigned int GOMP_OFFLOAD_get_dev_caps (int);
 extern int GOMP_OFFLOAD_get_type (void);
 extern int GOMP_OFFLOAD_get_num_devices (unsigned int);
 extern bool GOMP_OFFLOAD_init_device (int);
diff --git a/libgomp/libgomp.h b/libgomp/libgomp.h
index af771be090e1..9f13f9e32c2a 100644
--- a/libgomp/libgomp.h
+++ b/libgomp/libgomp.h
@@ -494,7 +494,7 @@ enum gomp_device_num
    section 2.3.1.  Those described as having one copy per task are
    stored within the structure; those described as having one copy
    for the whole program are (naturally) global variables.  */
-   
+
 struct gomp_task_icv
 {
   unsigned long nthreads_var;
@@ -590,6 +590,13 @@ enum gomp_target_offload_t
   GOMP_TARGET_OFFLOAD_DISABLED
 };
 
+enum gomp_runtime_usm_t
+{
+  GOMP_RUNTIME_USM_DISABLED,
+  GOMP_RUNTIME_USM_AUTO,
+  GOMP_RUNTIME_USM_ENABLED
+};
+
 #define gomp_supported_active_levels UCHAR_MAX
 
 extern struct gomp_task_icv gomp_global_icv;
@@ -620,6 +627,7 @@ extern const size_t gomp_omp_allocator_data_size;
 extern const struct gomp_default_icv gomp_default_icv_values;
 extern struct gomp_icv_list *gomp_initial_icv_list;
 extern struct gomp_offload_icv_list *gomp_offload_icv_list;
+extern enum gomp_runtime_usm_t gomp_runtime_usm_var;
 extern int goacc_device_num;
 extern char *goacc_device_type;
 extern int goacc_default_dims[GOMP_DIM_MAX];
@@ -1396,7 +1404,7 @@ typedef struct acc_dispatch_t
   __typeof (GOMP_OFFLOAD_openacc_create_thread_data) *create_thread_data_func;
   __typeof (GOMP_OFFLOAD_openacc_destroy_thread_data)
     *destroy_thread_data_func;
-  
+
   struct {
     /* Once created and put into the "active" list, asyncqueues are then never
        destructed and removed from the "active" list, other than if the TODO
@@ -1469,6 +1477,7 @@ struct gomp_device_descr
   __typeof (GOMP_OFFLOAD_supported_teams_dim) *supported_teams_dim_func;
   __typeof (GOMP_OFFLOAD_supported_threads_dim) *supported_threads_dim_func;
   __typeof (GOMP_OFFLOAD_get_caps) *get_caps_func;
+  __typeof (GOMP_OFFLOAD_get_dev_caps) *get_dev_caps_func;
   __typeof (GOMP_OFFLOAD_get_type) *get_type_func;
   __typeof (GOMP_OFFLOAD_get_num_devices) *get_num_devices_func;
   __typeof (GOMP_OFFLOAD_init_device) *init_device_func;
diff --git a/libgomp/libgomp.texi b/libgomp/libgomp.texi
index e12d4488a5ab..526c1ea3ab9b 100644
--- a/libgomp/libgomp.texi
+++ b/libgomp/libgomp.texi
@@ -4569,6 +4569,7 @@ variable is not set.
 * OMP_WAIT_POLICY::         How waiting threads are handled
 * GOMP_CPU_AFFINITY::       Bind threads to specific CPUs
 * GOMP_DEBUG::              Enable debugging output
+* GOMP_RUNTIME_USM::        Control runtime enabling of Unified Shared Memory
 * GOMP_STACKSIZE::          Set default thread stack size
 * GOMP_SPINCOUNT::          Set the busy-wait spin count
 * GOMP_RTEMS_THREAD_POOLS:: Set the RTEMS specific thread pools
@@ -5199,6 +5200,25 @@ This is currently not specified in more detail, and subject to change.
 
 
 
+@node GOMP_RUNTIME_USM
+@section @env{GOMP_RUNTIME_USM} -- Control runtime enabling of Unified Shared Memory
+@cindex Environment Variable
+@table @asis
+@item @emph{Description}:
+Control the behavior of automatic Unified Shared Memory (USM) in the runtime.
+The default is @code{disabled}, meaning that Unified Shared Memory will
+only be used if specified by the @code{requires} clause.  The variable
+may also be set to @code{auto}, in which the runtime will decide whether
+or not to use USM based on safety and performance considerations.  More
+specifically, it will only utilize USM if it detects XNACK support and
+an APU.  Lastly, one can set the variable to @code{enabled}, which will
+unconditionally trust the device's self-report.  This can potentially
+cause instability, as some devices report capabilities that they may not
+actually have.
+@end table
+
+
+
 @node GOMP_STACKSIZE
 @section @env{GOMP_STACKSIZE} -- Set default thread stack size
 @cindex Environment Variable
diff --git a/libgomp/plugin/plugin-gcn.c b/libgomp/plugin/plugin-gcn.c
index 2a532b1141dc..adf1691eca1d 100644
--- a/libgomp/plugin/plugin-gcn.c
+++ b/libgomp/plugin/plugin-gcn.c
@@ -1747,7 +1747,7 @@ get_memory_region (hsa_region_t region, hsa_region_t *retval,
 }
 
 /* Callback of hsa_agent_iterate_regions.
- 
+
    Selects a kernargs memory region.  */
 
 static hsa_status_t
@@ -2239,7 +2239,7 @@ process_reverse_offload (uint64_t fn, uint64_t mapnum, uint64_t hostaddrs,
    We print all entries from the last item printed to the next entry without
    a "written" flag.  If the "final" flag is set then it'll continue right to
    the end.
- 
+
    The print buffer is circular, but the from and to locations don't wrap when
    the buffer does, so the output limit is UINT_MAX.  The target blocks on
    output when the buffer is full.  */
@@ -3648,7 +3648,6 @@ gcn_exec (struct kernel_info *kernel, struct gomp_offload_session *session,
 /* }}}  */
 /* {{{ Generic Plugin API  */
 
-#if 0  /* TODO: Use to enable self-mapping/USM automatically.  */
 /* FIXME: The auto-self-map feature depends on still mapping 'declare target'
    variables, even if ignoring all other mappings. Cf. PR 115279.  */
 
@@ -3701,7 +3700,37 @@ is_integrated_apu (struct agent_info *agent, bool check_xnack)
       }
   return is_apu;
 }
-#endif
+
+static bool
+is_apu_with_xnack (struct agent_info *agent)
+{
+  /* We do not care for non-APU targets, at the moment.
+     If this ever changes, we can just elide the below check.
+
+     Another point is that we are assuming the gfx902 does not
+     incur performance penalties, as it reports USM and XNACK+.
+     If it is later shown that enabling USM by default on this
+     platform incurs performance issues, then we need to add
+     an additional carveout here.  */
+  if (!is_integrated_apu (agent, false))
+    return false;
+  enum {
+    HSACO_ATTR_UNSUPPORTED,
+    HSACO_ATTR_OFF,
+    HSACO_ATTR_ON,
+    HSACO_ATTR_ANY,
+    HSACO_ATTR_DEFAULT
+  };
+
+  switch (agent->device_isa)
+    {
+#define GCN_DEVICE(name, NAME, ELF, ISA, XNACK, ...) \
+    case ELF: return (XNACK == HSACO_ATTR_ANY);
+#include "../../gcc/config/gcn/gcn-devices.def"
+    default: return false;
+    }
+  return false;
+}
 
 /* Return the name of the accelerator, which is "gcn".  */
 
@@ -3811,6 +3840,25 @@ GOMP_OFFLOAD_get_caps (void)
 	    | GOMP_OFFLOAD_CAP_OPENACC_200;
 }
 
+
+
+unsigned int
+GOMP_OFFLOAD_get_dev_caps (int ord)
+{
+  struct agent_info *agent = get_agent_info (ord);
+  bool claims_usm_p = 0;
+  unsigned int flags = GOMP_OFFLOAD_CAP_OPENMP_400
+		       | GOMP_OFFLOAD_CAP_OPENACC_200;
+  hsa_system_info_t type = HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT;
+  hsa_status_t status = hsa_fns.hsa_system_get_info_fn (type, &claims_usm_p);
+  if (status != HSA_STATUS_SUCCESS)
+    GOMP_PLUGIN_error ("Could not fetch SVM_ACCESSIBLE_BY_DEFAULT");
+  if (claims_usm_p)
+    flags |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+  if (claims_usm_p && is_apu_with_xnack (agent))
+    flags |= GOMP_OFFLOAD_CAP_AUTO_USM;
+  return flags;
+}
 /* Identify as GCN accelerator.  */
 
 int
diff --git a/libgomp/plugin/plugin-nvptx.c b/libgomp/plugin/plugin-nvptx.c
index 0be45c51d35c..7e5e044eb74f 100644
--- a/libgomp/plugin/plugin-nvptx.c
+++ b/libgomp/plugin/plugin-nvptx.c
@@ -1434,6 +1434,25 @@ GOMP_OFFLOAD_get_caps (void)
   return GOMP_OFFLOAD_CAP_OPENACC_200 | GOMP_OFFLOAD_CAP_OPENMP_400;
 }
 
+/* We duplicate functionality here for consistency, as this function
+   in plugin-gcn.c is for runtime USM.  The gcn function determines
+   the case for auto-usm as well, however, here we only concern
+   ourselves for the enabled and disabled case.  */
+
+unsigned int
+GOMP_OFFLOAD_get_dev_caps (int ord)
+{
+  unsigned int flags = GOMP_OFFLOAD_CAP_OPENACC_200
+		       | GOMP_OFFLOAD_CAP_OPENMP_400;
+  int pi = 0;
+  CUresult r;
+  r = CUDA_CALL_NOCHECK (cuDeviceGetAttribute, &pi,
+			 CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING, ord);
+  if (r == CUDA_SUCCESS && pi)
+    flags |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+  return flags;
+}
+
 int
 GOMP_OFFLOAD_get_type (void)
 {
diff --git a/libgomp/target.c b/libgomp/target.c
index c3d395103b46..859e2de18471 100644
--- a/libgomp/target.c
+++ b/libgomp/target.c
@@ -3359,6 +3359,24 @@ gomp_init_device (struct gomp_device_descr *devicep)
       gomp_fatal ("device initialization failed");
     }
 
+  /* Now that we have initialized the device, we can evaluate auto USM.
+     If the user has explicitly requested USM, we can just skip the check.  */
+  if (!(devicep->capabilities & GOMP_OFFLOAD_CAP_SHARED_MEM))
+    {
+      devicep->capabilities = devicep->get_dev_caps_func (devicep->target_id);
+      if ((gomp_runtime_usm_var == GOMP_RUNTIME_USM_AUTO)
+	   && !(devicep->capabilities & GOMP_OFFLOAD_CAP_AUTO_USM))
+	devicep->capabilities &= ~GOMP_OFFLOAD_CAP_SHARED_MEM;
+      if (gomp_runtime_usm_var == GOMP_RUNTIME_USM_DISABLED)
+	devicep->capabilities &= ~GOMP_OFFLOAD_CAP_SHARED_MEM;
+    }
+  /* Peel off the GOMP_OFFLOAD_CAP_AUTO_USM, if it was set by the plugin, as
+     we no longer need it.  */
+  devicep->capabilities &= ~GOMP_OFFLOAD_CAP_AUTO_USM;
+  /* We can now set the requires mask based on the capabilities.
+     This makes it so the runtime treats it as if the user requested USM.  */
+  if (devicep->capabilities & GOMP_OFFLOAD_CAP_SHARED_MEM)
+    omp_requires_mask |= GOMP_REQUIRES_UNIFIED_SHARED_MEMORY;
   /* Load to device all images registered by the moment.  */
   for (i = 0; i < num_offload_images; i++)
     {
@@ -3372,6 +3390,7 @@ gomp_init_device (struct gomp_device_descr *devicep)
   /* Initialize OpenACC asynchronous queues.  */
   goacc_init_asyncqueues (devicep);
 
+  gomp_debug (0, "capabilities: %d\n", devicep->capabilities);
   devicep->state = GOMP_DEVICE_INITIALIZED;
 }
 
@@ -6728,6 +6747,7 @@ gomp_load_plugin_for_device (struct gomp_device_descr *device,
   DLSYM_OPT (supported_threads_dim, supported_threads_dim);
   DLSYM_OPT (supported_teams_dim, supported_teams_dim);
   DLSYM (get_caps);
+  DLSYM (get_dev_caps);
   DLSYM (get_type);
   DLSYM (get_num_devices);
   DLSYM (init_device);
@@ -6753,7 +6773,8 @@ gomp_load_plugin_for_device (struct gomp_device_descr *device,
       DLSYM (get_interop_str);
       DLSYM (get_interop_type_desc);
     }
-
+  /* Returns offloading capabilities, but does not say anything about
+     auto USM yet.  */
   device->capabilities = device->get_caps_func ();
   device->session.size = 0;
   if (device->capabilities & GOMP_OFFLOAD_CAP_OPENMP_400)
@@ -6921,10 +6942,10 @@ gomp_target_init (void)
 	      {
 		/* Augment DEVICES and NUM_DEVICES.  */
 
-		/* If USM has been requested and is supported by all devices
-		   of this type, set the capability accordingly.  */
+		/* If USM has been requested, set the capability.  */
 		if (omp_requires_mask
-		    & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY | GOMP_REQUIRES_SELF_MAPS))
+		    & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY
+		       | GOMP_REQUIRES_SELF_MAPS))
 		  current_device.capabilities |= GOMP_OFFLOAD_CAP_SHARED_MEM;
 
 		devs = realloc (devs, (num_devs + new_num_devs)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.