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.
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
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
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.
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
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
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:
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.