libgomp/plugin: Add initial interop support to nvptx + gcn

Message ID a4aee05b-269a-45fa-9bfc-98e4a1890b03@baylibre.com
State New
Headers
Series libgomp/plugin: Add initial interop support to nvptx + gcn |

Commit Message

Tobias Burnus March 10, 2025, 8:48 p.m. UTC
  This patch requires the to be submitted GOMP_interop patch, which
handles the generic libgomp parts. But once it is available, this
patch adds support for the foreign runtimes cuda/cuda_driver/hip
for nvptx and hip/hsa for gcn.

The patch is based on my old RFC patch
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661207.html
with minor updates and tons of bugs fixed.

The patch has been lightly tested by the included test case on
nvptx and gcn – and, for nvptx, using the example from the OpenMP
example document.

I intent to commit this patch relatively soonish after the
libgomp patch has been committed, but I expect that more extensive
testing (+ adding more tests to libgomp/testsuite) will find bugs
that have to be fixed.

Comments, remarks, suggestions regarding this patch or my plans?

Tobias

PS: I surely will re-run the tests once the libgomp patch is in
and before I commit this patch.

PPS: Besides tests, one follow up task will be documentation;
cf. old draft patch at
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661365.html

Post-post-post script: We could think of making some of the features
tunable (e.g. queue size). That could be easily added via the 'attr'
feature (without any compatibility issues). However, currently we
don't and I didn't see any of evaluated importance. (Comments welcome.)
  

Comments

Andrew Stubbs March 11, 2025, 9:41 a.m. UTC | #1
On 10/03/2025 21:48, Tobias Burnus wrote:
> This patch requires the to be submitted GOMP_interop patch, which
> handles the generic libgomp parts. But once it is available, this
> patch adds support for the foreign runtimes cuda/cuda_driver/hip
> for nvptx and hip/hsa for gcn.
> 
> The patch is based on my old RFC patch
> https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661207.html
> with minor updates and tons of bugs fixed.
> 
> The patch has been lightly tested by the included test case on
> nvptx and gcn – and, for nvptx, using the example from the OpenMP
> example document.
> 
> I intent to commit this patch relatively soonish after the
> libgomp patch has been committed, but I expect that more extensive
> testing (+ adding more tests to libgomp/testsuite) will find bugs
> that have to be fixed.
> 
> Comments, remarks, suggestions regarding this patch or my plans?

It needs some user documentation (what's implemented/not, fallback 
cases, queue sizes/modes, etc.), but otherwise it looks reasonable to me.

Andrew

> 
> Tobias
> 
> PS: I surely will re-run the tests once the libgomp patch is in
> and before I commit this patch.
> 
> PPS: Besides tests, one follow up task will be documentation;
> cf. old draft patch at
> https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661365.html
> 
> Post-post-post script: We could think of making some of the features
> tunable (e.g. queue size). That could be easily added via the 'attr'
> feature (without any compatibility issues). However, currently we
> don't and I didn't see any of evaluated importance. (Comments welcome.)
  
Tobias Burnus March 21, 2025, 8:47 p.m. UTC | #2
This patch has now been committed as r15-8657-g41b9c3b848c8cb with two 
changes:

* In interop-fr-1.c, one assert did not properly handle the case -99 (= 
use default device) in the is-returned-device-number-correct check.

(Or actually two: one for gcn and one identical one for nvptx.)

* It fixes libgomp.c-c++-common/get-mapped-ptr-1.c - That's actually a 
missing change of the commit r15-8654-g99e2906ae255fc on which this 
patch depended (OpenMP: 'interop' construct - add ME support + 
target-independent libgomp).

(The problem is that -5 was non-conforming device number but now it 
denotes omp_default_device; hence, the test now uses -6 as an invalid 
device number.)

Tobias

PS: One follow up patch will adding documentation for this, based on my 
old patch
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661365.html
  

Patch

libgomp/plugin: Add initial interop support to nvptx + gcn

The interop directive operates on an opaque object that represents a
foreign runtime. This commit adds support for
this to the two offloading plugins.

For nvptx, it supports cuda, cuda_driver and hip; the latter is AMD's
version of CUDA which for Nvidia devices boils down to normal CUDA.
Thus, at the end for this limited use, cuda/cuda_driver/hip are all
the same - and for plugin-nvptx.c, the they differ only in terms of
what gets fr_id, fr_name and get_interop_type_desc return.

For gcn, it supports hip and hsa.

libgomp/ChangeLog:

	* plugin/plugin-gcn.c (_LIBGOMP_PLUGIN_INCLUDE): Define.
	(struct hsa_runtime_fn_info): Add two queue functions.
	(hipError_t, hipCtx_t, hipStream_s, hipStream_t): New types.
	(struct hip_runtime_fn_info): New.
	(hip_runtime_lib, hip_fns): New global vars.
	(init_environment_variables): Handle hip_runtime_lib.
	(init_hsa_runtime_functions): Load the two queue functions.
	(init_hip_runtime_functions, GOMP_OFFLOAD_interop,
	GOMP_OFFLOAD_get_interop_int, GOMP_OFFLOAD_get_interop_ptr,
	GOMP_OFFLOAD_get_interop_str,
	GOMP_OFFLOAD_get_interop_type_desc): New.
	* plugin/plugin-nvptx.c (_LIBGOMP_PLUGIN_INCLUDE): Define.
	(GOMP_OFFLOAD_interop, GOMP_OFFLOAD_get_interop_int,
	GOMP_OFFLOAD_get_interop_ptr, GOMP_OFFLOAD_get_interop_str,
	GOMP_OFFLOAD_get_interop_type_desc): New.
	* testsuite/libgomp.c/interop-fr-1.c: New test.

 libgomp/plugin/plugin-gcn.c                | 464 ++++++++++++++++++++++-
 libgomp/plugin/plugin-nvptx.c              | 302 +++++++++++++++
 libgomp/testsuite/libgomp.c/interop-fr-1.c | 571 +++++++++++++++++++++++++++++
 3 files changed, 1335 insertions(+), 2 deletions(-)

