Response on the TR29113 draft N1761 References: ftp://ftp.nag.co.uk/sc22wg5/N1751-N1800/N1761.txt http://j3-fortran.org/doc/meeting/186/08-295.txt My response is "NO, with comments". The comments are provided below in the form of a list of issues, examples and suggestions. The minimum required to make me say YES is: * Issues 1, 2, 4 and 6 must be fixed. Issue 4 may be regarded as a showstopper by those from the MPI Forum who require existing infrastructure to be preserved, while providing improved Fortran interfaces. Issue 1 - missing examples for usage of descriptors: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I've tried to supply one which could be added to the draft; it is also used here to illustrate further TR issues. Example 1: C calling a Fortran implementation module mod_foo use, intrinsic :: iso_c_binding type, bind(c) :: foo integer(c_int) :: i real(c_float) :: r(3) character(c_int) :: c end type contains subroutine construct_foo(this, n, i, r, c) bind(c) ! bind(c) extended for TR29113 type(foo), allocatable, intent(out) :: this(:) integer(c_int), intent(in) :: n, i(n) real(c_float), intent(in) :: r(3, n) character(c_char), intent(in) :: c(n) : ! code for constructing this end subroutine end module C client code: #include /* interface */ void construct_foo(void *, int, int *, float *, char *); /* matching type definition */ typedef struct { int i; float r[3]; char c; } Foo; int main() { CFI_desc_t *my_desc; type Foo *array_elem; int :: istatus; int i[4] = { 1, 2, 3, 4 }; float r[4][3] = { { 1.0, 1.1, 1.2 }, { 2.0, 2.1, 2.2 }, { 3.0, 3.1, 3.2 }, { 4.0, 4.1, 4.2 } }; char c[4] = { 'a', 'b', 'c', 'd' }; my_desc = (CFI_desc_t *) malloc(sizeof(CFI_desc_t)); my_desc->base_addr = NULL; my_desc->elem_len = sizeof(Foo); /* may need Fortran call to C_SIZEOF if unknown */ my_desc->rank = 1; my_desc->type = CFI_type_struct; my_desc->attribute = CFI_attribute_allocatable; my_desc->state = 0; my_desc->fdesc = NULL; istatus = CFI_update_fdesc(my_desc); /* make attributes known to processor */ construct_foo(my_desc->fdesc, 4, i, r, c); istatus = CFI_update_cdesc(my_desc); /* update attributes for C program */ /* align 3rd element to C scalar of matching type. (for contiguous Fortran arrays one could also align a C array) */ array_elem = (Foo *)(base_addr + 2 * sizeof(Foo)); printf("Character component of 3rd element: %s\n",array_elem->c); istatus = CFI_deallocate(my_desc); free(my_desc); } Issue 2 - polymorphism of assumed-type entity: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since no changes to the definition of C_LOC() have been introduced, and this function is used to cast an object of TYPE(*) to a usable type, the text beginning in line 92 of N1761 should be replaced by "In the association of actual and dummy arguments, an assumed-type dummy argument is type and kind compatible with a non-polymorphic actual data argument of any type." Issue 3 - interface of CFI_is_contiguous: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I'd suggest _Bool CFI_is_contiguous(const CFI_cdesc_t *) and have it return false if a dynamic object is not allocated. The typical usage would then be more concise: if (mydesc->state && CFI_is_contiguous(mydesc)) ... Issue 4 - rank matching for generics: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The example provided in line 552-575 of N1761 is not conforming, since on of the uses of the interface contravenes p290, para 13 of the F2008 draft standard 08-007r2, which disallows matching of actual arguments of arbitrary ranks to the assumed size dummy of rank one for a generic interface. Either one must give up using a generic interface, in which case two separate calls must be provided for the scalar and array arguments, or a feature must be added to the language which allows to pass an argument of arbitrary rank by address / sequence association. Suggestion 2 below tries to provide an extension which solves this problem. Issue 5 - RANK intrinsic: ~~~~~~~~~~~~~~~~~~~~~~~~~~ Is this really needed considering we already have SIZE(SHAPE(X))? Issue 6 - referencing or defining assumed rank entities: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We need some rules to deal with this. Unless some checks normally performed at compile time are deferred to run time, introducing a SELECT RANK block construct might be appropriate. real, dimension(::) :: a real, pointer :: p(:) select rank (a) case(0) a = 1. case(1) a(:) = [ ... ] default ... ! no references or definitions of a allowed here end select It might also be feasible to allow a pointer of rank 1 to point at such an object: p => a if (i < size(a)) then ... = p(i) end if This would however imply a re-interpretation of scalar actuals as rank 1 entities of size 1. Issue 7 - non-interoperable types: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While I've heard it said that coverage of non-interoperable types was not the purpose of this TR, I wonder whether this is not implicitly already contained, at least for non-interoperable entities with following properties: * non-polymorphic, non-sequence * allocatable or pointer type components * ultimate type components of interoperable intrinsic type Assumed-shape/assumed-rank/allocatable/pointer, scalars and arrays of this type would be acceptable as dummy arguments, based on the existing capabilities of the descriptors. [since BIND is not a characteristic of a dummy argument, normal scalars may be a problem. Since allocatable scalars or pointer scalars can be used, it is not really a big problem.] So, if the type definition from example 1 above were replaced by type :: foo integer(c_int) :: i real(c_float), allocatable :: r(:) character(c_int) :: c end type the unpacking process in C would need to use a type definition typedef struct { int i; void *fdesc; char c; } Foo; Once an object of this type is mapped to an array element, either CFI_update_cdesc() can be used to access the type component, or (if Suggestion 1 below is adopted), the CFI_create_desc() constructor with a non-NULL last argument. [If type fields and dummy arguments use different descriptors, it may be necessary to introduce another component of CFI_desc_t, say tf_desc.] Issue 8 - C variable argument lists: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ with the changes introduced in 08-295 it should now also be possible to provide support for vararg interfaces by matching to specifics of a Fortran generic interface. The only obstruction to this would be dealing with c_char arguments; this however might also be solved by Suggestion 2 below. Suggestion 1 (design change interface creation): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is an attempt to formulate an alternative to the descriptor usage (and, to a lesser extent, design) described in N1761. The idea is to * improve proper separation of concerns * require the processor to auto-generate code which under the present proposal must be inserted manually, and thereby improve usability. After a first description I'll attempt to illustrate the advantages compared to N1761 via example programs. Since the attributes presently not supported for interoperable procedure arguments (assumed shape, pointer, allocatable, and perhaps also non-interoperable types) are characteristics, the processor is in principle capable of disambiguating interfaces using these features from those previously available for C interoperation. Hence I do not consider it necessary to provide a separate attribute with the BIND statement. (A) C calling a Fortran implementation: The matching C interface is replaced by an auto-generated mapped C interface. For example 1, this would be void construct_foo(CFI_desc_t *, int, int *, float *, char *); It would be the responsibility of the processor to * generate a local name by which the procedure name is known in Fortran * generate a global name based on the default label of the Fortran entity, unless a binding label is specified. (These two items could be handled analogously to the treatment of Fortran external procedures with no binding label (08-295)). * automatically generate a wrapper which performs any updates covered by CFI_update_fdesc in the present design, unwraps the entity CFI_desc_t and hands on the Fortran descriptor field to the Fortran-local procedure call. A direct call from Fortran to the global C name is non-conforming since no type matching CFI_desc_t is available within Fortran; the processor should prevent an interface attempting this from being created. * after return from the Fortran-local procedure performs any updates which are covered by CFI_update_cdesc in the present design. It is further suggested to provide a constructor (and associated destructor) function for the descriptor itself which has as its arguments those entities the programmer is responsible for. The call to the constructor would replace the simple malloc() and the immediately following field settings in the example 1 above. The necessary creation of the fdesc field should also be performed in this constructor unless a non-NULL value is supplied in the last argument, replacing this functionality in CFI_update_fdesc(). If a non-NULL value if provided for fdesc, a consistent C descriptor is created, ignoring the other arguments. Here the interfaces: CFI_desc_t *CFI_create_desc(size_t elem_len, int rank, int type, int attribute, void *fdesc); void CFI_destroy_desc(CFI_desc_t *cdesc, _Bool destroy_fdesc); and two constants for readability: CFI_destroy_fdesc (_Bool with value true) CFI_keep_fdesc (_Bool with value false) In contrast to statically determined Fortran code the constructor sets the attribute, type and rank of an object at run time. Do we oblige the processor to update this information if inconsistent with the defined Fortran interface? All other type fields of a CFI_desc_t object should not be explicitly set by the user, except for other calls to the processor-defined routines. The API calls CFI_update_fdesc() and CFI_update_cdesc() should be removed. The net amount of function call overhead would stay the same, but usability is improved at least for the real inter-language case. The C main() of example 1 above would thus read int main() { CFI_desc_t *my_desc; type Foo *array_elem; int :: istatus; int i[4] = { 1, 2, 3, 4 }; float r[4][3] = { { 1.0, 1.1, 1.2 }, { 2.0, 2.1, 2.2 }, { 3.0, 3.1, 3.2 }, { 4.0, 4.1, 4.2 } }; char c[4] = { 'a', 'b', 'c', 'd' }; my_desc = CFI_create_desc(sizeof(Foo), 1, CFI_type_struct, CFI_attribute_allocatable, NULL); construct_foo(my_desc, 4, i, r, c); /* align 3rd element to C scalar of matching type. (for contiguous Fortran arrays one could also align a C array) */ array_elem = (Foo *)(base_addr + 2 * sizeof(Foo)); printf("Character component of 3rd element: %s\n",array_elem->c); istatus = CFI_deallocate(my_desc); CFI_destroy_desc(my_desc, CFI_destroy_fdesc); } (B) Fortran calling a C implementation: The wrapper generated from the interface * packs up the Fortran descriptor into a C descriptor upon call to the local Fortran name, which is automatically generated for this purpose * executes the C routine and once the C routine has finished * updates the Fortran descriptor, and then deallocates the C descriptor without destroying the Fortran descriptor. Run time checks may be required to assure no incompatibilities with statically defined properties have been introduced. It is not intended that the Fortran local name be dereferenced from C. A C program calling the C interface (untypical) would need to perform the construction and allocation, deallocation and destruction of descriptors manually before and after the call, respectively. As an example, suppose we were to implement a modern MPI Interface. Example 2: module mpi use, intrinsic :: iso_c_binding implicit none private ! various C interoperable opaque type definitions not shown here public :: mpi_send interface subroutine mpi_send(buf, datatype, elem_size, dest, tag, comm, ierror) bind(c) type(*), dimension(..), contiguous, intent(in) :: buf type(mpi_datatype), intent(in) :: datatype integer(c_size_t), optional, intent(in) :: elem_size integer(c_int), intent(in) :: dest, ierror integer(c_size_t), optional, intent(in) :: tag type(mpi_comm), intent(in) :: comm end subroutine end interface end module The C implementation could look something like this: #include void mpi_send(CFI_desc_t *buf, MPI_Ftype *datatype, size_t *elem_size, int *dest, size_t *tag, MPI_Comm *comm, int *ierror) { int local_size = 0; /* bytes */ int i; void *local_buf; if (buf->state == 0) { *ierror = ...; /* may want to send length 0 buffer? */ return; } if (MPI_Ftype->MPI_Datatype == MPI_INT) local_size = 4; if (MPI_Ftype->MPI_Datatype == MPI_FLOAT) local_size = ...; /* etc. */ if (elem_size != NULL && MPI_Ftype->MPI_Datatype == MPI_DERIVED) { local_size = *elem_size; } if (local_size == 0) { *ierror = ...; return; } for (i=0; irank) { local_count *= buf->dim[i]->extent; } *ierror = MPI_Send(buf->base_addr, local_count, MPI_BYTE, *dest, *tag, *comm); } this single call would cover * scalars and contiguous arrays of arbitrary rank (is "contiguous" unambiguous here?) * all intrinsic types defined in MPI * all C interoperable types (with a static type structure, and in a slightly less safe manner than when using the MPI datatype constructors) A C implementation might for example be of advantage over a Fortran one if it is easier to obtain certain type-internal information from C (e.g., the MPI_Ftype->MPI_Datatype dereferences). Note that this also illustrates that OPTIONAL arguments can also be handled by the wrapping process (i.e., non-present arguments will be set NULL on the call to the C entity). In particular, the problem with having the combination of OPTIONAL and VALUE (not used in this example) attributes vanishes (for non-c_ptr types a convention may be needed). Suggestion 2 (add assumed-rank-and-size): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ in analogy to assumed rank (DIMENSION(::)), which one might also call assumed-rank-and-shape, a dummy variable can be declared with type(foo), DIMENSION(**) :: dummy A corresponding actual argument which is either a scalar or an arbitrary rank array of type foo would match this dummy argument and it would be allowed to have exactly one corresponding dummy with this attribute in a specific procedure of a generic interface when all other dummies have the same characteristics. The only allowed method of dereferencing or defining such an object from within Fortran would be via one dimensional indexing: dummy(i) = ... A BIND(C) interface may specify dummy arguments that are assumed-rank-and-size. If the dummy argument is assumed-rank-and-size, the actual argument is passed as its C address. Otherwise, the restrictions on this kind of argument will be essentially the same as those for assumed-size entities. Example 2a: extending the generic interface of example 1 for old-style calls. module mpi use, intrinsic :: iso_c_binding implicit none private ! various C interoperable opaque type definitions not shown here public :: mpi_send interface subroutine mpif_send(buf, count, datatype, dest, tag, comm, ierror) & bind(c, name='MPIF_Send') type(*), dimension(**), intent(in) :: buf integer(c_int) :: dataype, dest, tag, comm, ierror end subroutine subroutine mpi_send(buf, datatype, elem_size, dest, tag, comm, ierror) bind(c) type(*), dimension(..), contiguous, intent(in) :: buf type(mpi_datatype), intent(in) :: datatype integer(c_size_t), optional, intent(in) :: elem_size integer(c_int), intent(in) :: dest, ierror integer(c_size_t), optional, intent(in) :: tag type(mpi_comm), intent(in) :: comm end subroutine end interface end module Note however that the ierror argument is still Fortran-style, so a direct mapping to the C MPI_Send routine is not possible. With the old-style call, derived datatypes must be dealt with via the MPI datatype construction routines; apart from that the same functionality is available as for the new call, only in a slightly less type-safe manner. Additional notes: * processing of C interoperable string entities would be straightforward using this, making the special rule introduced for this case in F2003 superfluous. * the added bonus is that this also supports using character arguments in generic interfaces.