Accessing Fortran derived types from Julia

Hello everyone. I’ve been performing some tests to try and port some functionality from a Fortran module to Julia.

While testing out some of Julia’s interoperability features with Fortran I have run into some issues when dealing with Fortran derived types with nested derived types. My objective is to initialize the structure in Fortran and then access the information from Julia.

I have the following Fortran code:

MODULE derived_types
    USE iso_c_binding
    IMPLICIT NONE

    TYPE :: Point
        INTEGER :: x, y, z
    END TYPE Point

    TYPE :: ContainerPointerDerived
        TYPE(Point), POINTER :: x(:)
    END TYPE ContainerPointerDerived
        
    TYPE :: ContainerAllocatableDerived
        TYPE(Point), ALLOCATABLE :: x(:)
    END TYPE ContainerAllocatableDerived

    CONTAINS
        SUBROUTINE initialize_point(p, i) BIND(C)
            TYPE(Point), INTENT(INOUT) :: p
            INTEGER, INTENT(IN) :: i
            p%x = i
            p%y = i * 2
            p%z = i * 4
        END SUBROUTINE initialize_point

        SUBROUTINE initialize_container_pointer_derived(c, l) BIND(C)
            TYPE(ContainerPointerDerived), INTENT(INOUT) :: c
            INTEGER, INTENT(IN) :: l
            INTEGER :: i
            ALLOCATE(c%x(l))
            DO i = 1, l
                CALL initialize_point(c%x(i), i)
            END DO
        END SUBROUTINE initialize_container_pointer_derived

        SUBROUTINE initialize_container_allocatable_derived(c, l) BIND(C)
            TYPE(ContainerAllocatableDerived), INTENT(INOUT) :: c
            INTEGER, INTENT(IN) :: l
            INTEGER :: i
            ALLOCATE(c%x(l))
            DO i = 1, l
                CALL initialize_point(c%x(i), i)
            END DO
        END SUBROUTINE initialize_container_allocatable_derived

END MODULE derived_types

And the following Julia code:

mutable struct Point
    x::Cint
    y::Cint
    z::Cint
    function Point()
        p = new()
        return p
    end
end

mutable struct ContainerPointerDerived
    x::Ptr{Ptr{Point}}
    function ContainerPointerDerived()
        cpd = new()
        return cpd
    end
end

mutable struct ContainerAllocatableDerived
    x::Ptr{Ptr{Point}}
    function ContainerAllocatableDerived()
        cad = new()
        return cad
    end
end

cpd = ContainerPointerDerived() 
ccall((:initialize_container_pointer_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerPointerDerived}, Ref{Cint}), cpd, array_len)
println("- Result: ", cpd)
println("- Result wrapping: ", unsafe_wrap(Vector{Ptr{Point}}, cpd.x, array_len))

cad = ContainerAllocatableDerived()
ccall((:initialize_container_allocatable_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerAllocatableDerived}, Ref{Cint}), cad, array_len)
println("- Result: ", cad)
println("- Result wrapping: ", unsafe_wrap(Vector{Ptr{Point}}, cad.x, array_len))

This gives the following result:

- Result: ContainerPointerDerived(Ptr{Ptr{Point}} @0x0000000001673240)
- Result wrapping: Ptr{Point}[Ptr{Point} @0x0000000200000001, Ptr{Point} @0x0000000200000004, Ptr{Point} @0x0000000800000004, Ptr{Point} @0x0000000600000003]
forrtl: severe (151): allocatable array is already allocated
Image              PC                Routine            Line        Source
derived-types.so   00007F77F78355B7  initialize_contai     Unknown  Unknown
Unknown            00007F77E1710DB6  Unknown               Unknown  Unknown
libjulia-internal  00007F77F7A3151C  Unknown               Unknown  Unknown
libjulia-internal  00007F77F7A319BA  Unknown               Unknown  Unknown
libjulia-internal  00007F77F7A32D3B  ijl_toplevel_eval     Unknown  Unknown
sys.so             00007F77E3DBBE68  Unknown               Unknown  Unknown
libjulia-internal  00007F77F79F8F1E  ijl_apply_generic     Unknown  Unknown
sys.so             00007F77E3BC430E  Unknown               Unknown  Unknown
sys.so             00007F77E3423AF4  Unknown               Unknown  Unknown
sys.so             00007F77E3423B10  Unknown               Unknown  Unknown
libjulia-internal  00007F77F79F8F1E  ijl_apply_generic     Unknown  Unknown
sys.so             00007F77E3DF96AB  Unknown               Unknown  Unknown
sys.so             00007F77E3826020  Unknown               Unknown  Unknown
sys.so             00007F77E3826129  Unknown               Unknown  Unknown
libjulia-internal  00007F77F79F8F1E  ijl_apply_generic     Unknown  Unknown
libjulia-internal  00007F77F7A5D846  Unknown               Unknown  Unknown
libjulia-internal  00007F77F7A5E28F  jl_repl_entrypoin     Unknown  Unknown
julia              0000000000401069  main                  Unknown  Unknown
libc.so.6          00007F77F8620D90  Unknown               Unknown  Unknown
libc.so.6          00007F77F8620E40  __libc_start_main     Unknown  Unknown
julia              0000000000401099  Unknown               Unknown  Unknown