diff --git a/libgomp/plugin/plugin-gcn.c b/libgomp/plugin/plugin-gcn.c
index 5c65778191a..4b42a597cbd 100644
--- a/libgomp/plugin/plugin-gcn.c
+++ b/libgomp/plugin/plugin-gcn.c
@@ -41,7 +41,9 @@ 
 #include <hsa_ext_amd.h>
 #include <dlfcn.h>
 #include <signal.h>
+#define _LIBGOMP_PLUGIN_INCLUDE 1
 #include "libgomp-plugin.h"
+#undef _LIBGOMP_PLUGIN_INCLUDE
 #include "config/gcn/libgomp-gcn.h"  /* For struct output.  */
 #include "gomp-constants.h"
 #include <elf.h>
@@ -190,6 +192,8 @@  struct hsa_runtime_fn_info
   uint64_t (*hsa_queue_add_write_index_release_fn) (const hsa_queue_t *queue,
 						    uint64_t value);
   uint64_t (*hsa_queue_load_read_index_acquire_fn) (const hsa_queue_t *queue);
+  uint64_t (*hsa_queue_load_read_index_relaxed_fn) (const hsa_queue_t *queue);
+  uint64_t (*hsa_queue_load_write_index_relaxed_fn) (const hsa_queue_t *queue);
   void (*hsa_signal_store_relaxed_fn) (hsa_signal_t signal,
 				       hsa_signal_value_t value);
   void (*hsa_signal_store_release_fn) (hsa_signal_t signal,
@@ -216,6 +220,25 @@  struct hsa_runtime_fn_info
      const hsa_signal_t *dep_signals, hsa_signal_t completion_signal);
 };
 
+/* As an HIP runtime is dlopened, following structure defines function
+   pointers utilized by the interop feature of this plugin.
+   Add suffient type declarations to get this work.  */
+
+typedef int hipError_t;  /* Actually an enum; 0 == success. */
+typedef void* hipCtx_t;
+struct hipStream_s;
+typedef struct hipStream_s* hipStream_t;
+
+struct hip_runtime_fn_info
+{
+  hipError_t (*hipStreamCreate_fn) (hipStream_t *);
+  hipError_t (*hipStreamDestroy_fn) (hipStream_t);
+  hipError_t (*hipStreamSynchronize_fn) (hipStream_t);
+  hipError_t (*hipCtxGetCurrent_fn) (hipCtx_t *ctx);
+  hipError_t (*hipSetDevice_fn) (int deviceId);
+  hipError_t (*hipGetDevice_fn) (int *deviceId);
+};
+
 /* Structure describing the run-time and grid properties of an HSA kernel
    lauch.  This needs to match the format passed to GOMP_OFFLOAD_run.  */
 
@@ -553,9 +576,11 @@  struct hsa_context_info
 static struct hsa_context_info hsa_context;
 
 /* HSA runtime functions that are initialized in init_hsa_context.  */
-
 static struct hsa_runtime_fn_info hsa_fns;
 
+/* HIP runtime functions that are initialized in init_hip_runtime_functions.  */
+static struct hip_runtime_fn_info hip_fns;
+
 /* Heap space, allocated target-side, provided for use of newlib malloc.
    Each module should have it's own heap allocated.
    Beware that heap usage increases with OpenMP teams.  See also arenas.  */
@@ -578,10 +603,11 @@  static bool debug;
 
 static bool suppress_host_fallback;
 
-/* Flag to locate HSA runtime shared library that is dlopened
+/* Flag to locate HSA and HIP runtime shared libraries that are dlopened
    by this plug-in.  */
 
 static const char *hsa_runtime_lib;
+static const char *hip_runtime_lib;
 
 /* Flag to decide if the runtime should support also CPU devices (can be
    a simulator).  */
@@ -1068,6 +1094,10 @@  init_environment_variables (void)
   if (hsa_runtime_lib == NULL)
     hsa_runtime_lib = "libhsa-runtime64.so.1";
 
+  hip_runtime_lib = secure_getenv ("HIP_RUNTIME_LIB");
+  if (hip_runtime_lib == NULL)
+    hip_runtime_lib = "libamdhip64.so";
+
   support_cpu_devices = secure_getenv ("GCN_SUPPORT_CPU_DEVICES");
 
   const char *x = secure_getenv ("GCN_NUM_TEAMS");
@@ -1418,6 +1448,8 @@  init_hsa_runtime_functions (void)
   DLSYM_FN (hsa_executable_iterate_symbols)
   DLSYM_FN (hsa_queue_add_write_index_release)
   DLSYM_FN (hsa_queue_load_read_index_acquire)
+  DLSYM_FN (hsa_queue_load_read_index_relaxed)
+  DLSYM_FN (hsa_queue_load_write_index_relaxed)
   DLSYM_FN (hsa_signal_wait_acquire)
   DLSYM_FN (hsa_signal_store_relaxed)
   DLSYM_FN (hsa_signal_store_release)
@@ -4365,6 +4397,434 @@  unlock:
   return retval;
 }
 
