Fortran ccall variables persisting in the background

I wrote the following Fortran wrapper around an existing Fortran library that defines some ‘global’ parameters that persist between subroutines. General idea is that libfida defines a subroutine read_tables that loads some atomic physics cross-sections from an HDF5 file, which is then used by other subroutines like bb_cx_rates here.

module cx_helper
    implicit none
    private
    public :: cx_matrix, cx_rates
    contains
    subroutine cx_matrix(velocity,nlevels,rates) bind(C,name="cx_matrix")!{{{
        use libfida, only : SimulationInputs,bb_cx_rates,read_tables, nlevs,inputs,impurity_charge
        use, intrinsic :: iso_c_binding, only: c_int, c_double
        implicit none
        real(c_double), intent(in) :: velocity
        integer, intent(in) :: nlevels
        real(c_double), dimension(nlevels,nlevels), intent(inout) :: rates !! catches the output from bb_cx_rates
        integer :: n_in !! incoming neutral energy level (principle quantum number)
        real(c_double), dimension(nlevels) :: denn  !! density [cm^-3] of each energy state of neutrals
        real(c_double), dimension(3) :: vn = [0.0,0.0,0.0] !! cm/s x,y,z components of velocity of neutral
        real(c_double), parameter, dimension(3) :: vi = [0.0,0.0,0.0]!! velocity of ion
        if (nlevels .gt. nlevs) then
            print *,"nlevels must be less than ",nlevs
            stop
        endif 
        vn(1) = velocity
        impurity_charge = 6

        inputs%verbose = 1
        inputs%tables_file = '/Users/lmorton/Code/FIDASIM_lib/tables/atomic_tables.h5'
        inputs%calc_neutron = 0
        call read_tables
        do n_in=1,6
            denn = 0.0
            denn(n_in) = 1.0
            call bb_cx_rates(denn,vn,vi,rates(n_in,:))
        enddo
    end subroutine cx_matrix!}}}

The Julia wrapper then looks like:

function cx_matrix(velocity::Float64)
    nlevs = Int32(6) #Maximum is 6, but can be lower
    σ = Array{Float64,2}(undef,nlevs,nlevs)
    ccall((:cx_matrix, "./cx_helper.so"), Cvoid, (Ptr{Float64},Ref{Int32},Ptr{Float64}), Ref(velocity),Ref(nlevs),σ)
    return σ
end

This works OK the first time I call it, but the second time I crash the REPL:

At line 3767 of file fidasim.f90
Fortran runtime error: Attempting to allocate already allocated variable 'cross'

The line referenced looks like this:

subroutine read_atomic_cross(fid, grp, cross)
   ...
   allocate(cross%log_cross(cross%m_max,cross%n_max, cross%nenergy))

Basically, it looks like (much to my surprise) that the Fortran variables are persisting in memory outside the initial ccall. I assumed that the ccall invocation would destroy the entire Fortran runtime session prior to returning control to Julia, so that I would just need to re-invoke the read_tables subroutine each time. It’s handy to avoid that, but now I have more issues:

  • How do I destroy the Fortran variables when I’m done with them, to avoid memory leakage?
  • How do I test whether the variables are still alive, so I don’t allocate twice & crash the REPL?

More generally, I don’t have a very good sense for what’s happening under the hood when Julia calls a foreign function, and I’m not sure where to find info/documentation on that.

1 Like

There is no separate “Fortran runtime session”. The Fortran code is running in the same process as Julia. It is no different than if you were calling your Fortran code multiple times from other Fortran code. (It is not like launching a separate program.)

The same way as if you were calling your Fortran functions multiple times from Fortran. (It’s hard to say more without seeing the Fortran code that is allocating the cross cvariable that it is complaining about. But presumably you want the deallocate statement in Fortran?)

2 Likes

Ahh, thank you for correcting my conceptual error! I was thinking that there was a separate process/program being spawned.

But presumably you want the deallocate statement in Fortran?

OK, so Julia won’t know to deallocate those variables since Julia didn’t allocate them? And if I don’t do it with Fortran, then even when my Julia session ends, there will be memory tied up?

As far as I know there is no standard API for accessing the Fortran deallocate statement from other languages. (In contrast, for memory allocated by malloc in C, it is perfectly possible to call free in Julia.) Regardless, there is no automatic garbage collection in Fortran — if you allocate global variables with allocate, you (not the language runtime) need to deallocate them if you want to reclaim the memory while the process is running.

No, when a process exits, the operating system releases all memory assigned to that process, regardless of how it was allocated.

3 Likes