Currently, libgomp only supports statically determined capabilities
per device type. And it passes an OpenMP requirement to num_devices
to filter out devices that do not fulfill the requirement.

That works pretty well, but sometimes something more fine grained
would be useful. Assume two nvidia devices, one supporting the
access to host memory and the other not. (Like an old one and a
new one)

With 'omp requires unified_shared_memory', we would currently disable
both - even if one works - and calling to cuDeviceGetAttribute
with CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS already provides this
data per device.

Another case is a AMD Ryzen 9 7900X that has a gfx1036 on the same
die (an APU) and an RX 6950 XT as PCI card (discrete GPU). Using
-march=gfx-10-3-generic, code for both cards can be produced.

When both cards are enabled, the global SVM_ACCESSIBLE_BY_DEFAULT is
false - but as APUs can access host memory, we can enable support for
that GPU even if not all GPUs support it.

Thus:
* This patch enabled by GPU support for capabilites and
  moves the no-GPU matched code to libgomp's target.c itself.
* It also detects APUs and claims USM support for those.


This patch has been based on:
- some prior commented out is-APU check code in the plugins
- Andrew's patch for by-device capabilities at
  
https://patchwork.sourceware.org/project/gcc/patch/[email protected]/
  (the patch does more)
- Gio's patch athttps://gcc.gnu.org/pipermail/gcc-patches/2026-July/723729.html


Comments, remarks, suggestions, concerns?

Tobias

PS: A revised version of Gio's patch is supposed to land as follow up;
that one permits to enable self-mapping also via an environment variable.

For that use, it makes in particular sense to have a by-device cap support,
which is the main motivation for this patch.

Generally, enabling USM automatically is possible, but only if either
data mapping still happens for the static 'declare target' variables
or when the program has non such variables. Manually enabling the feature
(via an env var) additionally works, if no code copies from/to the device
variable from the host - and permits shifting some responsible to the user.

For 'omp requires self_maps', the same issues shows up, in principle, but
the compiler can change 'enter' to 'link' (+ libgomp updates those to
point to the host variable) - such that accessing host variables from the
device then 'automatically' works. That's what
https://gcc.gnu.org/pipermail/gcc-patches/2026-July/723214.html does.
libgomp: Add by-device capabilities for OpenMP 'omp requires' handling

Before the capabilities of a device were statically set to a generic
by-device-type value. This has been modified to additionally set them
to a value for a specific device.

On the plugin side, this permits to set the unified-shared memory
capability for only a subset of devices and to mark devices that are
integrated GPUs (APU) and support USM, which in turn permitted to move
the handling of OpenMP requirements from the plugin back to target.c
and permits in future to automatically enable unified-shared memory
for integrated GPUs/APUs.

Co-authored-by: Andrew Stubbs <[email protected]>
Co-authored-by: supers1ngular <[email protected]>

include/ChangeLog:

	* hsa.h (hsa_system_info_t): Add HSA_AMD_SYSTEM_INFO_XNACK_ENABLED.

libgomp/ChangeLog:

	* libgomp-plugin.h (GOMP_OFFLOAD_CAP_APU_SHARED_MEM,
	GOMP_OFFLOAD_CAP_UNIFIED_ADDR, GOMP_OFFLOAD_CAP_REV_OFFLOAD): Add.
	(GOMP_OFFLOAD_get_dev_caps): New prototype.
	(GOMP_OFFLOAD_get_num_devices): Remove requirement argument.
	* libgomp.h (struct gomp_device_descr): Add get_dev_caps_func.
	* libgomp.texi (nvptx, AMD Radeon): Update for by-device
	USM support.
	* oacc-host.c (host_get_num_devices,
	host_openacc_get_property): Update call.
	* oacc-init.c (resolve_device, acc_init_1, acc_shutdown_1,
	goacc_attach_host_thread_to_device, acc_get_num_devices,
	acc_set_device_num, get_property_any): Likewise.
	* plugin/plugin-gcn.c (is_integrated_apu): Remove.
	(GOMP_OFFLOAD_get_caps): Mark unified address and
	reverse offload as supported.
	(GOMP_OFFLOAD_get_dev_caps): New. 
	(GOMP_OFFLOAD_get_num_devices): Remove requirements arg.
	* plugin/plugin-nvptx.c (using_usm): Init with -1.
	(GOMP_OFFLOAD_get_caps): Mark unified address and
	reverse offload as supported.
	(GOMP_OFFLOAD_get_dev_caps): New.
	(GOMP_OFFLOAD_get_num_devices): Remove requirements arg.
	(GOMP_OFFLOAD_is_accessible_ptr): Check for using_usm > 0.
	* target.c (gomp_load_plugin_for_device): Load get_dev_caps.
	(gomp_target_init): Change to by-device capabilities, skip
	only those devices not devices types that do not support a
	capability.

 include/hsa.h                 |   3 +-
 libgomp/libgomp-plugin.h      |  12 ++-
 libgomp/libgomp.h             |   1 +
 libgomp/libgomp.texi          |  32 +++----
 libgomp/oacc-host.c           |   4 +-
 libgomp/oacc-init.c           |  16 ++--
 libgomp/plugin/plugin-gcn.c   | 193 +++++++++++++++++++++++-------------------
 libgomp/plugin/plugin-nvptx.c |  96 +++++++++++++--------
 libgomp/target.c              |  79 ++++++++++-------
 9 files changed, 256 insertions(+), 180 deletions(-)

