'Calling subroutines in different Fortran modules

I have been developing a free code for computational thermodynamics in the new Fortran standard for the last 10 years it it works quite well but it is large, more than 60000 lines of code and some 500 subroutines. The software calculates equilibria, phase diagrams and simulates phase transformations using model parameters read from assessed databases.

Recently I was asked to provide an encrypted database with this software and I agreed to try to do that. The actual encryption is done by an independent vendor which will also provide a library with routines to decrypt the database. However, I am obliged to prevent anyone extracting the database parameters from inside my software so I have to provide a small pre-compiled library together with the decryption library to store and calculate with the decrypted model parameters in a way that prevents a programmer to extract the model parameters from my software.

This task required a major reorganizing of my data structures and to extract some subroutines in a special library which will not be part of the free software. I have managed to modify the data structure and I have extracted a small set of subroutines for the calculations with the decrypted parameters. However, when reading the model parameters from the encrypted database I must find a place to store these related to the elements and phases selected by the user. The subroutines to find this place are in the program I want to keep free and develop. So while reading the encrypted database in the library module I must access a large number of subroutines in the free software.

I will try to explain my problem in a simple way using figure 1 where a main program calls subroutine SUBB inside module MODA. SUBB calls SUBC which calls SUBC before returning. This is the way my original code works and it is no problem because all subroutines are inside the same module.

figure 1

module moda
contains
  subroutine suba(ib)
    integer :: ib
    write(*,*)'suba 1:',ib
    ib=5
    call subc(ib)
    write(*,*)'suba 2:',ib
  end subroutine suba
!
  subroutine subb(id)
    integer :: id
    write(*,*)'subb 1:',id
    call suba(id)
    write(*,*)'subb 2:',id
  end subroutine subb
!
  subroutine subc(ie)
    integer :: ie
    write(*,*)'subc 1:',ie
    ie=2 
    write(*,*)'subc 2:',ie
  end subroutine subc
end module moda

program main
  use moda
  integer if
  if=0
  call subb(if)
  write(*,*)'Value: ',if
end program main

Assume SUBA is the subroutine reading the encrypted database and storing the model parameters in a secret way. I have made this into a separate module (and library) in figure 2 which will not be part of the free software. The library will be compiled independently of the main program.

figure 2

module secret
!  use modb, only: subc   creates compilation error
contains
  subroutine suba(ib)
    integer ib
    ib=5
    call subc(ib)
  end subroutine suba
end module secret

In figure 3 I separated out the module MODB (as I tried to "use" that for SUBA) and in figure 4 the main program.

Figure 3

module modb
!  use secret, only: suba   creates compilation error
contains
  subroutine subb(id)
    integer :: id
    call suba(id)
  end subroutine subb
  subroutine subc(ie)
    integer :: ie
    ie=2
  end subroutine subc
end module modb

Figure 4

program main
  use modb
  use secret
  integer if
  if=0
  call subb(ie)
  write(*,*)'Value: ',if
end program main

All my attempts to link this together fails, either by compilation errors when I "use" the different modules or that "SUBC_" is an undefined reference. I have spent a few weeks to handle the data structure problems, I thought the subroutine calls would be easier. Any hint is welcome.

I understand that "use" between two modules is probably illegal and anyway problematic but I have no idea how to handle this. I can not make the whole of MODB module part of the SECRET module because I want to keep it as small as possible. The code is still developing and I cannot maintain separate parts.

But maybe there is an elegant solution. I understood this "only:" trying to solve this problem. Maybe there is some "interface" or "external" feature I do not know about.

For development I use gfortran, gcc 10.2.0 from MSYS2 on Windows but my software is used on various Linux machines and MacOS. The current version is available on sundmanbo/opencalphad repository on github.



Solution 1:[1]

Your secret module depends on module modb which depends on objects from the secret module. That is a circular dependency. It can be readily resolved via Fortran 2008 submodules. The essence of the idea is to separate the interfaces of module procedures from their implementation. You would have to put the interfaces in the module in one file and the implementations in a submodule in a separate file. To avoid the compilation errors, you would have to then compile the module files (containing the interfaces) first, followed by the implementations (build generators like CMake would automatically detect the dependencies and do this for you). Here is an example implementation,

Contents of secret_mod.F90:

module secret

    implicit none

    interface
        module subroutine suba(ib)
            integer, intent(out) :: ib
        end subroutine
    end interface

end module secret

Contents of secret_mod@Routines_smod.F90:

submodule (secret) Routines_smod

contains

    module procedure suba
        use modb, only: subc
        ib = 5
        call subc(ib)
    end procedure

end submodule Routines_smod

Contents of modb_mod.F90:

module modb

    implicit none

    interface

        module subroutine subb(id)
            integer, intent(out) :: id
        end subroutine

        module subroutine subc(ie)
            integer, intent(out) :: ie
        end subroutine

    end interface

end module modb

Contents of modb_mod@Routines_smod.F90:

submodule (modb) Routines_smod

contains

    module procedure subc
        ie = 2
    end procedure subc

    module procedure subb
        use secret, only: suba
        call suba(id)
    end procedure subb

end submodule Routines_smod

The key is have the dependencies only in the submodules (not in the modules). Now compile while respecting the module/submodule order,

gfortran secret_mod.F90 modb_mod.F90 secret_mod@Routines_smod.F90 modb_mod@Routines_smod.F90 main.F90 -O
./a.out
 Value:            0

Solution 2:[2]

If I understand you correctly, you can resolve your circular dependency using a submodule. Submodules allow you to separate the procedure interfaces from the procedure implementations.

In your code, only the implmentation of suba needs to know about subc. So by separating suba into a module and a submodule, this moves the dependency on modb from the module to the submodule.

Using a submodule, your code would look like

module secret
  interface
    module subroutine suba(ib)
      integer ib
    end subroutine suba
  end interface
end module secret

submodule (secret) secret_submodule
  use modb, only: subc
contains
  subroutine suba(ib)
    integer ib
    ib=5
    call subc(ib)
  end subroutine suba
end submodule

module modb
  use secret, only: suba
contains
  subroutine subb(id)
    integer :: id
    call suba(id)
  end subroutine subb
  subroutine subc(ie)
    integer :: ie
    ie=2
  end subroutine subc
end module modb

program main
  use modb
  use secret

  integer if
  if=0
  call subb(ie)
  write(*,*)'Value: ',if
end program main

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Scientist
Solution 2 veryreverie