Search code examples
fortrangfortranintel-fortranfortran-iso-c-binding

Fortran C-interoperable submodule procedure with bind(C) reports error when compiled by gfortran


Consider the following Fortran module Foo_mod and its submodule Foo_smod,

module CallbackInterface_mod

    abstract interface
        function getLogFunc4C_proc(ndim,Point) result(logFunc) bind(C)
            use, intrinsic :: iso_c_binding, only : c_int32_t, c_double, c_int
            integer(c_int32_t), intent(in)  :: ndim
            real(c_double), intent(in)      :: Point(ndim)
            real(c_double)                  :: logFunc
        end function getLogFunc4C_proc
    end interface

end module CallbackInterface_mod

!***********************************************************************************************************************************
!***********************************************************************************************************************************

module Foo_mod

    interface
    module subroutine runFoo4C(ndim, getLogFuncFromC, inputString, inputStringLen) bind(C, name="runFoo")
        use, intrinsic :: iso_c_binding, only: c_int32_t, c_char, c_funptr, c_f_procpointer, c_size_t
        use CallbackInterface_mod, only: getLogFunc4C_proc
        implicit none
        integer(c_int32_t) , intent(in)                         :: ndim
        character(len=1, kind=c_char), dimension(*), intent(in) :: inputString
        integer(c_size_t) , intent(in)                          :: inputStringLen
        type(c_funptr), intent(in), value                       :: getLogFuncFromC
    end subroutine runFoo4C
    end interface

contains

    subroutine runFoo(ndim, getLogFunc, string)
        use CallbackInterface_mod, only: getLogFunc4C_proc
        use, intrinsic :: iso_fortran_env, only: RK => real64
        implicit none
        integer :: ndim
        procedure(getLogFunc4C_proc)    :: getLogFunc
        character(*), intent(in)        :: string
        real(RK)                        :: Point(ndim)
        character(:), allocatable       :: mystring
        Point = [1._RK,1._RK]
        write(*,*) "Hi again, this is a call from inside runFoo!"
        write(*,*) "getLogFunc(2,[1,1]) = ", getLogFunc(ndim,Point)
        write(*,*) "string = ", string
    end subroutine

end module Foo_mod

!***********************************************************************************************************************************
!***********************************************************************************************************************************

submodule (Foo_mod) Foo_smod

contains

    module subroutine runFoo4C(ndim, getLogFuncFromC, InputString, inputStringLen) bind(C, name="runFoo")

        use, intrinsic :: iso_c_binding, only: c_double, c_int32_t, c_char, c_funptr, c_f_procpointer, c_size_t
        use CallbackInterface_mod, only: getLogFunc4C_proc
        implicit none
        integer(c_int32_t) , intent(in)                         :: ndim
        character(len=1, kind=c_char), dimension(*), intent(in) :: InputString
        integer(c_size_t) , intent(in)                          :: inputStringLen
        type(c_funptr), intent(in), value                       :: getLogFuncFromC
        procedure(getLogFunc4C_proc), pointer                   :: getLogFunc
        real(c_double)                                          :: Point(ndim)
        character(:), allocatable                               :: inputString4tran
        integer                                                 :: i

        write(*,*) "InputString: ", InputString(1:inputStringLen)
        allocate( character(len=inputStringLen) :: inputString4tran )
        do i=1,inputStringLen
            inputString4tran(i:i) = InputString(i)
        end do
        write(*,*) "inputString4tran: ", inputString4tran

        ! associate the input C procedure pointer to a Fortran procedure pointer
        call c_f_procpointer(cptr=getLogFuncFromC, fptr=getLogFunc)
        Point = [1._c_double, 1._c_double]
        write(*,*) "getLogFunc(ndim=2, [1._c_double, 1._c_double]): ", getLogFunc( ndim, Point )

        call runFoo(ndim, getLogFunc, inputString4tran)

    end subroutine runFoo4C

end submodule Foo_smod

Now, this code seems like a perfectly sound Fortran module to me, and indeed it does compile and link successfully to C programs via Intel Fortran Compiler 2018, on both Windows and Linux, and runs correctly. However, when compiled by gfortran 7.3.0, it gives the following error:

gfortran -c ../Foo_mod.f90
../Foo_mod.f90:54:18:

submodule (Foo_mod) Foo_smod
                1
Error: BIND(C) attribute at (1) can only be used for variables or common blocks

The problem can be resolved if I remove the bind(c) attribute from the interface of the interoperable subroutine runFoo4C() in module Foo_mod, like the following,

...
module subroutine runFoo4C(ndim, getLogFuncFromC, inputString, inputStringLen) ! bind(C, name="runFoo")
...

However, the Intel Fortran compiler would then complain about the incompatibility of the subroutine's interface in module Foo_mod and the implementation of the subroutine in submodule Foo_smod. This seems to me more like a bug in GFORTRAN, than programming mistake. But your comments are appreciated before I go on to report it.


Solution

  • It is quite clear to me that this is a gfortran bug related to submodules. As that's a fairly recent feature addition, it's not unexpected that there may be bugs here and there.

    I will comment that when asking a question such as this here, you are strongly encouraged to provide a Minimal, Complete and Verifiable Example Since you don't show the module that the submodule goes with, we can't verify the issue ourselves without unnecessary extra work.