diff --git a/include/hsa.h b/include/hsa.h
index b591103c7eb..988e68973d3 100644
--- a/include/hsa.h
+++ b/include/hsa.h
@@ -480,7 +480,8 @@ typedef enum {
   * an Agent by declaration of accessibility with hsa_amd_svm_set_attributes.
   * The type of this attribute is bool.
   */
-  HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT = 0x202
+  HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT = 0x202,
+  HSA_AMD_SYSTEM_INFO_XNACK_ENABLED = 0x206
 } hsa_system_info_t;
 
 /**
diff --git a/libgomp/libgomp-plugin.h b/libgomp/libgomp-plugin.h
index db7c03fb350..a01873131fb 100644
--- a/libgomp/libgomp-plugin.h
+++ b/libgomp/libgomp-plugin.h
@@ -45,11 +45,18 @@
 extern "C" {
 #endif
 
-/* Capabilities of offloading devices.  */
+/* Capabilities of offloading devices.
+   GOMP_OFFLOAD_CAP_APU_SHARED_MEM is for devices where self mapping, i.e.
+   accessing the host memory directly, is expected to be faster than copying
+   the data.  That's the case for integrated GPUs (APUs) that use the same
+   memory controller for the host and device.  */
 #define GOMP_OFFLOAD_CAP_SHARED_MEM	(1 << 0)
 #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_APU_SHARED_MEM (1 << 4)
+#define GOMP_OFFLOAD_CAP_UNIFIED_ADDR	(1 << 5)
+#define GOMP_OFFLOAD_CAP_REV_OFFLOAD	(1 << 6)
 
 /* Type of offload target device.  Keep in sync with include/gomp-constants.h.  */
 enum offload_target_type
@@ -164,8 +171,9 @@ 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 int GOMP_OFFLOAD_get_num_devices (void);
 extern bool GOMP_OFFLOAD_init_device (int);
 extern bool GOMP_OFFLOAD_fini_device (int);
 extern unsigned GOMP_OFFLOAD_version (void);
diff --git a/libgomp/libgomp.h b/libgomp/libgomp.h
index 3c8a2a1115e..19d4909316e 100644
--- a/libgomp/libgomp.h
+++ b/libgomp/libgomp.h
@@ -1453,6 +1453,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 291f1fbd1a4..564a1df2091 100644
--- a/libgomp/libgomp.texi
+++ b/libgomp/libgomp.texi
@@ -3517,7 +3517,7 @@ successive clock ticks.
 @item @emph{Description}:
 Elapsed wall clock time in seconds.  The time is measured per thread, no
 guarantee can be made that two distinct threads measure the same time.
-Time is measured from some "time in the past", which is an arbitrary time
+Time is measured from some ``time in the past'', which is an arbitrary time
 guaranteed not to change during the execution of the program.
 
 @item @emph{C/C++}:
@@ -7703,16 +7703,18 @@ The implementation remark:
       such that the next reverse offload region is only executed after the previous
       one returned.
 @item OpenMP code that has a @code{requires} directive with @code{self_maps} or
