Attaching and detaching pointers
When a derived type with a Fortran pointer member is present on the device, the device copy of
the pointer member must be made to point at the device copy of its target before it can be
dereferenced in a kernel. OpenACC manages this with attach and detach actions, governed by
an attachment counter per pointer, as defined in the
OpenACC 3.3 specification:
OpenACC 3.3 section 2.6.8
Since multiple pointers can target the same address, each pointer in device memory is associated
with an attachment counter per device. The attachment counter for a pointer is initialized to zero
when the pointer is allocated in device memory. The attachment counter for a pointer is set to one
whenever the pointer is attached to new target address, and incremented whenever an attach action
for that pointer is performed for the same target address. The attachment counter is decremented
whenever a detach action occurs for the pointer, and the pointer is detached when the attachment
counter reaches zero.
In the tables below, the Correctness column states whether each compiler's observed behaviour conforms to the OpenACC specification.
Re-attaching a pointer after host reassociation
A common pattern in real applications is to swap the buffer a container points to: the host pointer is reassociated to a new target, and the pointer is attached again. The Attach Action (section 2.7.2) is explicit that a second attach may only increment the counter when the device pointer already points to the right target — otherwise it must repoint the device pointer:
OpenACC 3.3 section 2.7.2, Attach Action
If the attachment counter for var is nonzero and the pointer in device memory already points to the
device copy of the data in var, the attachment counter for the pointer var is incremented. Otherwise,
the pointer in device memory is attached to the device copy of the data by initiating an update for the
pointer in device memory to point to the device copy of the data and setting the attachment counter
for the pointer var to one.
In the program below, c%p is first attached to target a (all ones), then reassociated to b
(all twos) and attached again. Per the specification the kernel must read b through c%p and
print OK:
Code
program main
implicit none
type :: container
integer, pointer :: p(:)
end type
type(container) :: c
integer, pointer :: a(:), b(:)
integer :: i, res(10)
allocate(a(10), b(10))
a = 1
b = 2
c%p => a
!$acc enter data copyin(c)
!$acc enter data copyin(a) attach(c%p)
c%p => b
!$acc enter data copyin(b) attach(c%p)
res = -1
!$acc parallel loop present(c)
do i = 1, 10
res(i) = c%p(i)
end do
if (all(res == 2)) then
write(*,*) "OK"
else
write(*,*) "Wrong result: ", res
error stop
end if
end program
| Compiler | Result | Correctness | Notes |
|---|---|---|---|
| Cray Fortran 19.0.0 | 🟡 Wrong result | Not per spec — the second attach is silently skipped | Prints Wrong result: 10*1: the kernel reads the old target a through the stale device pointer. |
| nvfortran 25.3-0 | ✅ OK | Per spec — the device pointer is repointed to b's device copy |
Prints OK. |
Why Cray gets this wrong
The Cray runtime trace (CRAY_ACC_DEBUG=3) shows both attach operations. The first attach updates
the device pointer and copies the container to the device:
Cray trace, first attach
The second attach — after c%p => b — is recognised but skipped, even though the trace itself
prints the new pointee (14fee6a02000, the device copy of b) that the device pointer should have
been updated to. No copy to the device follows:
Cray trace, second attach
So the Cray runtime decides "already attached" from the attachment counter alone: counter is
nonzero, therefore skip. The specification instead requires both conditions — counter nonzero
and "the pointer in device memory already points to the device copy of the data" — before the
update may be skipped. Cray never compares the device pointer's current target with the new one,
so the device copy of c%p keeps pointing at a's device allocation and the kernel silently
reads stale data.
The test below corroborates this model from the other side: when the attachment counter is not positive, Cray does perform a full, correct attach. The bug is therefore precisely the missing target comparison in the counter-positive path.
detach on a never-attached pointer
What happens when a pointer that was never attached is detached? The Detach Action (section 2.7.2) requires this to be a silent no-op:
OpenACC 3.3 section 2.7.2, Detach Action
The program below detaches a nullified, never-attached pointer member, and then performs a real
attach to check that the spurious detach has not corrupted the pointer's bookkeeping. Per the
specification it should print OK:
Code
program main
implicit none
type :: container
integer, pointer :: p(:)
end type
type(container) :: c
integer, pointer :: a(:)
integer :: i, res(10)
allocate(a(10))
a = 1
nullify(c%p)
!$acc enter data copyin(c)
!$acc exit data detach(c%p)
c%p => a
!$acc enter data copyin(a) attach(c%p)
res = -1
!$acc parallel loop present(c)
do i = 1, 10
res(i) = c%p(i)
end do
if (all(res == 1)) then
write(*,*) "OK"
else
write(*,*) "Wrong result: ", res
error stop
end if
end program
| Compiler | Result | Correctness | Notes |
|---|---|---|---|
| Cray Fortran 19.0.0 | ✅ OK | Per spec in observable behaviour, but the counter bookkeeping deviates | The runtime performs a real detach action instead of a no-op: the trace shows detach pointer (ref count -1). The subsequent attach still works (see below). |
| nvfortran 25.3-0 | ✅ OK | Per spec — the detach is a no-op and the attach succeeds | Prints OK. |
The Cray trace shows the detach decrementing the attachment counter to -1 instead of taking no action:
Cray trace, spurious detach followed by attach
The end result is still correct, but only by virtue of the bug described above: because Cray's attach treats any non-positive counter as "not attached", the -1 counter still takes the full-attach path. The two deviations cancel out here; programs that mix spurious detaches with genuine re-attachments should not rely on that.