+
+static bool
+init_hip_runtime_functions (void)
+{
+  bool inited = false;
+  if (inited)
+    return hip_fns.hipStreamCreate_fn != NULL;
+  inited = true;
+
+  void *handle = dlopen (hip_runtime_lib, RTLD_LAZY);
+  if (handle == NULL)
+    return false;
+
+#define DLSYM_OPT_FN(function) \
+  hip_fns.function##_fn = dlsym (handle, #function)
+
+  DLSYM_OPT_FN (hipStreamCreate);
+  DLSYM_OPT_FN (hipStreamDestroy);
+  DLSYM_OPT_FN (hipStreamSynchronize);
+  DLSYM_OPT_FN (hipCtxGetCurrent);
+  DLSYM_OPT_FN (hipGetDevice);
+  DLSYM_OPT_FN (hipSetDevice);
+#undef DLSYM_OPT_FN
+
+  if (!hip_fns.hipStreamCreate_fn
+      || !hip_fns.hipStreamDestroy_fn
+      || !hip_fns.hipStreamSynchronize_fn
+      || !hip_fns.hipCtxGetCurrent_fn
+      || !hip_fns.hipGetDevice_fn
+      || !hip_fns.hipSetDevice_fn)
+    {
+      hip_fns.hipStreamCreate_fn = NULL;
+      return false;
+    }
+
+  return true;
+}
+
+
+void
+GOMP_OFFLOAD_interop (struct interop_obj_t *obj, int ord,
+		      enum gomp_interop_flag action, bool targetsync,
+		      const char *prefer_type)
+{
+  if ((action == gomp_interop_flag_destroy || action == gomp_interop_flag_use)
+      && !obj->stream)
+    return;
+  if ((action == gomp_interop_flag_destroy || action == gomp_interop_flag_use)
+      && obj->fr == omp_ifr_hsa)
+    {
+      /* Wait until the queue is is empty.   */
+      bool is_empty;
+      uint64_t read_index, write_index;
+      hsa_queue_t *queue = (hsa_queue_t *) obj->stream;
+      do
+	{
+	  read_index = hsa_fns.hsa_queue_load_read_index_relaxed_fn (queue);
+	  write_index = hsa_fns.hsa_queue_load_write_index_relaxed_fn (queue);
+	  is_empty = (read_index == write_index);
+	}
+      while (!is_empty);
+
+      if (action == gomp_interop_flag_destroy)
+	{
+	  hsa_status_t status = hsa_fns.hsa_queue_destroy_fn (queue);
+	  if (status != HSA_STATUS_SUCCESS)
+	    hsa_fatal ("Error destroying interop hsa_queue_t", status);
+	}
+      return;
+    }
+  if (action == gomp_interop_flag_destroy)
+    {
+      hipError_t err = hip_fns.hipStreamDestroy_fn ((hipStream_t) obj->stream);
+      if (err != 0)
+	GOMP_PLUGIN_fatal ("Error destroying interop hipStream_t: %d", err);
+      return;
+    }
+  if (action == gomp_interop_flag_use)
+    {
+      hipError_t err
+	= hip_fns.hipStreamSynchronize_fn ((hipStream_t) obj->stream);
+      if (err != 0)
+	GOMP_PLUGIN_fatal ("Error synchronizing interop hipStream_t: %d", err);
+      return;
+    }
+
+  bool fr_set = false;
+
+  /* Check for the preferred type; cf. parser in C/C++/Fortran or
+     dump_omp_init_prefer_type for the format.
+     Accept the first '{...}' block that specifies a 'fr' that we support.
+     Currently, no 'attr(...)' are supported.  */
+  if (prefer_type)
+    while (prefer_type[0] == (char) GOMP_INTEROP_IFR_SEPARATOR)
+      {
+	/* '{' item block starts.  */
+	prefer_type++;
+	/* 'fr(...)' block  */
+	while (prefer_type[0] != (char) GOMP_INTEROP_IFR_SEPARATOR)
+	  {
+	    omp_interop_fr_t fr = (omp_interop_fr_t) prefer_type[0];
+	    if (fr == omp_ifr_hip)
+	      {
+		obj->fr = omp_ifr_hip;
+		fr_set = true;
+	      }
+	    if (fr == omp_ifr_hsa)
+	      {
+		obj->fr = omp_ifr_hsa;
+		fr_set = true;
+	      }
+	    prefer_type++;
+	  }
+	prefer_type++;
+	/* 'attr(...)' block  */
+	while (prefer_type[0] != '\0')
+	  {
+	    /* const char *attr = prefer_type;  */
+	    prefer_type += strlen (prefer_type) + 1;
+	  }
+	prefer_type++;
+	/* end of '}'.  */
+	if (fr_set)
+	  break;
+      }
+
+  /* Prefer HIP, use HSA as fallback.  The warning is only printed if GCN_DEBUG
+     is set and does not distinguishes between on prefer_type or hip prefer_type
+     nor whether a later/lower preference also specifies 'hsa'.
+     The assumption is that the user code handles HSA gracefully, but likely
+     just by falling back to the host version.  On the other hand, have_hip is
+     likely true if HSA is available.  */
+  if (!fr_set || obj->fr == omp_ifr_hip)
+    {
+      bool have_hip = init_hip_runtime_functions ();
+      if (have_hip)
+	obj->fr = omp_ifr_hip;
+      else
+	{
+	  GCN_WARNING ("interop object requested, using HSA instead of HIP "
+		       "as %s could not be loaded", hip_runtime_lib);
+	  obj->fr = omp_ifr_hsa;
+	}
+    }
+
+  _Static_assert (sizeof (uint64_t) == sizeof (hsa_agent_t),
+		  "sizeof (uint64_t) == sizeof (hsa_agent_t)");
+  struct agent_info *agent = get_agent_info (ord);
+  obj->device_data = agent;
+
+  if (targetsync && obj->fr == omp_ifr_hsa)
+    {
+      hsa_status_t status;
+      /* Queue size must be (for GPUs) a power of 2 >= 40, i.e. at least 64 and
+	 maximally HSA_AGENT_INFO_QUEUE_MAX_SIZE. Arbitrary choice:  */
+      uint32_t queue_size = ASYNC_QUEUE_SIZE;
+      status = hsa_fns.hsa_queue_create_fn (agent->id, queue_size,
+					    HSA_QUEUE_TYPE_MULTI,
+					    NULL, NULL, UINT32_MAX, UINT32_MAX,
+					    (hsa_queue_t **) &obj->stream);
+      if (status != HSA_STATUS_SUCCESS)
+	hsa_fatal ("Error creating interop hsa_queue_t", status);
+    }
+  else if (targetsync)
+    {
+      hipError_t err;
+      int dev_curr;
+      err = hip_fns.hipGetDevice_fn (&dev_curr);
+      if (!err && ord != dev_curr)
+	err = hip_fns.hipSetDevice_fn (ord);
+      if (!err)
+	err = hip_fns.hipStreamCreate_fn ((hipStream_t *) &obj->stream);
+      if (!err && ord != dev_curr)
+	err = hip_fns.hipSetDevice_fn (dev_curr);
+      if (err != 0)
+	GOMP_PLUGIN_fatal ("Error creating interop hipStream_t: %d", err);
+    }
+}
+
+intptr_t
+GOMP_OFFLOAD_get_interop_int (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_hip && obj->fr != omp_ifr_hsa)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->fr;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return 0;
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return 1; /* amd */
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return 0;
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->device_num;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return 0;
+    case omp_ipr_device:
+      if (obj->fr == omp_ifr_hsa)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_type_ptr;
+	  return 0;
+	}
+	if (ret_code)
+	  *ret_code = omp_irc_success;
+	return ((struct agent_info *) obj->device_data)->device_id;
+    case omp_ipr_device_context:
+      if (ret_code && obj->fr == omp_ifr_hsa)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return 0;
+    case omp_ipr_targetsync:
+      if (ret_code && !obj->stream)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return 0;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return 0;
+}
+
+void *
+GOMP_OFFLOAD_get_interop_ptr (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_hip && obj->fr != omp_ifr_hsa)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return NULL;
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return NULL;
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return NULL;
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return NULL;
+    case omp_ipr_device:
+      if (obj->fr == omp_ifr_hsa)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_success;
+	  /* hsa_agent_t is an struct containing a single uint64_t. */
+	  return &((struct agent_info *) obj->device_data)->id;
+	}
+      else
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_type_int;
+	  return NULL;
+	}
+    case omp_ipr_device_context:
+      if (obj->fr == omp_ifr_hsa)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_no_value;
+	  return NULL;
+	}
+      else
+	{
+	  hipCtx_t ctx;
+	  int dev_curr;
+	  int dev = ((struct agent_info *) obj->device_data)->device_id;
+	  hipError_t err;
+	  err = hip_fns.hipGetDevice_fn (&dev_curr);
+	  if (!err && dev != dev_curr)
+	    err = hip_fns.hipSetDevice_fn (dev);
+	  if (!err)
+	    err = hip_fns.hipCtxGetCurrent_fn (&ctx);
+	  if (!err && dev != dev_curr)
+	    err = hip_fns.hipSetDevice_fn (dev_curr);
+	  if (err)
+	    GOMP_PLUGIN_fatal ("Error obtaining hipCtx_t for device %d: %d",
+			       obj->device_num, err);
+	  if (ret_code)
+	    *ret_code = omp_irc_success;
+	  return ctx;
+	}
+    case omp_ipr_targetsync:
+      if (!obj->stream)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_no_value;
+	  return NULL;
+	}
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->stream;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return NULL;
+}
+
+const char *
+GOMP_OFFLOAD_get_interop_str (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_hip && obj->fr != omp_ifr_hsa)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      if (obj->fr == omp_ifr_hip)
+	return "hip";
+      if (obj->fr == omp_ifr_hsa)
+	return "hsa";
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return "amd";
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return NULL;
+    case omp_ipr_device:
+      if (ret_code && obj->fr == omp_ifr_hsa)
+	*ret_code = omp_irc_type_ptr;
+      else if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_device_context:
+      if (ret_code && obj->fr == omp_ifr_hsa)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return NULL;
+    case omp_ipr_targetsync:
+      if (ret_code && !obj->stream)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return NULL;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return 0;
+}
+
+const char *
+GOMP_OFFLOAD_get_interop_type_desc (struct interop_obj_t *obj,
+				    omp_interop_property_t property_id)
+{
+  _Static_assert (omp_ipr_targetsync == omp_ipr_first,
+		  "omp_ipr_targetsync == omp_ipr_first");
+  _Static_assert (omp_ipr_platform - omp_ipr_first + 1 == 4,
+		  "omp_ipr_platform - omp_ipr_first + 1 == 4");
+  static const char *desc_hip[] = {"N/A",		/* platform */
+				   "hipDevice_t",	/* device */
+				   "hipCtx_t",		/* device_context */
+				   "hipStream_t"};	/* targetsync */
+  static const char *desc_hsa[] = {"N/A",		/* platform */
+				   "hsa_agent_t *",	/* device */
+				   "N/A",		/* device_context */
+				   "hsa_queue_t *"};	/* targetsync */
+  if (obj->fr == omp_ifr_hip)
+    return desc_hip[omp_ipr_platform - property_id];
+  else
+    return desc_hsa[omp_ipr_platform - property_id];
+  return NULL;
+}
+
 /* }}}  */
 /* {{{ OpenMP Plugin API  */
 