-      @code{unified_shared_memory} is only supported if @emph{all} the AMD GPUs
-      present have the @code{HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT}
-      property; some systems require the "xnack" feature enabled for this to be
-      true, in which case the runtime will attempt to set the @code{HSA_XNACK}
-      environment variable to @samp{1} automatically (user-set values are not
-      overridden, and the setting only affects the executable itself and any
-      child processes).  If any AMD GPU device does not support unified-shared
-      memory, all AMD GPUs are removed from the list of available devices
-      (``host fallback''); consider using @code{ROCR_VISIBLE_DEVICES} to only
-      enable devices supporting it.
+      @code{unified_shared_memory} is only supported if either @emph{all} the
+      AMD GPUs present have the @code{HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT}
+      property or if a device is an APU (integrated GPU) and either it does not
+      support XNACK or XNACK is enabled; some systems require the ``xnack''
+      feature enabled for this to be true, in which case the runtime will
+      attempt to set the @code{HSA_XNACK} environment variable to @samp{1}
+      automatically (user-set values are not overridden, and the setting only
+      affects the executable itself and any child processes).  AMD GPU devices
+      that do not support unified-shared memory are removed from the list of
+      available devices, possibly leaving only the initial device (``host
+      fallback''); consider using @code{ROCR_VISIBLE_DEVICES} to only enable
+      devices supporting unified-shared memory.
 @item The available stack size can be changed using the @code{GCN_STACK_SIZE}
       environment variable; the default is 32 kiB per thread.
 @item Low-latency memory (@code{omp_low_lat_mem_space}) is supported when the
@@ -7891,11 +7893,11 @@ The implementation remark:
       the next reverse offload region is only executed after the previous
       one returned.
 @item OpenMP code that has a @code{requires} directive with @code{self_maps} or
