Skip to content

2. Bounds, resizing, copies ​

The scheme reads the neighbours of each cell, so the field needs two ghost cells, 0 and n+1, that hold the boundary values. Fortran indexes them naturally with lower bound 0; FUNDAL allocates device arrays with any bounds.

fortran
call dev_alloc(fptr_dev=t_dev, lbounds=[0], ubounds=[n+1], ierr=ierr, init_value=0._R8P, label='temperature')
if (ierr /= 0) error stop 'device allocation failed'
print '(A,I0,A,I0)', 'device array bounds: ', lbound(t_dev, 1), ':', ubound(t_dev, 1)

lbounds and ubounds give the bounds of every dimension (here one); init_value sets every element on the device, by a kernel, so no host array and no copy are needed to start from zero.

Resizing ​

Refining the grid doubles the cells. dev_alloc on a pointer that already holds a buffer would lose that buffer (a leak, with pointer-allocate semantics); dev_alloc_replace frees it first:

fortran
n = 2*n ! refine the grid: twice the cells
call dev_alloc_replace(fptr_dev=t_dev, lbounds=[0], ubounds=[n+1], ierr=ierr, init_value=0._R8P, label='temperature')
if (ierr /= 0) error stop 'device allocation failed'
call dev_get_alloc_stats(allocs=allocs, bytes=bytes)
print '(A,I0,A,I0)', 'after the refinement: ', lbound(t_dev, 1), ':', ubound(t_dev, 1)
print '(A,I0,A,I0,A)', 'live device allocations: ', allocs, ' (', bytes, ' bytes)'

The contents are not preserved, as with deallocate and allocate: they are undefined unless init_value is passed. The pointer passed to dev_alloc_replace must have a defined association status: declared =>null(), nullified, or allocated.

Allocate and copy in one call ​

dev_assign_to_device mimics the assignment of an allocatable: it (re)allocates the device array with the shape of the host array and copies it. Its bounds deserve attention:

fortran
allocate(t(0:n+1))
t = 20._R8P ; t(0) = 0._R8P ; t(n+1) = 0._R8P   ! room temperature, cold boundaries
call dev_assign_to_device(dst=t_dev, src=t, ierr=ierr)
if (ierr /= 0) error stop 'device allocation failed'
print '(A,I0,A,I0)', 'dev_assign_to_device(dst, src):          ', lbound(t_dev, 1), ':', ubound(t_dev, 1)
call dev_assign_to_device(lbounds=lbound(t, 1), dst=t_dev, src=t, ierr=ierr)
if (ierr /= 0) error stop 'device allocation failed'
print '(A,I0,A,I0)', 'dev_assign_to_device(lbounds, dst, src): ', lbound(t_dev, 1), ':', ubound(t_dev, 1)

dev_assign_from_device does the same towards a host allocatable:

fortran
call dev_assign_from_device(dst=t_back, src=t_dev)
print '(A,I0,A,I0)', 'dev_assign_from_device(dst, src):          ', lbound(t_back, 1), ':', ubound(t_back, 1)
call dev_assign_from_device(lbounds=lbound(t_dev, 1), dst=t_back, src=t_dev)
print '(A,I0,A,I0)', 'dev_assign_from_device(lbounds, dst, src): ', lbound(t_back, 1), ':', ubound(t_back, 1)
print '(A,*(F5.1))', 'temperature:', t_back
text
$ heat_2
device array bounds: 0:5
after the refinement: 0:9
live device allocations: 1 (80 bytes)
dev_assign_to_device(dst, src):          1:10
dev_assign_to_device(lbounds, dst, src): 0:9
dev_assign_from_device(dst, src):          1:10
dev_assign_from_device(lbounds, dst, src): 0:9
temperature:  0.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0  0.0
live device allocations: 0

Lower bounds are reset to 1

dev_assign_to_device(dst, src) and dev_assign_from_device(dst, src) receive src as an assumed-shape array, whose lower bounds are 1: the destination is allocated 1:size, whatever the bounds of the source. Pass the lower bounds first, dev_assign_to_device(lbounds, dst, src), to keep them. Both routines always reallocate the destination, even when the shape does not change.

What you learned

Custom bounds and init_value; dev_alloc_replace to resize without leaks; dev_assign_* and the lower-bounds-first form that keeps the bounds. Reference: dev_alloc, dev_alloc_replace, dev_assign_to_device.

Next: 3. Portable kernels.