diff --git a/libgomp/plugin/plugin-nvptx.c b/libgomp/plugin/plugin-nvptx.c
index c47461eeccd..822c6a410e2 100644
--- a/libgomp/plugin/plugin-nvptx.c
+++ b/libgomp/plugin/plugin-nvptx.c
@@ -35,7 +35,9 @@ 
 #include "openacc.h"
 #include "config.h"
 #include "symcat.h"
+#define _LIBGOMP_PLUGIN_INCLUDE 1
 #include "libgomp-plugin.h"
+#undef _LIBGOMP_PLUGIN_INCLUDE
 #include "oacc-plugin.h"
 #include "gomp-constants.h"
 #include "oacc-int.h"
@@ -2425,6 +2427,306 @@  nvptx_stacks_acquire (struct ptx_device *ptx_dev, size_t size, int num)
   return (void *) ptx_dev->omp_stacks.ptr;
 }
 
+void
+GOMP_OFFLOAD_interop (struct interop_obj_t *obj, int ord,
+		      enum gomp_interop_flag action, bool targetsync,
+		      const char *prefer_type)
+{
+  obj->fr = omp_ifr_cuda;
+
+  if (action == gomp_interop_flag_destroy)
+    {
+      if (obj->stream)
+	CUDA_CALL_ASSERT (cuStreamDestroy, obj->stream);
+      return;
+    }
+  if (action == gomp_interop_flag_use)
+    {
+      if (obj->stream)
+	CUDA_CALL_ASSERT (cuStreamSynchronize, obj->stream);
+      return;
+    }
+
+  /* Check for the preferred type; cf. parser in C/C++/Fortran or
+     dump_omp_init_prefer_type for the format.
+     Accept the first '{...}' block that specifies a 'fr' that we support.
+     Currently, no 'attr(...)' are supported.  */
+  if (prefer_type)
+    while (prefer_type[0] == (char) GOMP_INTEROP_IFR_SEPARATOR)
+      {
+	bool found = false;
+	/* '{' item block starts.  */
+	prefer_type++;
+	/* 'fr(...)' block  */
+	while (prefer_type[0] != (char) GOMP_INTEROP_IFR_SEPARATOR)
+	  {
+	    omp_interop_fr_t fr = (omp_interop_fr_t) prefer_type[0];
+	    if (fr == omp_ifr_cuda
+		|| fr == omp_ifr_cuda_driver
+		|| fr == omp_ifr_hip)
+	      {
+		obj->fr = fr;
+		found = true;
+	      }
+	    prefer_type++;
+	  }
+	prefer_type++;
+	/* 'attr(...)' block  */
+	while (prefer_type[0] != '\0')
+	  {
+	    /* const char *attr = prefer_type;  */
+	    prefer_type += strlen (prefer_type) + 1;
+	  }
+	prefer_type++;
+	/* end of '}'.  */
+	if (found)
+	  break;
+      }
+
+  obj->device_data = ptx_devices[ord];
+
+  if (targetsync)
+    {
+      CUstream stream = NULL;
+      CUDA_CALL_ASSERT (cuStreamCreate, &stream, CU_STREAM_DEFAULT);
+      obj->stream = stream;
+    }
+}
+
+
+intptr_t
+GOMP_OFFLOAD_get_interop_int (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_cuda
+      && obj->fr != omp_ifr_cuda_driver
+      && obj->fr != omp_ifr_hip)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->fr;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return 0;
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return 11; /* nvidia */
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return 0;
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->device_num;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return 0;
+    case omp_ipr_device:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return ((struct ptx_device *) obj->device_data)->dev;
+    case omp_ipr_device_context:
+      if (ret_code && obj->fr == omp_ifr_cuda)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return 0;
+    case omp_ipr_targetsync:
+      if (!obj->stream)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_no_value;
+	  return 0;
+	}
+      /* ptr fits into (u)intptr_t */
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return (uintptr_t) obj->stream;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return 0;
+}
+
+void *
+GOMP_OFFLOAD_get_interop_ptr (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_cuda
+      && obj->fr != omp_ifr_cuda_driver
+      && obj->fr != omp_ifr_hip)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return NULL;
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_type_str;
+      return NULL;
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return NULL;
+    case omp_ipr_device:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_device_context:
+      if (obj->fr == omp_ifr_cuda)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_no_value;
+	  return NULL;
+	}
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return ((struct ptx_device *) obj->device_data)->ctx;
+    case omp_ipr_targetsync:
+      if (!obj->stream)
+	{
+	  if (ret_code)
+	    *ret_code = omp_irc_no_value;
+	  return NULL;
+	}
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return obj->stream;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return NULL;
+}
+
+const char *
+GOMP_OFFLOAD_get_interop_str (struct interop_obj_t *obj,
+			      omp_interop_property_t property_id,
+			      omp_interop_rc_t *ret_code)
+{
+  if (obj->fr != omp_ifr_cuda
+      && obj->fr != omp_ifr_cuda_driver
+      && obj->fr != omp_ifr_hip)
+    {
+      if (ret_code)
+	*ret_code = omp_irc_no_value;  /* Hmm. */
+      return 0;
+    }
+  switch (property_id)
+    {
+    case omp_ipr_fr_id:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_fr_name:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      if (obj->fr == omp_ifr_cuda)
+	return "cuda";
+      if (obj->fr == omp_ifr_cuda_driver)
+	return "cuda_driver";
+      if (obj->fr == omp_ifr_hip)
+	return "hip";
+      break;
+    case omp_ipr_vendor:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_vendor_name:
+      if (ret_code)
+	*ret_code = omp_irc_success;
+      return "nvidia";
+    case omp_ipr_device_num:
+      if (ret_code)
+	*ret_code = omp_irc_type_int;
+      return NULL;
+    case omp_ipr_platform:
+      if (ret_code)
+	*ret_code = omp_irc_no_value;
+      return NULL;
+    case omp_ipr_device:
+      if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return NULL;
+    case omp_ipr_device_context:
+      if (ret_code && obj->fr == omp_ifr_cuda)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return NULL;
+    case omp_ipr_targetsync:
+      if (ret_code && !obj->stream)
+	*ret_code = omp_irc_no_value;
+      else if (ret_code)
+	*ret_code = omp_irc_type_ptr;
+      return NULL;
+    default:
+      break;
+    }
+  __builtin_unreachable ();
+  return NULL;
+}
+
+const char *
+GOMP_OFFLOAD_get_interop_type_desc (struct interop_obj_t *obj,
+				    omp_interop_property_t property_id)
+{
+  _Static_assert (omp_ipr_targetsync == omp_ipr_first,
+		  "omp_ipr_targetsync == omp_ipr_first");
+  _Static_assert (omp_ipr_platform - omp_ipr_first + 1 == 4,
+		  "omp_ipr_platform - omp_ipr_first + 1 == 4");
+  static const char *desc_cuda[] = {"N/A",		/* platform */
+				    "int",		/* device */
+				    "N/A",		/* device_context */
+				    "cudaStream_t"};	/* targetsync */
+  static const char *desc_cuda_driver[] = {"N/A",	/* platform */
+					   "CUdevice",	/* device */
+					   "CUcontext",	/* device_context */
+					   "CUstream"};	/* targetsync */
+  static const char *desc_hip[] = {"N/A",		/* platform */
+				   "hipDevice_t",	/* device */
+				   "hipCtx_t",		/* device_context */
+				   "hipStream_t"};	/* targetsync */
+  if (obj->fr == omp_ifr_cuda)
+    return desc_cuda[omp_ipr_platform - property_id];
+  if (obj->fr == omp_ifr_cuda_driver)
+    return desc_cuda_driver[omp_ipr_platform - property_id];
+  else
+    return desc_hip[omp_ipr_platform - property_id];
+  return NULL;
+}
 
 void
 GOMP_OFFLOAD_run (int ord, void *tgt_fn, void *tgt_vars, void **args)