-      @code{unified_shared_memory} runs on nvptx devices if and only if
-      all of those support the @code{pageableMemoryAccess} property;@footnote{
+      @code{unified_shared_memory} runs on those nvptx devices that support
+      the @code{pageableMemoryAccess} property;@footnote{
       @uref{https://docs.nvidia.com/cuda/cuda-programming-guide/04-special-topics/unified-memory.html}}
-      otherwise, all nvptx device are removed from the list of available
-      devices (``host fallback'').
+      all other nvptx device are removed from the list of available devices,
+      possibly leaving only the initial device (``host fallback'').
 @item The default per-warp stack size is 128 kiB; see also @code{-msoft-stack}
       in the GCC manual.
 @item Low-latency memory (@code{omp_low_lat_mem_space}) is supported when the
diff --git a/libgomp/oacc-host.c b/libgomp/oacc-host.c
index b0546c87405..ef552888cb5 100644
--- a/libgomp/oacc-host.c
+++ b/libgomp/oacc-host.c
@@ -66,7 +66,7 @@ host_get_type (void)
 }
 
 static int
-host_get_num_devices (unsigned int omp_requires_mask __attribute__((unused)))
+host_get_num_devices (void)
 {
   return 1;
 }
@@ -265,7 +265,7 @@ host_openacc_get_property (int n, enum goacc_property prop)
 {
   union goacc_property_value nullval = { .val = 0 };
 
-  if (n >= host_get_num_devices (0))
+  if (n >= host_get_num_devices ())
     return nullval;
 
   switch (prop)
diff --git a/libgomp/oacc-init.c b/libgomp/oacc-init.c
index 70bf10c84d9..bfd50bf39a5 100644
--- a/libgomp/oacc-init.c
+++ b/libgomp/oacc-init.c
@@ -148,7 +148,7 @@ resolve_device (acc_device_t d, bool fail_is_error)
 	      if (dispatchers[d]
 		  && !strcasecmp (goacc_device_type,
 				  get_openacc_name (dispatchers[d]->name))
-		  && dispatchers[d]->get_num_devices_func (0) > 0)
+		  && dispatchers[d]->get_num_devices_func () > 0)
 		goto found;
 
 	    if (fail_is_error)
@@ -169,7 +169,7 @@ resolve_device (acc_device_t d, bool fail_is_error)
     case acc_device_not_host:
       /* Find the first available device after acc_device_not_host.  */
       while (known_device_type_p (++d))
-	if (dispatchers[d] && dispatchers[d]->get_num_devices_func (0) > 0)
+	if (dispatchers[d] && dispatchers[d]->get_num_devices_func () > 0)
 	  goto found;
       if (d_arg == acc_device_default)
 	{
@@ -302,7 +302,7 @@ acc_init_1 (acc_device_t d, acc_construct_t parent_construct, int implicit)
 
   base_dev = resolve_device (d, true);
 
-  ndevs = base_dev->get_num_devices_func (0);
+  ndevs = base_dev->get_num_devices_func ();
 
   if (ndevs <= 0 || goacc_device_num >= ndevs)
     acc_dev_num_out_of_range (d, goacc_device_num, ndevs);
@@ -351,7 +351,7 @@ acc_shutdown_1 (acc_device_t d)
   /* Get the base device for this device type.  */
   base_dev = resolve_device (d, true);
 
-  ndevs = base_dev->get_num_devices_func (0);
+  ndevs = base_dev->get_num_devices_func ();
 
   /* Unload all the devices of this type that have been opened.  */
   for (i = 0; i < ndevs; i++)
@@ -520,7 +520,7 @@ goacc_attach_host_thread_to_device (int ord)
       base_dev = cached_base_dev;
     }
   
-  num_devices = base_dev->get_num_devices_func (0);
+  num_devices = base_dev->get_num_devices_func ();
   if (num_devices <= 0 || ord >= num_devices)
     acc_dev_num_out_of_range (acc_device_type (base_dev->type), ord,
 			      num_devices);
@@ -595,7 +595,7 @@ acc_get_num_devices (acc_device_t d)
   if (!acc_dev)
     return 0;
 
-  n = acc_dev->get_num_devices_func (0);
+  n = acc_dev->get_num_devices_func ();
   if (n < 0)
     n = 0;
 
@@ -767,7 +767,7 @@ acc_set_device_num (int ord, acc_device_t d)
 
       cached_base_dev = base_dev = resolve_device (d, true);
 
-      num_devices = base_dev->get_num_devices_func (0);
+      num_devices = base_dev->get_num_devices_func ();
 
       if (num_devices <= 0 || ord >= num_devices)
         acc_dev_num_out_of_range (d, ord, num_devices);
@@ -800,7 +800,7 @@ get_property_any (int ord, acc_device_t d, acc_device_property_t prop)
 
   struct gomp_device_descr *dev = resolve_device (d, true);
 
-  int num_devices = dev->get_num_devices_func (0);
+  int num_devices = dev->get_num_devices_func ();
 
   if (num_devices <= 0 || ord >= num_devices)
     acc_dev_num_out_of_range (d, ord, num_devices);
diff --git a/libgomp/plugin/plugin-gcn.c b/libgomp/plugin/plugin-gcn.c
index cc47d205e1e..4aad9532183 100644
--- a/libgomp/plugin/plugin-gcn.c
+++ b/libgomp/plugin/plugin-gcn.c
@@ -3646,61 +3646,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.  */
-
-/* Return TRUE if the GPU is an APU, i.e. the GPU is integrated with the CPU
-   such that both use the same memory controller such that mapping or memory
-   migration is pointless.  If CHECK_XNACK is TRUE, it additionally requires
-   that the GPU has *no* XNACK support otherwise FALSE is returned.
-
-   In theory, enabling unified-shared memory for APUs should always work,
-   however, with AMD GPUs some APUs (e.g. MI300A) still require XNACK to be
-   enabled as it is required to handle page faults.
-
-   Thus, for unified-shared memory access, either of the following must hold:
-   * HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT is TRUE
-     This implies that all GPUs support USM access, either directly (as APU)
-     or via page migration.  For MI300A, this is only the case if
-     HSA_AMD_SYSTEM_INFO_XNACK_ENABLED is TRUE.
-   * If the GPU an APU *and* it does not support XNACK.  */
-
-static bool
-is_integrated_apu (struct agent_info *agent, bool check_xnack)
-{
-  enum {
-    HSACO_ATTR_UNSUPPORTED,
-    HSACO_ATTR_OFF,
-    HSACO_ATTR_ON,
-    HSACO_ATTR_ANY,
-    HSACO_ATTR_DEFAULT
-  };
-
-  bool is_apu;
-  uint8_t mem_prop[8];
-  hsa_status_t status;
-
-  status = hsa_fns.hsa_agent_get_info_fn (
-	     agent->id, (hsa_agent_info_t) HSA_AMD_AGENT_INFO_MEMORY_PROPERTIES,
-	     mem_prop);
-  _Static_assert (HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU < 8,
-		  "HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU < 8");
-  is_apu = (status == HSA_STATUS_SUCCESS
-	    && (mem_prop[0] & (1 << HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU)));
-
-  if (check_xnack)
-    switch(agent->device_isa)
-      {
-#define GCN_DEVICE(name, NAME, ELF, ISA, XNACK, ...) \
-      case ELF: return is_apu && (XNACK == HSACO_ATTR_UNSUPPORTED);
-#include "../../gcc/config/gcn/gcn-devices.def"
-      default: return false;  /* Just to be save.  */
-      }
-  return is_apu;
-}
-#endif
-
 /* Return the name of the accelerator, which is "gcn".  */
 
 const char *
@@ -3804,9 +3749,113 @@ GOMP_OFFLOAD_supported_threads_dim (int ord, int dim)
 unsigned int
 GOMP_OFFLOAD_get_caps (void)
 {
-  /* FIXME: Enable shared memory for APU, but not discrete GPU.  */
-  return /*GOMP_OFFLOAD_CAP_SHARED_MEM |*/ GOMP_OFFLOAD_CAP_OPENMP_400
-	    | GOMP_OFFLOAD_CAP_OPENACC_200;
+  return (GOMP_OFFLOAD_CAP_OPENMP_400 | GOMP_OFFLOAD_CAP_OPENACC_200
+	  | GOMP_OFFLOAD_CAP_UNIFIED_ADDR | GOMP_OFFLOAD_CAP_REV_OFFLOAD);
+}
+
+/* Return any additional capabilities that are specific to the specified
+   device.  Currently returns:
+   * GOMP_OFFLOAD_CAP_SHARED_MEM
+       when USM is supported
+   * GOMP_OFFLOAD_CAP_APU_SHARED_MEM
+       when USM is supported and the system is an APU. This assumes that the
+       same memory controler is used.
+       NOTE: To avoid potential issues with swapped-out memory, this is not
+       set if XNACK is not supported */
+
+unsigned int
+GOMP_OFFLOAD_get_dev_caps (int n)
+{
+  unsigned int caps = 0;
+
+  /* The device agents have been enumerated, but might not have been
+     initialized, so get_agent_info won't work here.  */
+  struct agent_info *agent = &hsa_context.agents[n];
+
+  /* First, check for general USM support - this is system wide, i.e.
+     implies that all devices support it.  */
+  bool b;
+  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, &b);
+  if (status != HSA_STATUS_SUCCESS)
+    {
+      GOMP_PLUGIN_error ("HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT failed");
+      return 0;
+    }
+
+  if (b)
+    caps |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+
+  /* Next check whether the device is an APU.  */
+  bool is_apu;
+  uint8_t mem_prop[8];
+
+  status = hsa_fns.hsa_agent_get_info_fn (
+	     agent->id, (hsa_agent_info_t) HSA_AMD_AGENT_INFO_MEMORY_PROPERTIES,
+	     mem_prop);
+  _Static_assert (HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU < 8,
+		  "HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU < 8");
+  is_apu = (status == HSA_STATUS_SUCCESS
+	    && (mem_prop[0] & (1 << HSA_AMD_MEMORY_PROPERTY_AGENT_IS_APU)));
+
+  /* If it is an APU:
+     - set GOMP_OFFLOAD_CAP_SHARED_MEM, unless the support is known
+       to be broken.
+     - Set GOMP_OFFLOAD_CAP_APU_SHARED_MEM to state that it might make
+       sense to enable USM automatically.  */
+  if (is_apu)
+    {
+      char name[64];
+      status = hsa_fns.hsa_agent_get_info_fn (agent->id, HSA_AGENT_INFO_NAME,
+					      &name);
+      if (status != HSA_STATUS_SUCCESS)
+	return 0;
+      gcn_isa device_isa = isa_code (name);
+
+      /* Known not to work (well): MI100 and the gfx902 APU. */
+      if (device_isa == EF_AMDGPU_MACH_AMDGCN_GFX908
+	  || device_isa == EF_AMDGPU_MACH_AMDGCN_GFX902)
+	return 0;
+
+      /* Obtain XNACK status.  */
+      enum {
+	HSACO_ATTR_UNSUPPORTED,
+	HSACO_ATTR_OFF,
+	HSACO_ATTR_ON,
+	HSACO_ATTR_ANY,
+	HSACO_ATTR_DEFAULT
+      };
+
+      int xnack;
+      switch(device_isa)
+	{
+#define GCN_DEVICE(name, NAME, ELF, ISA, XNACK, ...) \
+	case ELF: xnack = XNACK;
+#include "../../gcc/config/gcn/gcn-devices.def"
+	default: return 0;  /* Just to be save.  */
+	}
+
+      /* USM requires either enabled XNACK support or XNACK being
+	 unsupported.  For GOMP_OFFLOAD_CAP_APU_SHARED_MEM be prudent and
+	 require XNACK support in addition as this ensures it also works
+	 with swapped-out memory.  */
+      if (caps && xnack != HSACO_ATTR_UNSUPPORTED)
+	caps |= GOMP_OFFLOAD_CAP_APU_SHARED_MEM;
+      else if (xnack == HSACO_ATTR_UNSUPPORTED)
+	caps |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+      else
+	{
+	  type = HSA_AMD_SYSTEM_INFO_XNACK_ENABLED;
+	  status = hsa_fns.hsa_system_get_info_fn (type, &b);
+	  if (status != HSA_STATUS_SUCCESS)
+	    return 0;
+	  if (b)
+	    caps |= (GOMP_OFFLOAD_CAP_SHARED_MEM
+		     | GOMP_OFFLOAD_CAP_APU_SHARED_MEM);
+	}
+    }
+
+  return caps;
 }
 
 /* Identify as GCN accelerator.  */
@@ -3829,36 +3878,10 @@ GOMP_OFFLOAD_version (void)
 /* Return the number of GCN devices on the system.  */
 
 int
-GOMP_OFFLOAD_get_num_devices (unsigned int omp_requires_mask)
+GOMP_OFFLOAD_get_num_devices ()
 {
   if (!init_hsa_context (true))
     exit (EXIT_FAILURE);
-  /* Return -1 if no omp_requires_mask cannot be fulfilled but
-     devices were present.  */
-  if (hsa_context.agent_count > 0
-      && ((omp_requires_mask
-	   & ~(GOMP_REQUIRES_UNIFIED_ADDRESS
-	       | GOMP_REQUIRES_UNIFIED_SHARED_MEMORY
-	       | GOMP_REQUIRES_SELF_MAPS
-	       | GOMP_REQUIRES_REVERSE_OFFLOAD)) != 0))
-    return -1;
-  /* Check whether host page access is supported; this is per system level
-     (all GPUs supported by HSA).  While intrinsically true for APUs, it
-     requires XNACK support for discrete GPUs.  */
-  if (hsa_context.agent_count > 0
-      && (omp_requires_mask
-	  & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY | GOMP_REQUIRES_SELF_MAPS)))
-    {
-      bool b;
-      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, &b);
-      if (status != HSA_STATUS_SUCCESS)
-	GOMP_PLUGIN_error ("HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT "
-			   "failed");
-      if (!b)
-	return -1;
-    }
-
   return hsa_context.agent_count;
 }
 
