Submodules and Private Procedures

Imagine I have a large module A_mod containing a number of internal, private procedures, called by the select few public procedures within this module.

I now want to add some new functionality to this module, without making it any longer or requiring recompilation every time I change this new feature.

From my understanding, I could then use a submodule, say B_submod, to implement my new public subprogram, B_new_feature. However, this new subroutine/function needs to have access to some private routines in the original “parent” A_mod.

From my reading of the limited stuff out there about submodules, I thought this would be fine because of the host association, however it looks like the private components of A_mod are indeed private from any submodules, as my attempt fails to compile at the linking step, stating that the private subroutines from A_mod are undefined references when called from B_submod.

Is there any way around this?

One of the appeals of submodules is precisely that —i.e., that submodules have full access to module’s “privates”.

Submodules, on the other hand, are unaware of their siblings’ state.

Maybe the compiler you’re using (or the particular version of that compiler) doesn’t fully support submodules… or maybe it’s a linking issue?

The following code sample works just fine with gfortran 13.3.0 and ifx 2024.2.0:

module a_mod
    implicit none
    private
    save

    interface
        module subroutine b_new_feature()
        end subroutine
    end interface

    public b_new_feature

contains
    subroutine do_something()
        print*,'doing something'
    end subroutine
end module a_mod

submodule (a_mod) b_submod
    implicit none

contains
    module procedure b_new_feature
        call do_something()
    end procedure
end submodule b_submod

use a_mod

implicit none

call b_new_feature()

end

Thanks for the reply!

A gfortran --version says I’m on 9.4.0 so perhaps that is it, but I did think that this version had f2018 support.

As for a linking issue, I’m compiling the program with CMake and I can’t forsee how, in my albeit limited understanding, how the linking could be an issue. I have simply added the submodule files to a library object as though they were any f90 file, and the modfiles folder I have in my buildtree shows the A_mod@B_submod.smod file

No success with gfortran v 13.1.0 either sadly

You did mention that the build fails at the linking step —hence my comment.

The *.mod and *.smod files are needed by the compiler in order to produce the *.o (or *.obj) object files.

The linker doesn’t know about Fortran semantics/access/encapsulation. It only knows whether a symbol is defined or undefined (either in the provided object files or the libraries declared through the -l flags).

Also, the linker is very particular about visiting dependencies when looking for symbols, so some grouping might be required.

I’m not very familiar with CMake, so I don’t know how those link-related aspects translate to it.

Yes, I will look into it. Thanks a lot for your help. The code sample you provided does indeed compile on my machine so it must be a linking issue.

EDIT: It actually only compiles when given as one file e.g. if I copy and paste the code given above into a file test.f90 and run

gfortran-13 -o test test.f90 

Then that works – see below for the multi file issue which still persists

I took the above code and broke it into the following: a_mod.f90

module a_mod
    implicit none
    private
    save

    interface
        module subroutine b_new_feature()
        end subroutine
    end interface

    public b_new_feature

contains
    subroutine do_something()
        print*,'doing something'
    end subroutine
end module a_mod

and b_submod.f90

submodule (a_mod) b_submod
    implicit none

contains
    module procedure b_new_feature
        call do_something()
    end procedure
end submodule b_submod

and test.f90

program test
    use a_mod
    implicit none
    call b_new_feature()
end program test

I run the following commands

gfortran-13 -c a_mod.f90 
gfortran-13 -c b_submod.f90
gfortran-13 -c test.f90
gfortran-13 -o test test.o a_mod.o b_submod.o

and am met with the following error on the last command

/usr/bin/ld: b_submod.o: in function `__a_mod_MOD_b_new_feature':
b_submod.f08:(.text+0x5): undefined reference to `__a_mod_MOD_do_something'
collect2: error: ld returned 1 exit status

Am I doing something obviously wrong here?

Apologies for my continued confusion!

Hmm… I don’t think you’re doing anything wrong here. It seems to me that it’s a gfortran bug in relation to enforcing encapsulation at the object level (maybe others in the forum might correct me and point you in a different direction).

These are the symbols from the a_mod.f90 file you provided:

$ gfortran -c a_mod.f90 && nm ./a_mod.o
0000000000000000 t __a_mod_MOD_do_something
                 U _gfortran_st_write
                 U _gfortran_st_write_done
                 U _gfortran_transfer_character_write

Notice the lowercase “t” before the symbol __a_mod_MOD_do_something, which means the symbol is in the text section but local to the object file.

If I make do_something (explicitly) public in the a_mod.f90, then I get:

$ gfortran -c a_mod.f90 && nm ./a_mod.o
0000000000000000 T __a_mod_MOD_do_something
                 U _gfortran_st_write
                 U _gfortran_st_write_done
                 U _gfortran_transfer_character_write

Notice the uppercase “T” before the symbol, which means the symbol is in the text section and it’s global.

So, gfortran seems to try to enforce encapsulation at the object level, and that makes the linking step fail when submodules are involved —but it’s probably fine for other scenarios.

Using ifx, the split code you provided compiles and runs without issue:

$ ifx -c ./a_mod.f90 ./b_submod.f90 ./test.f90 
$ ifx -o test ./test.o ./b_submod.o ./a_mod.o
$ ./test 
 doing something

So ifx is not trying to enforce encapsulation at the object level:

$ ifx -c a_mod.f90 && nm ./a_mod.o
0000000000000000 T a_mod._
0000000000000010 T a_mod_mp_do_something_
                 U for_write_seq_lis
0000000000000000 r strlit
1 Like

While looking for a similar bug for gfortran, I found the one concerning this exact issue.

I should have been aware of this issue (since the 2022/11/16 comment in the bug report is mine), but after almost two years, I completely forgot, :laughing:.

The linker error goes away if you make do_something public. Since this is a workaround, probably best to leave a comment:

#if __GFORTRAN__
! Workaround for Bug 104630:
!    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104630
! `do_something` is for use only within submodules
public :: do_something
#endif

If a later version of GCC fixes the bug you can add a conditional ... && (__GNUC__ < 15) to the preprocessor expression.

1 Like

Thanks all for your help. I had run nm on the .o files but hadn’t noticed that.

Will try using ifx and if not, I guess I’ll have to make them public for now

EDIT: ifx works