.. contents:: :local:
Fortran function and subroutine references are complicated. This document attempts to collect the requirements imposed by the 2018 standard (and legacy extensions) on programs and implementations, work through the implications of the various features, and propose both a runtime model and a compiler design.
All section, requirement, and constraint numbers herein pertain to the Fortran 2018 standard.
This note does not consider calls to intrinsic procedures, statement functions, or calls to internal runtime support library routines.
*
as its final dimension, which is the most-significant one in Fortran and whose value does not affect indexed address calculations.DIMENSION::A(:)
) is a POINTER
or ALLOCATABLE
. POINTER
target data might not be contiguous.DIMENSION::A(:)
) is a dummy argument that is neither POINTER
nor ALLOCATABLE
; its lower bounds can be set by the procedure that receives them (defaulting to 1), and its upper bounds are functions of the lower bounds and the extents of dimensions in the shape of the effective argument.CHARACTER(*)
dummy argument takes its length from the effective argument.CHARACTER(*)
result of an external function (C721) has its length determined by its eventual declaration in a calling scope.DIMENSION::A(..)
dummy argument array has an unknown number of dimensions.CLASS(t)
dummy argument, ALLOCATABLE
, or POINTER
has a specific derived type or some extension of that type. An unlimited polymorphic CLASS(*)
object can have any intrinsic or derived type.BIND(C)
procedures are written in C or callable from C.Referenced procedures may or may not have declared interfaces available to their call sites.
Procedures with some post-Fortran '77 features require an explicit interface to be called (15.4.2.2) or even passed (4.3.4(5)):
ELEMENTAL
or BIND(C)
PURE
due to the context of the call (specification expression, DO CONCURRENT
, FORALL
)ALLOCATABLE
, POINTER
, VALUE
, TARGET
, OPTIONAL
, ASYNCHRONOUS
, VOLATILE
, and, as a consequence of limitations on its use, CONTIGUOUS
; INTENT()
, however, does not require an explicit interfaceALLOCATABLE
or POINTER
CHARACTER
function result whose length is neither constant nor assumedLEN
type parameter value that is not constant (note that result derived type parameters cannot be assumed (C795))Module procedures, internal procedures, procedure pointers, type-bound procedures, and recursive references by a procedure to itself always have explicit interfaces. (Consequently, they cannot be assumed-length CHARACTER(*)
functions; conveniently, assumed-length CHARACTER(*)
functions are prohibited from recursion (15.6.2.1(3))).
Other uses of procedures besides calls may also require explicit interfaces, such as procedure pointer assignment, type-bound procedure bindings, &c.
Note that non-parameterized monomorphic derived type arguments do not by themselves require the use of an explicit interface. However, dummy arguments with any derived type parameters do require an explicit interface, even if they are all KIND
type parameters.
15.5.2.9(2) explicitly allows an assumed-length CHARACTER(*)
function to be passed as an actual argument to an explicit-length dummy; this has implications for calls to character-valued dummy functions and function pointers. (In the scopes that reference CHARACTER
functions, they must have visible definitions with explicit result lengths.)
In the absence of any characteristic or context that requires an explicit interface (see above), an external function or subroutine (R503) or ENTRY
(R1541) can be called directly or indirectly via its implicit interface. Each of the arguments can be passed as a simple address, including dummy procedures. Procedures that can be called via an implicit interface can undergo more thorough checking by semantics when an explicit interface for them exists, but they must be compiled as if all calls to them were through the implicit interface. This note will mention special handling for procedures that are exposed to the possibility of being called with an implicit interface as F77ish procedures below; this is of course not standard terminology.
Internal and module subprograms that are ever passed as arguments &/or assigned as targets of procedure pointers may be F77ish.
Every F77ish procedure can and must be distinguished at compilation time. Such procedures should respect the external naming conventions (when external) and any legacy ABI used for Fortran '77 programs on the target architecture, so that portable libraries can be compiled and used by distinct implementations (and their versions) of Fortran.
Note that F77ish functions still have known result types, possibly by means of implicit typing of their names. They can also be CHARACTER(*)
assumed-length character functions.
In other words: these F77sh procedures that do not require the use of an explicit interface and that can possibly be referenced, directly or indirectly, with implicit interfaces are limited to argument lists that comprise only the addresses of effective arguments and the length of a CHARACTER
function result (when there is one), and they can return only scalar values with constant type parameter values. None of their arguments or results need be (or can be) implemented with descriptors, and any internal procedures passed to them as arguments must be simple addresses of non-internal subprograms or trampolines for internal procedures.
Note that the INTENT
attribute does not, by itself, require the use of explicit interface; neither does the use of a dummy procedure (implicit or explicit in their interfaces). So the analysis of calls to F77ish procedures must allow for the invisible use of INTENT(OUT)
.
Here is a summary script of all of the actions that may need to be taken by the calling procedure and its referenced procedure to effect the call, entry, exit, and return steps of the procedure reference protocol. The order of these steps is not particularly strict, and we have some design alternatives that are explored further below.
POINTER
nor ALLOCATABLE
must have explicit shapes (C816).POINTER
, any ALLOCATABLE
, derived type with non-constant length parameters, &c.).%VAL()
and some discretionary VALUE
arguments) into registers.CHARACTER
argument lengths in additional value arguments for CHARACTER
effective arguments not passed via descriptors. These lengths must be 64-bit integers.CHARACTER
function result if the function is F77ish.ENTRY
points: shuffle ENTRY
dummy arguments set a compiler-generated variable to identify the alternate entry point, and jump to the common entry point for common processing and a switch()
to the statement after the ENTRY
.CHARACTER
argument &/or assumed-length result length values.VALUE
copying if this step will not always be done by the caller (as I think it should be).INTENT(OUT)
non-pointer effective arguments (see below).CONTIGUOUS
&/or explicit-shape/assumed-size arrays of assumed-length CHARACTER(*)
).TARGET
and not already sufficiently contiguous. (PGI does this in the caller, whether the callee needs it or not.)Execute the callee, populating the function result or selecting the subroutine's alternate return.
VALUE
argument temporaries. (But don't finalize them; see 7.5.6.3(3)).INTENT(IN)
or VALUE
).RETURN
to caller.VALUE
).GO TO
alternate return, if any.(I've omitted some obvious steps, like preserving/restoring callee-saved registers on entry/exit, dealing with caller-saved registers before/after calls, and architecture-dependent ABI requirements.)
There are several conditions that require the compiler to generate code that allocates and populates temporary storage for an actual argument.
First, effective arguments that are expressions, not designators, obviously need to be computed and captured into memory in order to be passed by reference. This includes parenthesized designators like (X)
, which are expressions in Fortran, as an important special case. (This case also technically includes unparenthesized constants, but those are better implemented by passing addresses in read-only memory.) The dummy argument cannot be known to have INTENT(OUT)
or INTENT(IN OUT)
.
Small scalar or elemental VALUE
arguments may be passed in registers, as should arguments wrapped in the legacy VMS %VAL()
notation. Multiple elemental VALUE
arguments might be packed into SIMD registers.
Effective arguments that are designators, not expressions, must also be copied into temporaries in the following situations.
ALLOCATABLE
components, which also need to be copied, along with their ALLOCATABLE
components, and may be best implemented with a runtime library routine working off a description of the type.VALUE
attribute need to be copied; this can be done on either side of the call, but there are optimization opportunities available when the caller's side bears the responsibility.VALUE
dummy arguments with the attributes INTENT(OUT)
, INTENT(IN OUT)
, ASYNCHRONOUS
, or VOLATILE
(15.5.2.4(21)); INTENT()
can't always be checked.POINTER
dummy arguments that must be contiguous (due to a CONTIGUOUS
attribute, or not being assumed-shape or assumed-rank; this is always the case for F77ish procedures). This should be a runtime decision, so that effective arguments that turn out to be contiguous can be passed cheaply. This rule does not apply to coarray dummies, whose effective arguments are required to be simply contiguous when this rule would otherwise force the use of a temporary (15.5.2.8); neither does it apply to ASYNCHRONOUS
and VOLATILE
effective arguments, which are disallowed when copies would be necessary (C1538 - C1540). Only temporaries created by this contiguity requirement are candidates for being copied back to the original variable after the call (see below).Fortran requires (18.3.6(5)) that calls to interoperable procedures with dummy argument arrays with contiguity requirements handle the compaction of discontiguous data in the Fortran callee, at least when called from C. And discontiguous data must be compacted on the caller's side when passed from Fortran to C (18.3.6(6)).
We could perform all argument compaction (discretionary or required) in the callee, but there are many cases where the compiler knows that the effective argument data are contiguous when compiling the caller (a temporary is needed for other reasons, or the effective argument is simply contiguous) and a run-time test for discontiguity in the callee can be avoided by using a caller-compaction convention when we have the freedom to choose.
While we are unlikely to want to needlessly use a temporary for an effective argument that does not require one for any of these reasons above, we are specifically disallowed from doing so by the standard in cases where pointers to the original target data are required to be valid across the call (15.5.2.4(9-10)). In particular, compaction of assumed-shape arrays for discretionary contiguity on the leading dimension to ease SIMD vectorization cannot be done safely for TARGET
dummies without VALUE
.
Effective arguments associated with known INTENT(OUT)
dummies that require allocation of a temporary -- and this can only be for reasons of contiguity -- don‘t have to populate it, but they do have to perform minimal initialization of any ALLOCATABLE
components so that the runtime doesn’t crash when the callee finalizes and deallocates them. ALLOCATABLE
coarrays are prohibited from being affected by INTENT(OUT)
(see C846). Note that calls to implicit interfaces must conservatively allow for the use of INTENT(OUT)
by the callee.
Except for VALUE
and known INTENT(IN)
dummy arguments, the original contents of local designators that have been compacted into temporaries could optionally have their ALLOCATABLE
components invalidated across the call as an aid to debugging.
Except for VALUE
and known INTENT(IN)
dummy arguments, the contents of the temporary storage will be copied back into the effective argument designator after control returns from the procedure, and it may be necessary to preserve addresses (or the values of subscripts and cosubscripts needed to recalculate them) of the effective argument designator, or its elements, in additional temporary storage if they can't be safely or quickly recomputed after the call.
INTENT(OUT)
preparationEffective arguments that are associated with INTENT(OUT)
dummy arguments are required to be definable. This cannot always be checked, as the use of INTENT(OUT)
does not by itself mandate the use of an explicit interface.
INTENT(OUT)
arguments are finalized (as if) on entry to the called procedure. In particular, in calls to elemental procedures, the elements of an array are finalized by a scalar or elemental FINAL
procedure (7.5.6.3(7)).
Derived type components that are ALLOCATABLE
are finalized and deallocated; they are prohibited from being coarrays. Components with initializers are (re)initialized.
The preparation of effective arguments for INTENT(OUT)
could be done on either side of the call. If the preparation is done by the caller, there is an optimization opportunity in situations where unmodified incoming INTENT(OUT)
dummy arguments whose types lack FINAL
procedures are being passed onward as outgoing INTENT(OUT)
arguments.
Dummy arguments are represented with the addresses of new descriptors when they have any of the following characteristics:
DIMENSION::A(:)
)DIMENSION::A(..)
)LEN
parametersCLASS(T)
, CLASS(*)
)TYPE(*)
)INTENT(IN) POINTER
argument (15.5.2.7, C.10.4)ALLOCATABLE
and other POINTER
arguments can be passed by simple address.
Non-F77ish procedures use descriptors to represent two further kinds of dummy arguments:
CHARACTER(*)
F77ish procedures use other means to convey character length and host instance links (respectively) for these arguments.
Function results are described by the caller & callee in a caller-supplied descriptor when they have any of the following characteristics, some which necessitate an explicit interface:
ALLOCATABLE
or POINTER
)LEN
parameter (C795 prohibit assumed lengths)Storage for a function call‘s result is allocated by the caller when possible: the result is neither ALLOCATABLE
nor POINTER
, the shape is scalar or explicit, and the type has LEN
parameters that are constant expressions. In other words, the result doesn’t require the use of a descriptor but can't be returned in registers. This allows a function result to be written directly into a local variable or temporary when it is safe to treat the variable as if it were an additional INTENT(OUT)
argument. (Storage for CHARACTER
results, assumed or explicit, is always allocated by the caller, and the length is always passed so that an assumed-length external function will work when eventually called from a scope that declares the length that it will use (15.5.2.9 (2)).)
Note that the lower bounds of the dimensions of non-POINTER
non-ALLOCATABLE
dummy argument arrays are determined by the callee, not the caller. (A Fortran pitfall: declaring A(0:9)
, passing it to a dummy array D(:)
, and assuming that LBOUND(D,1)
will be zero in the callee.) If the declaration of an assumed-shape dummy argument array contains an explicit lower bound expression (R819), its value needs to be computed by the callee; it may be captured and saved in the incoming descriptor as long as we assume that argument descriptors can be modified by callees. Callers should fill in all of the fields of outgoing non-POINTER
non-ALLOCATABLE
argument descriptors with the assumption that the callee will use 1 for lower bound values, and callees can rely on them being 1 if not modified.
Except for VALUE
and known INTENT(IN)
dummy arguments and array sections with vector-valued subscripts (15.5.2.4(21)), temporary storage into which effective argument data were compacted for contiguity before the call must be redistributed back to its original storage by the caller after the return.
In conjunction with saved cosubscript values, a standard descriptor would suffice to represent a pointer to the original storage into which the temporary data should be redistributed; the descriptor need not be fully populated with type information.
Note that coindexed objects with ALLOCATABLE
ultimate components are required to be associated only with dummy arguments with the VALUE
&/or INTENT(IN)
attributes (15.6.2.4(6)), so there is no requirement that the local image somehow reallocate remote storage when copying the data back.
Calls to the type-bound procedures of monomorphic types are resolved at compilation time, as are calls to NON_OVERRIDABLE
type-bound procedures. The resolution of calls to overridable type-bound procedures of polymorphic types must be completed at execution (generic resolution of type-bound procedure bindings from effective argument types, kinds, and ranks is always a compilation-time task (15.5.6, C.10.6)).
Each derived type that declares or inherits any overridable type-bound procedure bindings must correspond to a static constant table of code addresses (or, more likely, a static constant type description containing or pointing to such a table, along with information used by the runtime support library for initialization, copying, finalization, and I/O of type instances). Each overridable type-bound procedure in the type corresponds to an index into this table.
Calls to dummy procedures and procedure pointers that resolve to internal procedures need to pass an additional “host instance” argument that addresses a block of storage in the stack frame of their host subprogram that was active at the time they were passed as an effective argument or associated with a procedure pointer. This is similar to a static link in implementations of programming languages with nested subprograms, although Fortran only allows one level of nesting. The 64-bit x86 and little-endian OpenPower ABIs reserve registers for this purpose (%r10
& R11
); 64-bit ARM has a reserved register that can be used (x18
).
The host subprogram objects that are visible to any of their internal subprograms need to be resident in memory across any calls to them (direct or not). Any host subprogram object that might be defined during a call to an internal subprogram needs to be reloaded after a call or reside permanently in memory. A simple conservative analysis of the internal subprograms can identify all of these escaping objects and their definable subset.
The address of the host subprogram storage used to hold the escaping objects needs to be saved alongside the code address(es) that represent a procedure pointer. It also needs to be conveyed alongside the text address for a dummy procedure.
For F77ish procedures, we cannot use a “procedure pointer descriptor” to pass a procedure argument -- they expect to receive a single address argument. We will need to package the host instance link in a trampoline that loads its address into the designated register.
GNU Fortran and Intel Fortran construct trampolines by writing a sequence of machine instructions to a block of storage in the host's stack frame, which requires the stack to be executable, which seems inadvisable for security reasons; XLF manages trampolines in its runtime support library, which adds some overhead to their construction and a reclamation obligation; NAG Fortran manages a static fixed-sized stack of trampolines per call site, imposing a hidden limit on recursion and foregoing reentrancy; PGI passes host instance links in descriptors in additional arguments that are not always successfully forwarded across implicit interfaces, sometimes leading to crashes when they turn out to be needed.
F18 will manage a pool of trampolines in its runtime support library that can be used to pass internal procedures as effective arguments to F77ish procedures, so that a bare code address can serve to represent the effective argument. But targets that can only be called with an explicit interface have the option of using a “fat pointer” (or additional argument) to represent a dummy procedure closure so as to avoid the overhead of constructing and reclaiming a trampoline. Procedure descriptors can also support multiple code addresses.
External subroutines and functions (R503) and ENTRY
points (R1541) with BIND(C)
(R808) have linker-visible names that are either explicitly specified in the program or determined by straightforward rules. The names of other F77ish external procedures should respect the conventions of the target architecture for legacy Fortran '77 programs; this is typically something like foo_
.
In other cases, however, we have fewer constraints on external naming, as well as some additional requirements and goals.
Module procedures need to be distinguished by the name of their module and (when they have one) the submodule where their interface was defined. Note that submodule names are distinct in their modules, not hierarchical, so at most two levels of qualification are needed.
Pure ELEMENTAL
functions (15.8) must use distinct names for any alternate entry points used for packed SIMD arguments of various widths if we support calls to these functions in SIMD parallel contexts. There are already conventions for these names in libpgmath
.
The names of non-F77ish external procedures should be distinguished as such so that incorrect attempts to call or pass them with an implicit interface will fail to resolve at link time. Fortran 2018 explicitly enables us to do this with a correction to Fortran 2003 in 4.3.4(5).
Last, there must be reasonably permanent naming conventions used by the F18 runtime library for those unrestricted specific intrinsic functions (table 16.2 in 16.8) and extensions that can be passed as arguments.
In these cases where external naming is at the discretion of the implementation, we should use names that are not in the C language user namespace, begin with something that identifies the current incompatible version of F18, the module, the submodule, and elemental SIMD width, and are followed by the external name. The parts of the external name can be separated by some character that is acceptable for use in LLVM IR and assembly language but not in user Fortran or C code, or by switching case (so long as there‘s a way to cope with extension names that don’t begin with letters).
In particular, the period (.
) seems safe to use as a separator character, so a Fa.
prefix can serve to isolate these discretionary names from other uses and to identify the earliest link-compatible version. For examples: Fa.mod.foo
, Fa.mod.submod.foo
, and (for an external subprogram that requires an explicit interface) Fa.foo
. When the ABI changes in the future in an incompatible way, the initial prefix becomes Fb.
, Fc.
, &c.
8.5.10 INTENT
attributes
INTENT(OUT)
argument shall not be associated with an object that is or has an allocatable coarray.INTENT(OUT)
argument shall not have LOCK_TYPE
or EVENT_TYPE
.8.5.18 VALUE
attribute
ALLOCATABLE
, POINTER
, INTENT(OUT)
, INTENT(IN OUT)
, or VOLATILE
.BIND(C)
, the argument cannot be OPTIONAL
.15.5.1 procedure references:
ELEMENTAL
as argumentPOINTER
ultimate component15.5.2.4 requirements for non-POINTER
non-ALLOCATABLE
dummies:
TYPE(*)
if effective is PDT or has TBPs or FINAL
ALLOCATABLE
ultimate component requires INTENT(IN)
&/or VALUE
dummyINTENT(OUT)
& INTENT(IN OUT)
dummies require definable actualsINTENT(OUT)
, INTENT(IN OUT)
, ASYNCHRONOUS
, VOLATILE
)VOLATILE
attributes must match when dummy has a coarray ultimate componentASYNCHRONOUS
and VOLATILE
15.5.2.5 requirements for ALLOCATABLE
& POINTER
arguments when both the dummy and effective arguments have the same attributes:
15.5.2.6 ALLOCATABLE
dummy arguments:
ALLOCATABLE
INTENT(IN)
dummyINTENT(OUT)
& INTENT(IN OUT)
dummies require definable actuals15.5.2.7 POINTER
dummy arguments:
CONTIGUOUS
dummy requires simply contiguous actualPOINTER
unless dummy is INTENT(IN)
and effective could be the right-hand side of a pointer assignment statement15.5.2.8 corray dummy arguments:
VOLATILE
attributes must match15.5.2.9 dummy procedures:
POINTER
requirements on effective arguments15.6.2.1 procedure definitions:
NON_RECURSIVE
procedures cannot recurse.CHARACTER(*)
functions cannot be declared as RECURSIVE
, array-valued, POINTER
, ELEMENTAL
, or `PURE' (C723), and cannot be called recursively (15.6.2.1(3)).PURE
requirements (15.7): C1583 - C1599. These also apply to ELEMENTAL
procedures that are not IMPURE
.
ELEMENTAL
requirements (15.8.1): C15100-C15103, and C1533 (can't pass as effective argument unless intrinsic)
For interoperable procedures and interfaces (18.3.6):
VALUE
arguments are scalar and of interoperable typePOINTER
dummies cannot be CONTIGUOUS
(18.3.6 paragraph 2(5))ALLOCATABLE
, POINTER
, assumed-shape, or assumed-rank (18.3.6 paragraph 2 (5))CHARACTER
dummies that are ALLOCATABLE
or POINTER
must be deferred-length%VAL()
, %REF()
, and %DESCR()
legacy VMS interoperability extensionsELEMENTAL
procedures (& unrestricted specific intrinsics)