diff --git a/libgomp/plugin/plugin-nvptx.c b/libgomp/plugin/plugin-nvptx.c
index c85ff89002e..b17d549f9c7 100644
--- a/libgomp/plugin/plugin-nvptx.c
+++ b/libgomp/plugin/plugin-nvptx.c
@@ -354,7 +354,7 @@ struct ptx_device
 
 static struct ptx_device **ptx_devices;
 
-static bool using_usm = false;
+static int using_usm = -1;
 
 /* "Native" GPU thread stack size.  */
 static unsigned native_gpu_thread_stack_size = 0;
@@ -1431,7 +1431,59 @@ GOMP_OFFLOAD_supported_threads_dim (int ord, int dim)
 unsigned int
 GOMP_OFFLOAD_get_caps (void)
 {
-  return GOMP_OFFLOAD_CAP_OPENACC_200 | GOMP_OFFLOAD_CAP_OPENMP_400;
+  /* For unified-shared address: see comment in
+     nvptx_open_device for CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING.  */
+
+  return (GOMP_OFFLOAD_CAP_OPENMP_400 | GOMP_OFFLOAD_CAP_OPENACC_200
+	  | GOMP_OFFLOAD_CAP_UNIFIED_ADDR | GOMP_OFFLOAD_CAP_REV_OFFLOAD);
+}
+
+/* Return any additional capabilities that are specific to the specified
+   device.  Currently returns:
+   * GOMP_OFFLOAD_CAP_SHARED_MEM
+       when USM is supported
+   * GOMP_OFFLOAD_CAP_APU_SHARED_MEM
+       when USM is supported and CPU and GPU are integrated using the
+       same memory controller. As of July 2026 no such Nvidia GPU seems
+       to exist.  */
+
+unsigned int
+GOMP_OFFLOAD_get_dev_caps (int n)
+{
+  unsigned int caps = 0;
+
+  /* Check for USM.  */
+
+  int pi;
+  CUresult r;
+  r = CUDA_CALL_NOCHECK (cuDeviceGetAttribute, &pi,
+			 CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS,
+			 n);
+  if (r != CUDA_SUCCESS)
+    return 0;
+
+  if (pi)
+    {
+      caps |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+      if (using_usm == -1)
+	using_usm = true;
+    }
+  else
+    using_usm = false;
+
+#if 0
+  int pi;
+  CUresult r;
+  r = CUDA_CALL_NOCHECK (cuDeviceGetAttribute, &pi,
+			 CU_DEVICE_ATTRIBUTE_INTEGRATED, n);
+  if (r == CUDA_SUCCESS && pi != 0)
+    {
+      caps |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+      caps |= GOMP_OFFLOAD_CAP_APU_SHARED_MEM;
+    }
+#endif
+
+  return caps;
 }
 
 int
