Now committed as r15-3836-g4cb20dc043cf70

Contrary to the originally posted patch, it also acts on the newer/newly added 'omp requires self_maps'.

In the area of (unified-)shared memory/self maps, the next step seems to be to do still mapping for static variables – before moving to refinements like how to handle implicit 'declare target' for static variables, …

For this piece of code, we also want to run it for APUs even when no USM has been requested, avoid adding those to the mapping table (for self maps) and do a more efficient mapping (e.g. memcpy or avoid multiple locks).

Tobias

Tobias Burnus wrote:

short version: I think the patch as posted is fine and no action beyond is needed for this one issue.

See below for the long version.

Possibly modifications (now or as follow up):
- using memcpy + or let the plugin do it
- not adding link variables to the splay tree with 'USM'.

Thomas Schwinge wrote:
Tested on x86-64-gnu-linux and nvptx offloading (that supports USM).
(I yet have to set up such a USM configuration...)

You already used an USM config, e.g., when running gfx90a (likewise: gfx90c), except that USM on mainline it currently only works if you explicitly set 'export HSA_XNACK=1'.

For Nvptx, you need a post-Volta GPU with the open-kernels driver, which is for newer driver versions the default.

* * *
Do I understand correctly that even if
'GOMP_REQUIRES_UNIFIED_SHARED_MEMORY', we cannot just skip all the
'mem_map' setup in 'gomp_load_image_to_device' etc., because we're not
(yet?) setting 'GOMP_OFFLOAD_CAP_SHARED_MEM'?

We actually do set GOMP_OFFLOAD_CAP_SHARED_MEM with 'requires unified_shared_memory'.

But, indeed, we cannot skip the memory mapping parts – due to the way we handle static variables.

* * *

+
+      if (is_link_var
+      && (omp_requires_mask & GOMP_REQUIRES_UNIFIED_SHARED_MEMORY))
+    gomp_copy_host2dev (devicep, NULL, (void *) target_var->start,
+                &k->host_start, sizeof (void *), false, NULL);
      }
