j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
175 stars 14 forks source link

Syntax for constraining the conversion of a C function pointer #322

Open ivan-pi opened 6 months ago

ivan-pi commented 6 months ago

Consider the following C code:

struct WrenLoadModuleResult;

typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result);

typedef struct WrenLoadModuleResult
{
  const char* source;
  WrenLoadModuleCompleteFn onComplete;
  void* userData;
} WrenLoadModuleResult;

A Fortran binding of the type and functions looks as follows:

type, bind(c) :: WrenLoadModuleResult
  type(c_ptr) :: source
  type(c_funptr) :: onComplete      ! procedure(WrenLoadModuleCompleteFn), pointer, nopass
  type(c_ptr) :: userData
end type

abstract interface
  subroutine WrenLoadModuleCompleteFn(vm,name,result) bind(c)
    type(c_ptr), value :: vm
    character(kind=c_char), intent(in) :: name(*)
    type(WrenLoadModuleResult), value :: result
  end subroutine
end interface

Notably, with the Fortran syntax, we can't capture the information about the procedure interface.

To use the onComplete function from the Fortran side, the caller has to manually associate the C function pointer with a Fortran procedure:

type(c_ptr) :: vm
character(kind=c_char,len=*), parameter :: name = "foo"//c_null_char

type(WrenLoadModuleResult) :: result
procedure(WrenLoadModuleCompleteFn), pointer :: f_onComplete

! create result
result = ...

call c_f_procpointer(cptr=result%onComplete, fptr=f_onComplete)
call f_onComplete( ... )

Would it be possible to introduce a syntax, e.g.

  type, bind(c) :: WrenLoadModuleResult
     ! ...
     type(c_funptr), interface(WrenLoadModuleCompleteFn), nopass :: onComplete
     ! ...
  end type

  ! ...

  call result%onComplete( ... )

which would save the manual trouble of converting the C to a Fortran procedure and thereby also guarantee a conversion to the right procedure interface?

FortranFan commented 6 months ago

@ivan-pi wrote Dec. 17, 2023 07:51 AM EST:

Would it be possible to introduce a syntax ..

@ivan-pi , are you inquiring in the context of the Fortran standard or a compiler-specific extension, say with LFortran and/or GCC gfortran? The latter is more amenable, say with LFortran, you will find a few engaged technical discussions with @certik and team can lead to a practical solution! However, as to whether that can be extended to other processors can be a massive/impossible hurdle.

When it comes to the standard, something like this appears to require a far better communication channel than what exists now re: the fundamentals between certain "subject-matter experts" (SMEs) on the standard committee and the practitioners and the user-community evangelists and which can lead to fruitful back-and-forth and eventually to pathways for enhanced evolution of the Fortran language facilities.

Meaning, consider the standard interoperability with a C companion processor in the standard starting Fortran 2003 thru' the latest 2023 revision. You will notice that effectively the core design of what is in the Fortran standard when it comes to interoperability between references of objects (function parameters, global entities, procedure addresses, etc.) processed by a Fortran processor and a companion C processor is via a void pointer e.g., type(c_ptr), type(c_funptr) with additional facilities - standard APIs such as c_f_pointer - permitting processor-specific mapping "under the hood".

Ostensibly, what you inquire of may involve a fundamental restructuring in the general context of a Fortran standard, especially because in C one can point to anything and "cast" with impunity and there is NOT much of built-in safety - see below:

typedef void (*Ifunc)(const char *);

typedef struct {
    Ifunc pfunc;
} foo_c;

void func();

void getfoo( foo_c str ) {
    str.pfunc = (Ifunc)func;  //<-- interface mismatch but no safety in the language; cast away with impunity
}

C:\temp>cl /c /W3 c.c Microsoft (R) C/C++ Optimizing Compiler Version 19.36.32537 for x64 Copyright (C) Microsoft Corporation. All rights reserved.

c.c

C:\temp>



So with the comment, "save the manual trouble of converting the C to a Fortran procedure and thereby also guarantee a conversion to the right procedure interface," this request appears to ask for a LOT such as a certain safety or reduced vulnerabilities in the practice of Fortran.

These are good goals but which require better engagement with those have an OUTSIZED influence on the standard development and language design and semantics such as the **3rd and 4th authors** in the NINTH edition of [**Modern Fortran Explained incorporating Fortran 2023**](https://books.google.com/books?id=8urkEAAAQBAJ&pg=PA443&dq=modern+fortran+explained+2023&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwi2st-g6IODAxVmF1kFHR3vBMIQ6AF6BAgFEAI#v=onepage&q=modern%20fortran%20explained%202023&f=false), both of whom will be key SMEs on such matters.
FortranFan commented 6 months ago
typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result);

@ivan-pi ,

Separately, you may want to recheck whether the last parameter here with WrenLoadModuleCompleteFn prototype definition is "pass-by-value".

Toward the recheck, an absolutely silly case like this might be helpful?

#include <stdio.h>

typedef void (*Ifunc)(const char *);

typedef struct {
    Ifunc pfunc;
} foo_c;
void func(const char *);

void getfoo( foo_c str ) {
    str.pfunc = (Ifunc)func;
}

void func(const char *s) {
    printf("func: %s\n", s);
}

int main( void ) {
    foo_c foo;
    foo.pfunc = NULL;
    getfoo( foo );
    if ( foo.pfunc ) {
        foo.pfunc("Hello World!");
    }
    else {
        printf("foo.pfunc is NULL.\n");
    }
    return 0; 
}

C:\temp>c.exe foo.pfunc is NULL.

C:\temp>


whereas if the pass-by-value is eshewed:
```c
#include <stdio.h>

typedef void (*Ifunc)(const char *);

typedef struct {
    Ifunc pfunc;
} foo_c;
void func(const char *);

void getfoo( foo_c *str ) {
    str->pfunc = (Ifunc)func;
}

void func(const char *s) {
    printf("func: %s\n", s);
}

int main( void ) {
    foo_c foo;
    foo.pfunc = NULL;
    getfoo( &foo );
    if ( foo.pfunc ) {
        foo.pfunc("Hello World!");
    }
    else {
        printf("foo.pfunc is NULL.\n");
    }
    return 0; 
}

C:\temp>c.exe func: Hello World!

C:\temp>