We’ve narrowed it down and made a reproducible of the real things in a C++ example. Had to do in C++ because the gnu compiler with GPU offloading that I have at hand is 14.2.0 and it errors out if you put a struct in the map() clause in Fortran. The output of the code below compiling with : nvc++ -mp=gpu bug.cpp is:
[jlv900@gadi-gpu-v100-0106 MOM6-examples]$ nvc++ -mp=gpu ed-2.cpp
[jlv900@gadi-gpu-v100-0106 MOM6-examples]$ ./a.out
Allocating c.a on host...
Address of c.a on host: 0x20dd0d0
Mapping c and c.a to device...
Address of c.a on device: 0x149c83afa200
Checking device address again, to confirm it doesn't change between target regions
Address of c.a on device: 0x149c83afa200
!$omp target update to(c)...
Address of c.a on device (after update): 0x20dd0d0
with g++ -fopenmp bug.cpp:
[jlv900@gadi-gpu-v100-0106 MOM6-examples]$ g++ -fopenmp ed-2.cpp
[jlv900@gadi-gpu-v100-0106 MOM6-examples]$ ./a.out
Allocating c.a on host...
Address of c.a on host: 0xa6f7e0
Mapping c and c.a to device...
Address of c.a on device: 0x14fa17400200
Checking device address again, to confirm it doesn't change between target regions
Address of c.a on device: 0x14fa17400200
!$omp target update to(c)...
Address of c.a on device (after update): 0x14fa17400200
So the device address with nvc++ is being updated to the host? while in G++ it is not.
The code is:
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <omp.h>
struct container {
double* a;
};
int main() {
container c;
uintptr_t addr_a_host = 0;
uintptr_t addr_a_dev = 0;
std::cout << "Allocating c.a on host..." << std::endl;
// Allocate 10x20 array
c.a = static_cast<double*>(std::calloc(10 * 20, sizeof(double)));
addr_a_host = reinterpret_cast<uintptr_t>(c.a);
std::cout << " Address of c.a on host: 0x" << std::hex << addr_a_host << std::dec << std::endl;
std::cout << "Mapping c and c.a to device..." << std::endl;
#pragma omp target enter data map(to: c, c.a[0:10*20])
// Query address of c.a on device
#pragma omp target map(from: addr_a_dev)
{
addr_a_dev = reinterpret_cast<uintptr_t>(c.a);
}
std::cout << " Address of c.a on device: 0x" << std::hex << addr_a_dev << std::dec << std::endl;
// Confirm address doesn't change across target regions
#pragma omp target map(from: addr_a_dev)
{
addr_a_dev = reinterpret_cast<uintptr_t>(c.a);
}
std::cout << "Checking device address again, to confirm it doesn't change between target regions" << std::endl;
std::cout << " Address of c.a on device: 0x" << std::hex << addr_a_dev << std::dec << std::endl;
std::cout << "!$omp target update to(c)..." << std::endl;
#pragma omp target update to(c)
#pragma omp target map(from: addr_a_dev)
{
addr_a_dev = reinterpret_cast<uintptr_t>(c.a);
}
std::cout << " Address of c.a on device (after update): 0x" << std::hex << addr_a_dev << std::dec << std::endl;
std::free(c.a);
return 0;
}
which is a carbon copy of our Fortran example:
program test
use iso_fortran_env, only: int64
implicit none
type container
double precision, allocatable :: a(:, :)
end type container
type(container) :: c
integer(int64) :: addr_a, addr_a_d
print '(A)', "Allocating c%a on host..."
allocate( c%a(10, 20), source=0.d0)
addr_a = loc(c%a)
print '(A, Z)', " Address of c%a on host: ", addr_a
print '(A)', "Mapping c, c%a to device..."
!$omp target enter data map(to: c, c%a)
!$omp target map(from: addr_a_d)
addr_a_d = loc(c%a)
!$omp end target
print '(A, Z)', " Address of c%a on device: ", addr_a_d
!$omp target map(from: addr_a_d)
addr_a_d = loc(c%a)
!$omp end target
print '(A)', "Checking device address again, to confirm it doesn't change between target regions"
print '(A, Z)', " Address of c%a on device: ", addr_a_d
print '(A)', "!$omp target update to(c)..."
!$omp target update to(c)
!$omp target map(from: addr_a_d)
addr_a_d = loc(c%a)
!$omp end target
print '(A, Z)', " Address of c%a on device (after update):", addr_a_d
end program test