diff --git a/libgomp/testsuite/libgomp.c/interop-fr-1.c b/libgomp/testsuite/libgomp.c/interop-fr-1.c
new file mode 100644
index 00000000000..02c29656e40
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c/interop-fr-1.c
@@ -0,0 +1,571 @@ 
+/* { dg-do run } */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <omp.h>
+#include "../libgomp.c-c++-common/on_device_arch.h"
+
+#define DEFAULT_DEVICE -99
+
+/* The following assumes that when a nvptx device is available,
+   cuda/cuda_driver/hip are supported.
+   And that likewise when a gcn device is available that the
+   plugin also can not only the HSA but also the HIP library
+   such that hsa/hip are supported.
+   For the host, omp_interop_none is expected.
+
+   Otherwise, it only does some basic tests without checking
+   that the returned result really makes sense.  */
+
+void check_host (int);
+void check_nvptx (int);
+void check_gcn (int);
+
+void check_type (omp_interop_t obj)
+{
+  const char *type;
+
+  type = omp_get_interop_type_desc (obj, omp_ipr_fr_id);
+  if (obj != omp_interop_none)
+    assert (strcmp (type, "omp_interop_t") == 0);
+  else
+    assert (type == NULL);
+
+  type = omp_get_interop_type_desc (obj, omp_ipr_fr_name);
+  if (obj != omp_interop_none)
+    assert (strcmp (type, "const char *") == 0);
+  else
+    assert (type == NULL);
+
+  type = omp_get_interop_type_desc (obj, omp_ipr_vendor);
+  if (obj != omp_interop_none)
+    assert (strcmp (type, "int") == 0);
+  else
+    assert (type == NULL);
+
+  type = omp_get_interop_type_desc (obj, omp_ipr_vendor_name);
+  if (obj != omp_interop_none)
+    assert (strcmp (type, "const char *") == 0);
+  else
+    assert (type == NULL);
+
+  type = omp_get_interop_type_desc (obj, omp_ipr_device_num);
+  if (obj != omp_interop_none)
+    assert (strcmp (type, "int") == 0);
+  else
+    assert (type == NULL);
+
+  if (obj != omp_interop_none)
+    return;
+  assert (omp_get_interop_type_desc (obj, omp_ipr_platform) == NULL);
+  assert (omp_get_interop_type_desc (obj, omp_ipr_device) == NULL);
+  assert (omp_get_interop_type_desc (obj, omp_ipr_device_context) == NULL);
+  assert (omp_get_interop_type_desc (obj, omp_ipr_targetsync) == NULL);
+}
+
+void
+do_check (int dev)
+{
+  int num_dev = omp_get_num_devices ();
+  const char *dev_type;
+  if (dev != DEFAULT_DEVICE)
+    omp_set_default_device (dev);
+  int is_nvptx = on_device_arch_nvptx ();
+  int is_gcn = on_device_arch_gcn ();
+  int is_host;
+ 
+  if (dev != DEFAULT_DEVICE)
+    is_host = dev == -1 || dev == num_dev;
+  else
+    {
+      int def_dev = omp_get_default_device ();
+      is_host = def_dev == -1 || def_dev == num_dev;
+    }
+
+  assert (is_nvptx + is_gcn + is_host == 1);
+
+  if (num_dev > 0 && dev != DEFAULT_DEVICE)
+    {
+      if (is_host)
+	omp_set_default_device (0);
+      else
+	omp_set_default_device (-1);
+    }
+
+  if (is_host)
+    dev_type = "host";
+  else if (is_nvptx)
+    dev_type = "nvptx";
+  else if (is_gcn)
+    dev_type = "gcn";
+
+  printf ("Running on the %s device (%d)\n", dev_type, dev);
+  if (is_host)
+    check_host (dev);
+  else if (is_nvptx)
+    check_nvptx (dev);
+  else if (is_gcn)
+    check_gcn (dev);
+}
+
+
+void
+check_host (int dev)
+{
+  omp_interop_t obj = (omp_interop_t) -1L;
+  if (dev == DEFAULT_DEVICE) {
+    #pragma omp interop init(target : obj)
+  } else {
+    #pragma omp interop init(target : obj) device(dev)
+  }
+  assert (obj == omp_interop_none);
+  check_type (obj);
+
+  obj = (omp_interop_t) -1L;
+  if (dev == DEFAULT_DEVICE) {
+    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {attr("ompx_bar"), fr("cuda"), attr("ompx_foobar")},{fr("cuda_driver")}, {fr("hip")}, {fr("hsa")}) : obj)
+  } else {
+    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {attr("ompx_bar"), fr("cuda"), attr("ompx_foobar")},{fr("cuda_driver")}, {fr("hip")}, {fr("hsa")}) : obj) device(dev)
+  }
+  assert (obj == omp_interop_none);
+  check_type (obj);
+
+  obj = (omp_interop_t) -1L;
+  if (dev == DEFAULT_DEVICE) {
+    #pragma omp interop init(targetsync : obj)
+  } else {
+    #pragma omp interop init(targetsync : obj) device(dev)
+  }
+  assert (obj == omp_interop_none);
+  check_type (obj);
+
+  obj = (omp_interop_t) -1L;
+  if (dev == DEFAULT_DEVICE) {
+    #pragma omp interop init(targetsync, prefer_type("cuda","cuda_driver", "hip", "hsa") : obj)
+  } else {
+    #pragma omp interop init(targetsync, prefer_type("cuda","cuda_driver", "hip", "hsa") : obj) device(dev)
+  }
+  assert (obj == omp_interop_none);
+  check_type (obj);
+}
+
+
+void
+check_nvptx (int dev)
+{
+  for (int variant = 0; variant <= 7; variant++)
+    {
+      omp_interop_t obj = (omp_interop_t) -1L;
+      switch (variant)
+	{
+	/* Expect 'cuda'.  */
+	case 0:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target : obj)
+	  } else {
+	    #pragma omp interop init(target : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 1:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync : obj)
+	  } else {
+	    #pragma omp interop init(targetsync : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 2:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {fr("hsa")}, {attr("ompx_bar"), fr("cuda"), attr("ompx_foobar")},{fr("cuda_driver")}, {fr("hip")}) : obj)
+	  } else {
+	    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {fr("hsa")}, {attr("ompx_bar"), fr("cuda"), attr("ompx_foobar")},{fr("cuda_driver")}, {fr("hip")}) : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 3:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "cuda", "cuda_driver", "hip") : obj)
+	  } else {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "cuda", "cuda_driver", "hip") : obj) device(dev)
+	  }
+	  break;
+	  }
+
+	/* Expect 'cuda_driver'.  */
+	case 4:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target, prefer_type("hsa", "cuda_driver", "hip", "cuda") : obj)
+	  } else {
+	    #pragma omp interop init(target, prefer_type("hsa", "cuda_driver", "hip", "cuda") : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 5:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "cuda_driver", "hip", "cuda") : obj)
+	  } else {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "cuda_driver", "hip", "cuda") : obj) device(dev)
+	  }
+	  break;
+	  }
+
+	/* Expect 'hip'.  */
+	case 6:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target, prefer_type("hsa", "hip", "cuda", "cuda_driver") : obj)
+	  } else {
+	    #pragma omp interop init(target, prefer_type("hsa", "hip", "cuda", "cuda_driver") : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 7:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "hip", "cuda", "cuda_driver") : obj)
+	  } else {
+	    #pragma omp interop init(targetsync, prefer_type("hsa", "hip", "cuda", "cuda_driver") : obj) device(dev)
+	  }
+	  break;
+	  }
+	default:
+	  abort ();
+	}
+      assert (obj != omp_interop_none && obj != (omp_interop_t) -1L);
+
+      omp_interop_rc_t ret_code = omp_irc_no_value;
+      omp_interop_fr_t fr = (omp_interop_fr_t) omp_get_interop_int (obj, omp_ipr_fr_id, &ret_code);
+
+      assert (ret_code == omp_irc_success);
+      if (variant >= 0 && variant <= 3)
+	assert (fr == omp_ifr_cuda);
+      else if (variant <= 5)
+	assert (fr == omp_ifr_cuda_driver);
+      else if (variant <= 7)
+	assert (fr == omp_ifr_hip);
+      else
+	assert (0);
+
+      ret_code = omp_irc_no_value;
+      const char *fr_name = omp_get_interop_str (obj, omp_ipr_fr_name, &ret_code);
+
+      assert (ret_code == omp_irc_success);
+      if (fr == omp_ifr_cuda)
+	assert (strcmp (fr_name, "cuda") == 0);
+      else if (fr == omp_ifr_cuda_driver)
+	assert (strcmp (fr_name, "cuda_driver") == 0);
+      else if (fr == omp_ifr_hip)
+	assert (strcmp (fr_name, "hip") == 0);
+      else
+	assert (0);
+
+      ret_code = omp_irc_no_value;
+      int vendor = (int) omp_get_interop_int (obj, omp_ipr_vendor, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (vendor == 11);  /* Nvidia */
+
+      ret_code = omp_irc_no_value;
+      const char *vendor_name = omp_get_interop_str (obj, omp_ipr_vendor_name, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (strcmp (vendor_name, "nvidia") == 0);
+
+      ret_code = omp_irc_no_value;
+      int dev_num = (int) omp_get_interop_int (obj, omp_ipr_device_num, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (dev_num == dev);
+
+      /* Platform: N/A.  */
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_int (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_ptr (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_str (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+
+      /* Device: int / CUdevice / hipDevice_t -- all internally an 'int'.  */
+      ret_code = omp_irc_no_value;
+      int fr_device = (int) omp_get_interop_int (obj, omp_ipr_device, &ret_code);
+
+      /* CUDA also starts from 0 and goes to < n with cudaGetDeviceCount(&cn).  */
+      assert (ret_code == omp_irc_success);
+      assert (fr_device >= 0 && fr_device < omp_get_num_devices ());
+
+      /* Device context: N/A / CUcontext / hipCtx_t -- a pointer.  */
+      ret_code = omp_irc_out_of_range;
+      void *ctx = omp_get_interop_ptr (obj, omp_ipr_device_context, &ret_code);
+
+      if (fr == omp_ifr_cuda)
+	{
+	  assert (ret_code == omp_irc_no_value);
+	  assert (ctx == NULL);
+	}
+      else
+	{
+	  assert (ret_code == omp_irc_success);
+	  assert (ctx != NULL);
+	}
+
+      /* Stream/targetsync: cudaStream_t / CUstream / hipStream_t -- a pointer.  */
+      ret_code = omp_irc_out_of_range;
+      void *stream = omp_get_interop_ptr (obj, omp_ipr_targetsync, &ret_code);
+
+      if (variant % 2 == 0)  /* no targetsync */
+	{
+	  assert (ret_code == omp_irc_no_value);
+	  assert (stream == NULL);
+	}
+      else
+	{
+	  assert (ret_code == omp_irc_success);
+	  assert (stream != NULL);
+	}
+
+      check_type (obj);
+      if (fr == omp_ifr_cuda)
+	{
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_platform), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device), "int") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device_context), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_targetsync), "cudaStream_t") == 0);
+	}
+      else if (fr == omp_ifr_cuda_driver)
+	{
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_platform), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device), "CUdevice") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device_context), "CUcontext") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_targetsync), "CUstream") == 0);
+	}
+      else
+	{
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_platform), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device), "hipDevice_t") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device_context), "hipCtx_t") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_targetsync), "hipStream_t") == 0);
+	}
+
+      if (dev == DEFAULT_DEVICE) {
+	#pragma omp interop use(obj)
+	#pragma omp interop destroy(obj)
+      } else {
+	#pragma omp interop use(obj) device(dev)
+	#pragma omp interop destroy(obj) device(dev)
+      }
+    }
+}
+
+
+void
+check_gcn (int dev)
+{
+  for (int variant = 0; variant <= 5; variant++)
+    {
+      omp_interop_t obj = (omp_interop_t) -1L;
+      switch (variant)
+	{
+	/* Expect 'hip'.  */
+	case 0:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target : obj)
+	  } else {
+	    #pragma omp interop init(target : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 1:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync : obj)
+	  } else {
+	    #pragma omp interop init(targetsync : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 2:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {fr("cuda")}, {fr("cuda_driver")}, {attr("ompx_bar"), fr("hip"), attr("ompx_foobar")},{fr("hsa")}) : obj)
+	  } else {
+	    #pragma omp interop init(target, prefer_type({attr("ompx_foo")}, {fr("cuda")}, {fr("cuda_driver")}, {attr("ompx_bar"), fr("hip"), attr("ompx_foobar")},{fr("hsa")}) : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 3:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync, prefer_type("cuda", "cuda_driver", "hip", "hsa") : obj)
+	  } else {
+	    #pragma omp interop init(targetsync, prefer_type("cuda", "cuda_driver", "hip", "hsa") : obj) device(dev)
+	  }
+	  break;
+	  }
+
+	/* Expect 'hsa'.  */
+	case 4:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(target, prefer_type("cuda", "cuda_driver", "hsa", "hip") : obj)
+	  } else {
+	    #pragma omp interop init(target, prefer_type("cuda", "cuda_driver", "hsa", "hip") : obj) device(dev)
+	  }
+	  break;
+	  }
+	case 5:
+	  {
+	  if (dev == DEFAULT_DEVICE) {
+	    #pragma omp interop init(targetsync, prefer_type("cuda", "cuda_driver", "hsa", "hip") : obj)
+	  } else {
+	    #pragma omp interop init(targetsync, prefer_type("cuda", "cuda_driver", "hsa", "hip") : obj) device(dev)
+	  }
+	  break;
+	  }
+	default:
+	  abort ();
+	}
+      assert (obj != omp_interop_none && obj != (omp_interop_t) -1L);
+
+      omp_interop_rc_t ret_code = omp_irc_no_value;
+      omp_interop_fr_t fr = (omp_interop_fr_t) omp_get_interop_int (obj, omp_ipr_fr_id, &ret_code);
+
+      assert (ret_code == omp_irc_success);
+      if (variant >= 0 && variant <= 3)
+	assert (fr == omp_ifr_hip);
+      else if (variant <= 5)
+	assert (fr == omp_ifr_hsa);
+      else
+	assert (0);
+
+      ret_code = omp_irc_no_value;
+      const char *fr_name = omp_get_interop_str (obj, omp_ipr_fr_name, &ret_code);
+
+      assert (ret_code == omp_irc_success);
+      if (fr == omp_ifr_hip)
+	assert (strcmp (fr_name, "hip") == 0);
+      else if (fr == omp_ifr_hsa)
+	assert (strcmp (fr_name, "hsa") == 0);
+      else
+	assert (0);
+
+      ret_code = omp_irc_no_value;
+      int vendor = (int) omp_get_interop_int (obj, omp_ipr_vendor, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (vendor == 1);  /* Amd */
+
+      ret_code = omp_irc_no_value;
+      const char *vendor_name = omp_get_interop_str (obj, omp_ipr_vendor_name, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (strcmp (vendor_name, "amd") == 0);
+
+      ret_code = omp_irc_no_value;
+      int dev_num = (int) omp_get_interop_int (obj, omp_ipr_device_num, &ret_code);
+      assert (ret_code == omp_irc_success);
+      assert (dev_num == dev);
+
+      /* Platform: N/A.  */
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_int (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_ptr (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+      ret_code = omp_irc_success;
+      (void) omp_get_interop_str (obj, omp_ipr_platform, &ret_code);
+      assert (ret_code == omp_irc_no_value);
+
+      /* Device: hipDevice_t / hsa_agent_t* -- hip is internally an 'int'.  */
+      ret_code = omp_irc_no_value;
+      if (fr == omp_ifr_hip)
+	{
+	  /* HIP also starts from 0 and goes to < n as with cudaGetDeviceCount(&cn).  */
+	  int fr_device = (int) omp_get_interop_int (obj, omp_ipr_device, &ret_code);
+	  assert (ret_code == omp_irc_success);
+	  assert (fr_device >= 0 && fr_device < omp_get_num_devices ());
+	}
+      else
+	{
+	  void *agent = omp_get_interop_ptr (obj, omp_ipr_device, &ret_code);
+	  assert (ret_code == omp_irc_success);
+	  assert (agent != NULL);
+	}
+
+      /* Device context: hipCtx_t / N/A -- a pointer.  */
+      ret_code = omp_irc_out_of_range;
+      void *ctx = omp_get_interop_ptr (obj, omp_ipr_device_context, &ret_code);
+      if (fr == omp_ifr_hip)
+	{
+	  assert (ret_code == omp_irc_success);
+	  assert (ctx != NULL);
+	}
+      else
+	{
+	  assert (ret_code == omp_irc_no_value);
+	  assert (ctx == NULL);
+	}
+
+      /* Stream/targetsync: cudaStream_t / CUstream / hipStream_t -- a pointer.  */
+      ret_code = omp_irc_out_of_range;
+      void *stream = omp_get_interop_ptr (obj, omp_ipr_targetsync, &ret_code);
+
+      if (variant % 2 == 0)  /* no targetsync */
+	{
+	  assert (ret_code == omp_irc_no_value);
+	  assert (stream == NULL);
+	}
+      else
+	{
+	  assert (ret_code == omp_irc_success);
+	  assert (stream != NULL);
+	}
+
+      check_type (obj);
+      if (fr == omp_ifr_hip)
+	{
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_platform), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device), "hipDevice_t") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device_context), "hipCtx_t") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_targetsync), "hipStream_t") == 0);
+	}
+      else
+	{
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_platform), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device), "hsa_agent_t *") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_device_context), "N/A") == 0);
+	  assert (strcmp (omp_get_interop_type_desc (obj, omp_ipr_targetsync), "hsa_queue_t *") == 0);
+	}
+
+      if (dev == DEFAULT_DEVICE) {
+	#pragma omp interop use(obj)
+	#pragma omp interop destroy(obj)
+      } else {
+	#pragma omp interop use(obj) device(dev)
+	#pragma omp interop destroy(obj) device(dev)
+      }
+    }
+}
+
+
+int
+main ()
+{
+  do_check (DEFAULT_DEVICE);
+  int ndev = omp_get_num_devices ();
+  for (int dev = -1; dev < ndev; dev++)
+    do_check (dev);
+  for (int dev = -1; dev < ndev; dev++)
+    {
+      omp_set_default_device (dev);
+      do_check (DEFAULT_DEVICE);
+    }
+}