Fortran interface variants#
hipFORT generates more than one Fortran interface for the same underlying C routine. Which ones are available depends on the Fortran compiler used to build hipFORT and on two CMake options. This page describes the variants, how they differ at the call site, and how to choose between them.
For the wider picture — which libraries hipFORT wraps, and how to link against them — see Using hipFORT in your application.
The three variants#
Fortran 2003 C bindings. Always present. Device memory is held in a
type(c_ptr)and sizes are counted in bytes. These only require theiso_c_bindingmodule.Fortran 2008 array overloads. Generated once per rank, they take a Fortran array and a number of elements instead of a
type(c_ptr)and a byte count. They reduce the chance of compile-time and runtime errors and make the call site easier to read. hipFORT enables them automatically once it detects Fortran 2008 support in the compiler.Fortran 2018 assumed-rank overloads. An experimental opt-in that replaces the per-rank overloads with a single
dimension(..)overload. See Assumed-rank interfaces (Fortran 2018) below.
By convention, application and test sources that rely on the array overloads use
the .f08 file extension (see the test/f2008 examples), while Fortran 2003
sources use .f03.
Independently of these variants, a routine that takes a scalar through a pointer accepts either the Fortran variable or its address; see Scalar arguments passed by pointer.
How a variant is selected#
Each C routine X becomes a generic interface X. You always call X;
the compiler picks the specific procedure that matches your arguments. The
Fortran 2008 and Fortran 2018 specifics are compiled in or out by the
preprocessor, which is why the binding sources are .F90 files:
interface rocblas_dscal
function rocblas_dscal_(handle, n, alpha, x, incx) bind(c, name="rocblas_dscal")
...
end function
module procedure rocblas_dscal_typed
#ifdef USE_ASSUMED_RANK
module procedure rocblas_dscal_assumed_rank
#else
#ifdef USE_ASSUMED_SHAPE
module procedure rocblas_dscal_rank_0, rocblas_dscal_rank_1
#endif
#endif
end interface
-DHIPFORT_USE_FPOINTER_INTERFACES=ON (the default when the compiler supports
Fortran 2008) defines USE_ASSUMED_SHAPE, and -DHIPFORT_ASSUMED_RANK=ON
defines USE_ASSUMED_RANK instead. If you compile the binding sources yourself,
define the same macro.
Specific procedure names#
The specific procedures appear under these names in the API reference, which
documents the Fortran 2008 build and so has no X_assumed_rank specifics. The
supported-API tables list them by suffix, with X_ shown as “C binding”. Call
them through the generic name X.
Specific |
Standard |
What it takes |
|---|---|---|
|
Fortran 2003 |
The |
|
Fortran 2003 |
A scalar the C routine takes through a pointer ( |
|
Fortran 2003 |
For routines that mix scalars read per the pointer mode and host-only
scalars: the former as |
|
Fortran 2003 |
An argument the C API allows to be |
|
Fortran 2008 |
Buffers as Fortran arrays of rank |
|
Fortran 2008 |
A buffer followed by its leading dimension ( |
|
Fortran 2008 |
For a strided-batched routine, each operand with the batch as one more
dimension: a matrix followed by its leading dimension and batch stride as
|
|
Fortran 2008 |
As above, with an argument that |
|
Fortran 2018 |
Buffers as |
Other kinds of arguments#
Handles (
rocblas_handle,hipStream_t, plan and descriptor handles) aretype(c_ptr), passed by value.Enumerations are named constants of
enum, bind(c)blocks, in thehipfort_<lib>_enumsmodules (hipfort_enumsfor HIP). Pass the constants, such asrocblas_operation_none; their kind is that of a Cint.Structures of the C API (
hipDeviceProp_t, …) arebind(c)derived types inhipfort_typesand thehipfort_<lib>_typesmodules.Character strings: a
const char*input is atype(c_ptr)inX_, and acharacter(kind=c_char, len=*)inX_typedwhere the library takes a name or a message (ROCTx). Terminate the string withc_null_char, as C expects:ierr = roctxRangePush("solve"//c_null_char).Callbacks (
hipStreamAddCallback,hipLaunchHostFunc, …) aretype(c_funptr): passc_funlocof abind(c)procedure.Batched routines (
*_batched) takeAas a device array of device pointers, one per matrix: pass thetype(c_ptr)of that array, which you fill with thetype(c_ptr)of each matrix. The*_strided_batchedforms take one array and a stride instead, whichX_strided_ranktakes with the batch as its last dimension.64-bit routines (
*_64in rocBLAS, hipBLAS and rocSOLVER) take their sizes, increments and leading dimensions asinteger(c_int64_t), and have the same variants as the 32-bit ones. The exception is rocBLAS’srocblas_set_vector_64,rocblas_get_matrix_64and the other transfer helpers, which only have thetype(c_ptr)form.
Every routine returns its library’s status. The hipfort_check module has a
helper per library (hipCheck, rocblasCheck, hipsolverCheck, …) that
prints the status by name and stops the program when a call fails.
Passing arrays to the array overloads#
The array overloads hand the address of the array to the C library, which reads
it as one contiguous block. Pass contiguous arrays: a whole array, or a
section such as a(:, j) or a(i:j). A strided section such as
a(1:n:2) is not rejected, but the library then reads the wrong elements.
No overload, Fortran 2008 or Fortran 2018, declares its arrays contiguous:
the compiler would then pass a contiguous copy of such a section, made on the
host, which is fatal for an array in device memory on GPUs without XNACK
enabled, and which an asynchronous routine would still be reading after the
call returns.
The address is that of your array, valid as long as the array is. For an
asynchronous routine, such as hipMemcpyAsync from a host array, keep the
array alive and unchanged until the stream has completed, and give it the
target attribute: if the compiler has to pass a temporary copy (an
expression, a non-contiguous section), the copy disappears when the call
returns, while the transfer may still be running.
Allocating and copying device memory#
The Fortran 2003 form holds the device pointer in a type(c_ptr) and counts
bytes:
use iso_c_binding
use hipfort
integer :: ierr ! error code
real, target :: a_h(5,6) ! host array ('target' is required by c_loc)
type(c_ptr) :: a_d ! device array pointer
!
! real has 4 bytes; the '_c_size_t' suffix writes 4 as an integer(c_size_t)
ierr = hipMalloc(a_d, size(a_h)*4_c_size_t)
ierr = hipMemcpy(a_d, c_loc(a_h), size(a_h)*4_c_size_t, &
hipMemcpyHostToDevice)
The array overloads take a Fortran array pointer and a shape, and count elements rather than bytes:
use hipfort
integer :: ierr ! error code
real :: a_h(5,6) ! host array
real, pointer :: a_d(:,:) ! device array pointer
!
ierr = hipMalloc(a_d, shape(a_h)) ! or hipMalloc(a_d, [5,6])
! or hipMalloc(a_d, 5, 6)
ierr = hipMemcpy(a_d, a_h, size(a_h), hipMemcpyHostToDevice)
hipMalloc is also overloaded with mold, source and dsource
arguments, in the spirit of the ALLOCATE intrinsic. mold takes the shape
of another array, including its bounds, without copying anything, while
source also performs a blocking copy to the device:
ierr = hipMalloc(a_d, mold=a_h) ! shape of a_h, no copy
ierr = hipMalloc(a_d, source=a_h) ! shape of a_h, plus a copy from the host
Use dsource instead of source when the source array already lives on the
device.
The other allocation forms:
hipMalloc(a_d, n1, n2, ...)takes one length per dimension, asinteger(c_int)orinteger(c_size_t).hipMalloc(a_d, dims=[n1, n2], lbounds=[0, 0])sets the lower bounds of the pointer;dims8andlbounds8are theirinteger(c_int64_t)spelling. Pass exactly one ofdims,dims8,mold,sourceordsource.hipMallocManagedandhipHostMalloctake the same forms, followed by the HIPflags. In the forms with lengths,flagsis optional and defaults tohipMemAttachGlobalandhipHostMallocDefault.hipFree(a_d)andhipHostFree(a_d)free the memory and nullify the pointer.hipFree(a_d, only_if_allocated=.true.)does nothing whena_dis not associated.
When an allocation fails, the pointer is left unassociated, so
associated(a_d) tells whether the memory exists, and hipFree(a_d,
only_if_allocated=.true.) is safe in a cleanup path. A zero-size allocation
succeeds without allocating memory; do not rely on associated for it.
The copy routines count elements, not bytes: the length of hipMemcpy and
hipMemcpyAsync, and the pitches and width of hipMemcpy2D and
hipMemcpy2DAsync (height stays a number of rows), for arrays of the same
element type on both sides. Without length, hipMemcpy and
hipMemcpyAsync copy size(dest) elements.
hipHostRegister(a, flags) registers a whole host array; a form with lengths
registers its first elements. hipHostGetDevicePointer and
hipHostGetFlags take the host array itself.
These array overloads exist for logical(c_bool), integer(c_int),
integer(c_int64_t), real(c_float), real(c_double),
complex(c_float_complex) and complex(c_double_complex) arrays of rank 0
to 7.
The vector and matrix transfer helpers of rocBLAS and hipBLAS
(rocblas_set_vector, hipblasGetMatrix, their asynchronous forms, …)
have the same element types. A vector is a scalar or a rank-1 array. A matrix
is a scalar or a rank-1 array, each with an explicit leading dimension, or a
rank-2 array, whose leading dimension is then size(A, 1). The element size
the C routine takes is derived from the array type.
Like the array interfaces of the math libraries, these hipMalloc and
hipMemcpy overloads exist only when the Fortran 2008 interfaces are enabled:
a build with -DHIPFORT_USE_FPOINTER_INTERFACES=OFF has only the
type(c_ptr) forms. The allocation routines (hipMalloc,
hipMallocManaged, hipHostMalloc, hipFree, hipHostFree) and the
hipHostRegister family (hipHostUnregister, hipHostGetDevicePointer,
hipHostGetFlags) have no assumed-rank form, so
their per-rank overloads stay available with -DHIPFORT_ASSUMED_RANK=ON.
Scalar arguments passed by pointer#
Many C routines take a scalar through a pointer: alpha and beta in the
BLAS and sparse libraries, or an output such as a buffer size, a version or a
count. hipFORT offers two forms of each such routine under the same name, and the
compiler picks one from the type of the argument:
The Fortran variable. hipFORT passes its address to the library. This is the usual call, for a scalar held on the host:
real(c_double) :: alpha, beta integer(c_size_t) :: buffer_size ! ierr = rocblas_dgemm(handle, transa, transb, m, n, k, alpha, & dA, lda, dB, ldb, beta, dC, ldc) ierr = rocsparse_dgemvi_buffer_size(handle, trans, m, n, nnz, buffer_size)
Its address, a
type(c_ptr). This is the C API’s own spelling. Use it to pass a device address, to passc_null_ptrfor an output the C API lets you skip, or to keep calling withc_loc:real(c_double), target :: alpha, beta ! ierr = rocblas_dgemm(handle, transa, transb, m, n, k, c_loc(alpha), & dA, lda, dB, ldb, c_loc(beta), dC, ldc)
An output the C API lets you skip is an optional argument of the first form.
For a routine that has such an argument (an X_opt specific), X_ is not
part of the generic: omit the argument to skip it rather than passing
c_null_ptr, and do not pass c_loc of it.
Device pointer mode#
alpha, beta and a few other scalars are read from host or device memory
depending on the pointer mode of the handle (rocblas_set_pointer_mode,
hipblasSetPointerMode, rocsparse_set_pointer_mode,
hipsparseSetPointerMode). The Fortran-variable form passes the address of the
variable, which is normally host memory, so it is for the host pointer mode, the
default. In device pointer mode, pass the device address as a type(c_ptr):
type(c_ptr) :: d_alpha, d_beta ! device memory, from hipMalloc
!
ierr = rocblas_set_pointer_mode(handle, rocblas_pointer_mode_device)
ierr = rocblas_dgemm(handle, transa, transb, m, n, k, d_alpha, &
dA, lda, dB, ldb, d_beta, dC, ldc)
A few rocSPARSE and hipSPARSE routines take both kinds of scalar, for example
rocsparse_dcsrgemm_buffer_size, which reads alpha and beta per the
pointer mode but always writes buffer_size on the host. In device pointer
mode, pass alpha and beta as type(c_ptr) and buffer_size as the
variable.
The Fortran 2008 array overloads take alpha and beta as Fortran variables
only. To use them in device pointer mode, the variables themselves must live on
the device, for example a real(c_double), pointer allocated with
hipMalloc; otherwise, call the type(c_ptr) form.
Device-only scalars#
hipSOLVER’s devInfo is written by the GPU, so it is always device memory.
So are rocSOLVER’s info and the other single values it writes, such as the
n_sweeps and residual of the Jacobi solvers or the nev of the
selective eigensolvers.
With
type(c_ptr)buffers, pass it as atype(c_ptr), for example the onehipMallocreturned. There is no Fortran-variable form here: a host integer would be written by the GPU.With the Fortran 2008 array overloads, pass either an integer that lives on the device, such as an
integer(c_int), pointerallocated withhipMalloc, like the arrays beside it, or atype(c_ptr).A batched routine writes one
infoper batch entry: pass aninteger(c_int)array ofbatch_countentries on the device, or itstype(c_ptr).
Note
These forms appear as the X_typed and X_devptr specifics in the API
reference; see Specific procedure names.
Assumed-rank interfaces (Fortran 2018)#
The Fortran 2008 array interfaces are generated once per rank, so each generic
carries a fixed set of ranks. Building hipFORT with
-DHIPFORT_ASSUMED_RANK=ON replaces those per-rank overloads with a single
Fortran 2018 dimension(..) overload that accepts an actual argument of any
rank. This is what lets a rank-3 array be passed to a routine whose per-rank
overloads stop at rank 1, such as rocblas_saxpy.
The option is experimental and OFF by default. Note the following:
It is mutually exclusive with the classic per-rank interfaces rather than additive: enabling it replaces them.
It requires a Fortran 2018 compiler (hipFORT probes for
c_loc()of adimension(..)argument) and the Fortran 2008 interfaces. If either is missing, hipFORT warns and falls back to the per-rank interfaces (or to thetype(c_ptr)interfaces only, if the Fortran 2008 interfaces are off) instead of failing the build.Only contiguous arrays may be passed.
hipMallockeeps its per-rank overloads either way;hipMemcpy,hipMemcpyAsync,hipMemcpy2DandhipMemcpy2DAsyncgain assumed-rank forms.
test/f2018/rocblas/saxpy.f90 is the one test written for this mode (it
passes a rank-3 array); it is only registered when the option is enabled, in
which case the test/f2008 programs also go through the assumed-rank
specifics. See the
rocBLAS examples for a walkthrough.