My questions are the following:

  • How could I extract the contents of the struct? As you can see, I’m currently able to perform the unsafe_wrap and obtain the pointers to each Point type, but how I don’t know how to convert the Ptr into the object itself.
  • The Fortran error indicating that the array is already allocated disappears if I comment the call to the first subroutine. What could be causing this?

Thanks in advance

Just a quick note. You likely want to use struct instead of mutable struct.

1 Like

Julia structs can mirror C structs. A Fortran derived type has to be BIND(C) to be like a C struct. This puts a restriction on the types of the fields in the derived type, which must be interopera,ble.
Especially Fortran pointers are not bare C pointers. They are not interoperable.

3 Likes

This does seem to have done the trick! I also had to modify how I approached the passing of arguments to ccall.

The Julia code now looks like this:

struct Point
    x::Cint
    y::Cint
    z::Cint
end
struct ContainerAllocatableDerived
    x::Ptr{Point}
end

array_len = 5
cad = Ref{ContainerAllocatableDerived}()
ccall((:initialize_container_allocatable_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerAllocatableDerived}, Ref{Cint}), cad, array_len)
println("- Result: ", cad)
println("- Result dereferenced: ", cad[])
println("- Print result contents from Fortran: ")
ccall((:print_container_allocatable_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerAllocatableDerived}, Ref{Cint}), cad, array_len)
point_array = unsafe_wrap(Vector{Point}, cad[].x, array_len)
println("- Result wrapping: ", point_array)

Getting the following result:

- Result: Base.RefValue{ContainerAllocatableDerived}(ContainerAllocatableDerived(Ptr{Point} @0x0000000001c568e0))
- Result dereferenced: ContainerAllocatableDerived(Ptr{Point} @0x0000000001c568e0)
- Print result contents from Fortran:
           1           2           4
           2           4           8
           3           6          12
           4           8          16
           5          10          20
- Result wrapping: Point[Point(1, 2, 4), Point(2, 4, 8), Point(3, 6, 12), Point(4, 8, 16), Point(5, 10, 20)]

My only issue now is that I’m unable to modify the contents of the array from Fortran when passing the composite type again. I’m not sure where the problem is, as I have also done some tests with an array of predefined size and it does not seem to cause any problems; im able to modify the components of the Point objects inside the array.

    TYPE :: ContainerFixedDerived
        TYPE(Point) :: x(3)
    END TYPE ContainerFixedDerived
    ...
        SUBROUTINE initialize_container_fixed_derived(c) BIND(C)
            TYPE(ContainerFixedDerived), INTENT(INOUT) :: c
            INTEGER :: i
            DO i = 1, 3
                CALL initialize_point(c%x(i), i)
            END DO
        END SUBROUTINE initialize_container_fixed_derived

        SUBROUTINE modify_container_fixed_derived(c) BIND(C)
            TYPE(ContainerFixedDerived), INTENT(INOUT) :: c
            c%x(1)%x = 20
        END SUBROUTINE modify_container_fixed_derived
        SUBROUTINE modify_container_allocatable_derived(c) BIND(C)
            TYPE(ContainerAllocatableDerived), INTENT(INOUT) :: c
            c%x(1)%x = 30
        END SUBROUTINE modify_container_allocatable_derived
struct ContainerFixedDerived
    x::NTuple{3, Point}
end
...
cfd = Ref{ContainerFixedDerived}()
ccall((:initialize_container_fixed_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerFixedDerived},), cfd)
...

ccall((:modify_container_fixed_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerFixedDerived}, ), cfd)
println("- Result: ", cfd)
println("- Result dereferenced: ", cfd[])
println("- Print result contents from Fortran: ")
ccall((:print_container_fixed_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerFixedDerived},), cfd)
println("- Contents: ", cfd[].x)

ccall((:modify_container_allocatable_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerAllocatableDerived}, ), cad)
println("- Result: ", cad)
println("- Result dereferenced: ", cad[])
println("- Print result contents from Fortran: ")
ccall((:print_container_allocatable_derived, "./lib/derived-types.so"), Cvoid, (Ref{ContainerAllocatableDerived}, Ref{Cint}), cad, array_len)
point_array = unsafe_wrap(Vector{Point}, cad[].x, array_len)
println("- Result wrapping: ", point_array)