@@ -1441,40 +1493,9 @@ GOMP_OFFLOAD_get_type (void)
 }
 
 int
-GOMP_OFFLOAD_get_num_devices (unsigned int omp_requires_mask)
+GOMP_OFFLOAD_get_num_devices ()
 {
-  int num_devices = nvptx_get_num_devices ();
-  /* Return -1 if no omp_requires_mask cannot be fulfilled but
-     devices were present.  Unified-shared address: see comment in
-     nvptx_open_device for CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING.  */
-  if (num_devices > 0
-      && ((omp_requires_mask
-	   & ~(GOMP_REQUIRES_UNIFIED_ADDRESS
-	       | GOMP_REQUIRES_SELF_MAPS
-	       | GOMP_REQUIRES_UNIFIED_SHARED_MEMORY
-	       | GOMP_REQUIRES_REVERSE_OFFLOAD)) != 0))
-    return -1;
-  /* Check whether host page access (direct or via migration) is supported;
-     if so, enable USM.  Currently, capabilities is per device type, hence,
-     check all devices.  */
-  if (num_devices > 0
-      && (omp_requires_mask
-	  & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY | GOMP_REQUIRES_SELF_MAPS)))
-    {
-      for (int dev = 0; dev < num_devices; dev++)
-	{
-	  int pi;
-	  CUresult r;
-	  r = CUDA_CALL_NOCHECK (cuDeviceGetAttribute, &pi,
-				 CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS,
-				 dev);
-	  if (r != CUDA_SUCCESS || pi == 0)
-	    return -1;
-	}
-
-      using_usm = true;
-    }
-  return num_devices;
+  return nvptx_get_num_devices ();
 }
 
 bool