Calling 'gomp_copy_host2dev' looks a bit funny given we've just
determined USM (..., but I'm not asking for plain 'memcpy').

I guess a plain memcpy would do as well. [Assuming that the device's static variable is host accessible, which it probably is and should be.]

I add it to my to-do list for USM-related tasks to change this; possibly moving it to the plugin side has some advantages? Possibly not adding it to the splay tree if not needed. (Cf. below for env var discussion.)

Regarding the unload: For 'declare target link(A)', we have, e.g., 'static int *A' on the device side. Thus, we could do 'A = NULL' – and rather should do 'A = {clobber}', but that's rather pointless in general and especially when unloading the image.

What's the advantage/rationale of doing this here vs. in
'gomp_map_vars_internal' for 'REFCOUNT_LINK'?  (May be worth a source
code comment?)

(A, B, C refers to the following example.)

We don't see 'A' (or 'B') in the GOMP_target_ext call and thus not in gomp_map_vars_internal.

Besides: We only want to do the initialization once and not every time gomp_map_vars_internal is called.

I think the following program may help to understand the issue and the patch better.

Note: While A, B, C are 'int …[3]' on the host, on the device we only have 'int B[3]' while for A it's 'int *A' and C only exists on the host.

 * * *

#pragma requires unified_shared_memory

static int A[3], B[3], C[3];
#pragma omp declare target link(A) enter(B)

#pragma omp begin declare target
void f(int *p)
{
   A[2] += B[2] + p[2];  // p points to the host's C variable
}
#pragma omp end declare target

void foo(int dev) {
  int *ptr = C;
  #pragma omp target firstprivate(ptr) device(dev)
    f (ptr);
}


* * *

Here, 'ptr' (and thus 'p') point to the host 'C' variable, both before the target
region and inside the target region.

'B' points to the device local version of the variable.

And 'A' on a non-host device is likely to be NULL ('static int *A' + .BSS) before this patch.
Or pointing to the host's 'A' with this patch.

* * *

With A pointing to the host version (and likewise 'p' pointing to the host C), host fallback and device version yield identical result for 'A' and for 'C' (via ptr/p). — However, 'B' on host and non-host device have nothing in common. While that might be fine, in general it is not.

Hence, in order to get for a .BSS valued 'B' the same result on host and device, we need, e.g.

#pragma omp data map(always: B) device(dev)
  foo (dev);

to call 'foo' to ensure that the two 'B' are in sync.

* * *

Code wise, this means that with GOMP_OFFLOAD_CAP_SHARED_MEM, we still have
to apply the map for 'declare target enter(…)' variables, except if host
and device share the same code – but that should only be the case for
host fallback (= initial device) and, possibly, GOMP_OFFLOAD_CAP_NATIVE_EXEC.

* * *

NOTE: OpenMP still permits to honor explicit 'map' with 'requires unified_shared_memory', only with 'self' maps, copying the data in 'map' is explicitly disallowed.

* * *

This patch + honoring 'map' for static (non-'link'?) variables even with
GOMP_OFFLOAD_CAP_SHARED_MEM where the main items for the USM follow-up patches, I meant by "More USM cleanup/fixes/extensions to make it _more_ useful" on slide 16 of https://gcc.gnu.org/wiki/cauldron2024#cauldron2024talks.openmp_openacc_and_offloading_in_gcc

Plus, to go a bit beyond:
- offering a flag to change 'declare target enter(…)' to 'link(…)'
  [RFC: enable it by default for 'requires unified_shared_memory'?]

- switching to GOMP_OFFLOAD_CAP_SHARED_MEM by default for APUs
  (= same memory controller) for performance

- Adding a GOMP_ environment variable to toggle between mapping vs. USM
  access on systems not detected as being APUs. (That is: systems that
  support USM but use an interconnect or page migration for the memory
  access. Possibly, also overriding the USM detection for systems which
  can access the host memory but due to some own memory are not recognized
  (→ device attributes) as being USM devices.
  And possibly also forcing to honor explicit maps with requires

(Example for the latter is Andrew's gfx1103, which reports
HSA_AMD_SYSTEM_INFO_SVM_ACCESSIBLE_BY_DEFAULT = 'false'; I assume it
can still access all host memory, but I might be wrong.)

- Documenting how GCC handles this in libgomp.texi

BTW: See https://gcc.gnu.org/onlinedocs/libgomp/Offload-Target-Specifics.html
for how USM is detected for nvptx + gcn devices.

* * *

For completeness, I also intent to look at Andrew's pinned memory/(pseudo)USM
patches; they are useful but address other aspects as those listed above
→ https://gcc.gnu.org/pipermail/gcc-patches/2024-June/654331.htmlhttps://gcc.gnu.org/pipermail/gcc-patches/2024-May/652932.html
commit 4cb20dc043cf70b8a1b4846c86599cc1ff9680d9
Author: Tobias Burnus <tbur...@baylibre.com>
Date:   Tue Sep 24 17:41:39 2024 +0200

    libgomp: with USM, init 'link' variables with host address
    
    If requires unified_shared_memory or self_maps is set, make
    'declare target link' variables to point initially to the host pointer.
    
    libgomp/ChangeLog:
    
            * target.c (gomp_load_image_to_device): For requires
            unified_shared_memory, update 'link' vars to point to the host var.
            * testsuite/libgomp.c-c++-common/target-link-3.c: New test.
            * testsuite/libgomp.c-c++-common/target-link-4.c: New test.
---
 libgomp/target.c                                   |  6 +++
 .../testsuite/libgomp.c-c++-common/target-link-3.c | 52 ++++++++++++++++++++++
 .../testsuite/libgomp.c-c++-common/target-link-4.c | 52 ++++++++++++++++++++++
 3 files changed, 110 insertions(+)

diff --git a/libgomp/target.c b/libgomp/target.c
index 6918694a843..cf62af61f3b 100644
--- a/libgomp/target.c
+++ b/libgomp/target.c
@@ -2454,6 +2454,12 @@ gomp_load_image_to_device (struct gomp_device_descr *devicep, unsigned version,
       array->right = NULL;
       splay_tree_insert (&devicep->mem_map, array);
       array++;
+
+      if (is_link_var
+	  && (omp_requires_mask
+	      & (GOMP_REQUIRES_UNIFIED_SHARED_MEMORY | GOMP_REQUIRES_SELF_MAPS)))
+	gomp_copy_host2dev (devicep, NULL, (void *) target_var->start,
+			    &k->host_start, sizeof (void *), false, NULL);
     }
 
   /* Last entry is for the ICV struct variable; if absent, start = end = 0.  */
diff --git a/libgomp/testsuite/libgomp.c-c++-common/target-link-3.c b/libgomp/testsuite/libgomp.c-c++-common/target-link-3.c
new file mode 100644
index 00000000000..c707b38b7d4
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c-c++-common/target-link-3.c
@@ -0,0 +1,52 @@
+/* { dg-do run }  */
+
+#include <stdint.h>
+#include <omp.h>
+
+#pragma omp requires unified_shared_memory
+
+int A[3] = {-3,-4,-5};
+static int q = -401;
+#pragma omp declare target link(A, q)
+
+#pragma omp begin declare target
+void
+f (uintptr_t *pA, uintptr_t *pq)
+{
+  if (A[0] != 1 || A[1] != 2 || A[2] != 3 || q != 42)
+    __builtin_abort ();
+  A[0] = 13;
+  A[1] = 14;
+  A[2] = 15;
+  q = 23;
+  *pA = (uintptr_t) &A[0];
+  *pq = (uintptr_t) &q;
+}
+#pragma omp end declare target
+
+int
+main ()
+{
+  uintptr_t hpA = (uintptr_t) &A[0];
+  uintptr_t hpq = (uintptr_t) &q;
+  uintptr_t dpA, dpq;
+
+  A[0] = 1;
+  A[1] = 2;
+  A[2] = 3;
+  q = 42;
+
+  for (int i = 0; i <= omp_get_num_devices (); ++i)
+    {
+      #pragma omp target device(device_num: i) map(dpA, dpq)
+	f (&dpA, &dpq);
+      if (hpA != dpA || hpq != dpq)
+	__builtin_abort ();
+      if (A[0] != 13 || A[1] != 14 || A[2] != 15 || q != 23)
+	__builtin_abort ();
+      A[0] = 1;
+      A[1] = 2;
+      A[2] = 3;
+      q = 42;
+    }
+}
diff --git a/libgomp/testsuite/libgomp.c-c++-common/target-link-4.c b/libgomp/testsuite/libgomp.c-c++-common/target-link-4.c
new file mode 100644
index 00000000000..785055e216d
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c-c++-common/target-link-4.c
@@ -0,0 +1,52 @@
+/* { dg-do run }  */
+
+#include <stdint.h>
+#include <omp.h>
+
+#pragma omp requires self_maps
+
+int A[3] = {-3,-4,-5};
+static int q = -401;
+#pragma omp declare target link(A, q)
+
+#pragma omp begin declare target
+void
+f (uintptr_t *pA, uintptr_t *pq)
+{
+  if (A[0] != 1 || A[1] != 2 || A[2] != 3 || q != 42)
+    __builtin_abort ();
+  A[0] = 13;
+  A[1] = 14;
+  A[2] = 15;
+  q = 23;
+  *pA = (uintptr_t) &A[0];
+  *pq = (uintptr_t) &q;
+}
+#pragma omp end declare target
+
+int
+main ()
+{
+  uintptr_t hpA = (uintptr_t) &A[0];
+  uintptr_t hpq = (uintptr_t) &q;
+  uintptr_t dpA, dpq;
+
+  A[0] = 1;
+  A[1] = 2;
+  A[2] = 3;
+  q = 42;
+
+  for (int i = 0; i <= omp_get_num_devices (); ++i)
+    {
+      #pragma omp target device(device_num: i) map(dpA, dpq)
+	f (&dpA, &dpq);
+      if (hpA != dpA || hpq != dpq)
+	__builtin_abort ();
+      if (A[0] != 13 || A[1] != 14 || A[2] != 15 || q != 23)
+	__builtin_abort ();
+      A[0] = 1;
+      A[1] = 2;
+      A[2] = 3;
+      q = 42;
+    }
+}

Reply via email to