With the following result:

- Result: Base.RefValue{ContainerFixedDerived}(ContainerFixedDerived((Point(20, 2, 4), Point(2, 4, 8), Point(3, 6, 12))))
- Result dereferenced: ContainerFixedDerived((Point(20, 2, 4), Point(2, 4, 8), Point(3, 6, 12)))
- Print result contents from Fortran:
          20           2           4
           2           4           8
           3           6          12
- Contents: (Point(20, 2, 4), Point(2, 4, 8), Point(3, 6, 12))

[5223] signal (11.128): Segmentation fault

That was one of my worries when starting to fiddle with the functionality. I guess a solution would be to remove the nested derived types and just create a single structure composed of all intrinsic/basic types.

For now, as I showed in my previous response, it looks like I might have been able to correct the errors and make it work. Still open to suggestions, as I am fairly new to both languages and might be making mistakes that would cause problems in the future.

Hoe would you work with these structures in C?

One possible alternative is to create interface functions for setting and getting data from the nested derived types on each side, but which only interact through basic types.

1 Like

In theory I’m not planning on working with these structures in C directly. Right now I’m just testing the limits to see what is possible and what is not communicating Julia with Fortran directly.

The future plan is to take a very big Fortran codebase and port some of its functionality to Julia, thus why I’m testing out how to pass and receive data to and from both languages.

That’s also a very good alternative, yes. And it would certainly be easier to maintain compared to the “de-nested” structure, just in case the original structure is modified.

Yes, but Julia uses C struct layouts and is itself programmed in C. If you can tell me what would be the equivalent structures would be in C, then I can tell you what needs be done in Julia.

I see, sorry for the misunderstanding. The thing is I’m not really sure what the C struct layout would be as I’m just trying to translate a Fortran structure.

Although I’m just using toy examples right now, the real structure would look something like this:

    TYPE :: Point
        REAL :: coords(3)
    END TYPE Point
        
    TYPE :: MyElement
        INTEGER, POINTER :: grid(:, :, :, :)
        INTEGER :: status
    END TYPE MyElement

    TYPE :: Mesh
        INTEGER :: a
        INTEGER :: b
        TYPE(MyElement) :: elements(6)
        TYPE(Point), POINTER :: points(:)
        INTEGER, ALLOCATABLE :: values(:)
    END TYPE Mesh

Which causes the issue with POINTERs of derived types.

My best guess for the C structure would be:

struct Point{
    float coords[3];
};

struct MyElement{
    int *grid;
    int status
};

struct Mesh{
    int a;
    int b;
    MyElement elements[6];
    Point *points;
    int *values;
};

But this might not be correct because of the incompatibility between Fortran pointers/allocatables and C pointers, as @dubosipsl noted.

The question is whether you want to manipulate these objects from Julia. If so the types must be bind(c), like:

module point_m
    use iso_c_binding, only: dp=>c_double, c_int, c_ptr, c_f_pointer
    implicit none
    
    TYPE, BIND(C) :: Point_t
        REAL(dp) :: coords(3)
    END TYPE Point
        
    TYPE, BIND(c) :: MyElement_t
        TYPE(c_ptr) :: grid
        INTEGER(c_int) :: grid_shape(4)
        INTEGER(c_int) :: status
    END TYPE
    
    TYPE, BIND(C) :: Mesh_t
        INTEGER(c_int) :: a
        INTEGER(c_int) :: b
        TYPE(MyElement) :: elements(6)
        TYPE(c_ptr) :: points
        integer(c_int) :: points_len
        TYPE(c_ptr) :: values
        integer(c_int) :: values_len
    END TYPE Mesh
    
end module

but you get low-level types, and some kind of type casting will be needed to actually use these objects. From the Fortran side you can cast a C pointer to a Fortran pointer:


! C signature :
! void do_something(mesh_t *mesh) ;

subroutine do_something(mesh) BIND(C)
  TYPE(mesh_t), INTENT(IN) :: mesh
  ! locals
  TYPE(point_t), POINTER :: points(:)
  INTEGER(c_int), POINTER :: values(:)
  ! see https://gcc.gnu.org/onlinedocs/gfortran/Working-with-C-Pointers.html
  call c_f_pointer(mesh%points, points, shape=[mesh%points_len])
  call c_f_pointer(mesh%values, values, shape=[mesh%values_len])
  ! continue with points and values
end subroutine

Maybe your problem would get better answers from the folks at https://fortran-lang.discourse.group/.