@@ -2032,11 +2053,12 @@ int
 GOMP_OFFLOAD_is_accessible_ptr (int ord,
 				const void *ptr, size_t size)
 {
+  struct ptx_device *ptx_dev = ptx_devices[ord];
+
   /* USM implies access.  */
-  if (using_usm)
+  if (using_usm > 0)
     return 1;
 
-  struct ptx_device *ptx_dev = ptx_devices[ord];
   CUcontext old_ctx;
   CUDA_CALL_ERET (false, cuCtxPushCurrent, ptx_dev->ctx);
 
diff --git a/libgomp/target.c b/libgomp/target.c
index 0f509152120..637601dc816 100644
--- a/libgomp/target.c
+++ b/libgomp/target.c
@@ -6296,6 +6296,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_OPT (get_dev_caps, get_dev_caps);
   DLSYM (get_type);
   DLSYM (get_num_devices);
   DLSYM (init_device);
@@ -6463,38 +6464,10 @@ gomp_target_init (void)
 
 	if (gomp_load_plugin_for_device (&current_device, plugin_name))
 	  {
-	    int omp_req = omp_requires_mask & ~GOMP_REQUIRES_TARGET_USED;
-	    new_num_devs = current_device.get_num_devices_func (omp_req);
-	    if (gomp_debug_var > 0 && new_num_devs < 0)
-	      {
-		bool found = false;
-		int type = current_device.get_type_func ();
-		for (int img = 0; img < num_offload_images; img++)
-		  if (type == offload_images[img].type)
-		    found = true;
-		if (found)
-		  {
-		    char buf[GOMP_REQUIRES_NAME_BUF_LEN];
-		    gomp_requires_to_name (buf, sizeof (buf), omp_req);
-		    char *name = (char *) malloc (cur_len + 1);
-		    memcpy (name, cur, cur_len);
-		    name[cur_len] = '\0';
-		    gomp_debug (1,
-				"%s devices present but 'omp requires %s' "
-				"cannot be fulfilled\n", name, buf);
-		    free (name);
-		  }
-	      }
-	    else if (new_num_devs >= 1)
+	    new_num_devs = current_device.get_num_devices_func ();
+	    if (new_num_devs >= 1)
 	      {
 		/* Augment DEVICES and NUM_DEVICES.  */
-
-		/* If USM has been requested and is supported by all devices
-		   of this type, set the capability accordingly.  */
-		if (omp_requires_mask
-		    & (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)
 				      * sizeof (struct gomp_device_descr));
 		if (!devs)
@@ -6512,13 +6485,59 @@ gomp_target_init (void)
 		current_device.mem_map.root = NULL;
 		current_device.mem_map_rev.root = NULL;
 		current_device.state = GOMP_DEVICE_UNINITIALIZED;
+		int saved_num_devs = num_devs;
 		for (i = 0; i < new_num_devs; i++)
 		  {
 		    current_device.target_id = i;
 		    devs[num_devs] = current_device;
 		    gomp_mutex_init (&devs[num_devs].lock);
+
+		    /* Skip the device (= remove from available devices)
+		       if a requirement cannot be fulfilled.
+		       For USM/self_maps, set SHARED_MEM capability for the
+		       device.  */
+		    int dev_caps = current_device.capabilities;
+		    if (current_device.get_dev_caps_func)
+		      dev_caps |= current_device.get_dev_caps_func (i);
+
+		    if (((omp_requires_mask & GOMP_REQUIRES_UNIFIED_ADDRESS)
+			 && !(dev_caps & GOMP_OFFLOAD_CAP_UNIFIED_ADDR))
+			|| ((omp_requires_mask & GOMP_REQUIRES_REVERSE_OFFLOAD)
+			    && !(dev_caps & GOMP_REQUIRES_REVERSE_OFFLOAD)))
+		      continue;
+
+		    if (omp_requires_mask & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY
+					     | GOMP_REQUIRES_SELF_MAPS))
+		      {
+			if (dev_caps & GOMP_OFFLOAD_CAP_SHARED_MEM)
+			  devs[num_devs].capabilities
+			    |= GOMP_OFFLOAD_CAP_SHARED_MEM;
+			else
+			  continue;
+		      }
 		    num_devs++;
 		  }
+		if (saved_num_devs == num_devs)
+		  {
+		    bool found = false;
+		    for (int img = 0; img < num_offload_images; img++)
+		      if (current_device.type == offload_images[img].type)
+			found = true;
+		    if (found)
+		      {
+			char buf[GOMP_REQUIRES_NAME_BUF_LEN];
+			gomp_requires_to_name (buf, sizeof (buf),
+					       omp_requires_mask);
+			char *name = (char *) malloc (cur_len + 1);
+			memcpy (name, cur, cur_len);
+			name[cur_len] = '\0';
+			gomp_debug (1,
+				    "%d %s devices present but 'omp requires "
+				    "%s' cannot be fulfilled\n",
+				    new_num_devs, name, buf);
+			free (name);
+		      }
+		  }
 	      }
 	  }
 

Reply via email to