Cross-Language Linking in Meson: FORTRAN & C++

Hi everyone,

I am currently working on linking a FORTRAN simulation with a dependency on a C++ library after compiling it. I successfully managed to compile the two static libraries the code depends upon, and also compile the main simulation code. It is at this stage that I seem to have misconfigured Meson as the linker is unable to find the library as evidenced by error messages such as

/usr/bin/ld: blub_file_bin.F:(.text+0x279eb): undefined reference to `tecini112_'

When I check the library file with nm libdummy.a | grep tecini112, I find the respective routine:

0000000000004050 T tecini112

Within the source code references to the library are mostly made through the use of a .for file , i.e. for, i.e. within 3 files of the simulation source it is imported through

include "dummy/dummy.for"

Which defines the interface, i.e. for tecini112:

      INTERFACE

      INTEGER(4) FUNCTION tecini112
      & (Title,
      &  Variables,
      &  FName,
      &  ScratchDir,
      &  FileType,
      &  Debug,
      &  VIsDouble)
          CHARACTER(LEN=*) Title
          CHARACTER(LEN=*) Variables
          CHARACTER(LEN=*) FName
          CHARACTER(LEN=*) ScratchDir
          INTEGER(4)       FileType
          INTEGER(4)       Debug
          INTEGER(4)       VIsDouble
      END FUNCTION tecini112

      END INTERFACE

There also exist accompanying .f90, and .inc files to the .for file, all of which are not included in meson right now. Which is also where the error might originate from. dummy/include is the incdir, and lib_one is built from dummy/src, i.e. the library is then built by

lib_one = static_library(
    'dummy',
    sources: dummy_src,
    include_directories: incdir,
    dependencies: mpicpp,
    install: false,
)

and the executable is built as

executable(
    'Simulator',
    sources: srcs,
    link_with: [lib_one, lib_two],
    link_args: ['-g', '-lquadmath', '-fbacktrace'],
    fortran_args: [<bunch of flags>],
    dependencies: mpif,
)

Here is furthermore the link command of meson:

gcc  -o SIMULATOR <a bunch of object files> -Wl,--as-needed -Wl,--no-undefined -Wl,-O1 -Wl,--start-group libdummy.a lib2t.a -g -lquadmath -fbacktrace -pthread -Wl,-rpath -Wl,/usr/local/lib -Wl,--enable-new-dtags -L/usr/local/lib -lmpi_usempif08 -lmpi_usempi_ignore_tkr -lmpi_mpifh -lmpi -Wl,-rpath -Wl,/usr/local/lib -Wl,--enable-new-dtags -L/usr/local/lib -lmpi -Wl,-rpath -Wl,/usr/local/lib -Wl,--enable-new-dtags -L/usr/local/lib -lmpi_usempif08 -lmpi_usempi_ignore_tkr -lmpi_mpifh -lmpi -lgfortran -lm -Wl,--end-group

Would be really thankful for any pointers.

Thank you for reading this far,
Ludger

Check whether your Fortran compiler adds an underscore to external routine names, and consider providing a BIND(C) interface that matches the routine name in the C library.

Alternatively, consider changing the name of the routine in the C library to match what the Fortran routine expects.

Matching decorated names is one aspect; the calling sequences must also match.

1 Like

You indicated this is a C++ library. I’m not positive about this (perhaps someone else can confirm or refute) but the source for the library function you want to call needs to have been wrapped in extern "C" { ... } so that it presents a C interface that Fortran can call (using its iso_c_bindings and bind(C) facilities). This may require you to write your own bit of C++ wrapper code if the library doesn’t provide a C interface.

1 Like

After a lot of stumbling in the dark, and in the wild, I think I have finally made some good headway here. After reading in-depth into @mecej4 & @nncarlson’s suggestions I first inspected the interface provided by the Tecio library, which looked to be clean of sorts.

Taking a step back, I took a look at the build systems of SU2, ExaHyPe, and ChiDG, and then rewrote the Meson build system. This is where I think I made a significant error previously. Having gotten it to compile up to the linking stage now, and having a much clearer error here, I can most certainly say that I messed something up in there previously.

Now the error I get, when linking Tecio against my simulation code, is the following:

/usr/bin/ld: externals/tecio/src/libtecio.a(dataio4.cpp.o): in function `DumpGeometry(FileStream_s*, _Geom_s const*, char, char)':
dataio4.cpp:(.text+0x2db8): undefined reference to `PatternLengthInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x2dd9): undefined reference to `LineThicknessInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x3635): undefined reference to `ArrowheadSizeInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x3656): undefined reference to `ArrowheadAngleInputSpec'
/usr/bin/ld: externals/tecio/src/libtecio.a(dataio4.cpp.o): in function `ReadInGeometry(FileStream_s*, short, char, _Geom_s*, int)':
dataio4.cpp:(.text+0x70d3): undefined reference to `PatternLengthInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x70f6): undefined reference to `LineThicknessInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x7181): undefined reference to `ArrowheadSizeInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x71a4): undefined reference to `ArrowheadAngleInputSpec'
/usr/bin/ld: externals/tecio/src/libtecio.a(dataio4.cpp.o): in function `ReadInText(FileStream_s*, short, char, _Text_s*, int)':
dataio4.cpp:(.text+0x79be): undefined reference to `TextBoxMarginInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x7a35): undefined reference to `TextAngleInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x7a54): undefined reference to `TextLineSpacingInputSpec'
/usr/bin/ld: dataio4.cpp:(.text+0x7cc3): undefined reference to `LineThicknessInputSpec'
/usr/bin/ld: source/sim.p/sim_marching.F.o: in function `__sim_marching_MOD_rrc':
sim_marching.F:(.text+0x1283): undefined reference to `getlastsignal'
/usr/bin/ld: source/sim.p/sim_ordnung.F.o: in function `__sim_ordnung_MOD_mach_verzeichnis':
sim_ordnung.F:(.text+0xc54): undefined reference to `check_dir'
/usr/bin/ld: sim_ordnung.F:(.text+0xf07): undefined reference to `inquire_dir'
/usr/bin/ld: sim_ordnung.F:(.text+0xf47): undefined reference to `create_dir'
/usr/bin/ld: source/sim.p/sim_signals.F.o: in function `__sim_signals_MOD_init_signals':
sim_signals.F:(.text+0x14): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0x28): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0x3c): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0x50): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0xc9): undefined reference to `watchsignal'

Looking at the linker command:

--as-needed --no-undefined -O1 --start-group XWENOpack/libXWENOpack.a externals/tecio/src/libtecio.a -lquadmath -lstdc++ -rpath /usr/local/lib --enable-new-dtags -l
mpi_usempif08 -lmpi_usempi_ignore_tkr -lmpi_mpifh -lmpi -rpath /usr/local/lib --enable-new-dtags -lmpi_usempif08 -lmpi_usempi_ignore_tkr -lmpi_mpifh -lmpi -rpath /usr/local/lib --enable-new-dtags -lmpi -lgfortran -lm --end-group -lgfortra
n -lm -lgcc_s -lgcc -lquadmath -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-redhat-linux/10/crtend.o /usr/lib/gcc/x86_64-redhat-linux/10/../../../../lib64/crtn.o

I am mostly concerned about the --no-undefined, as I am still a little unsure whether this might not be causing the trouble here. I am now building the tecio libraries of other codes, and probing them with readelf -sW libtecio.a to see whether the respective references are defined in them.

Has anyone had the same, or a similar issue with Tecio before?

Managed to fix all the Tecio issues - the key here was that Tecio wants the user to provide the magical flag -DMAKEARCHIVE, once that flag is provided all the references are defined and Tecio can be linked completely.

What I am currently still puzzled by are the last few errors

sim_signals.F:(.text+0x14): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0x28): undefined reference to `watchsignalname'
/usr/bin/ld: sim_signals.F:(.text+0xc9): undefined reference to `watchsignal'
collect2: error: ld returned 1 exit status

Currently I am building the C-libraries containing those routines separately as static libraries, and am then linking them in. Which stumps me here is the way the individual routines are being exposed to the FORTRAN side of things, i.e.

subroutine INIT_SIGNALS
      implicit none

      integer  WATCHSIGNAL
      external WATCHSIGNAL
      ...

end subroutine INIT_SIGNALS

And in the C-file

...

#define WATCHSIGNAL_F    F77_FUNC(watchsignal,WATCHSIGNAL)

int WATCHSIGNAL_F    (int *signum);
int WATCHSIGNAL_F(int *signump)
{
    sighandler_t previous;
    int signum = *signump;
    int rval;
    
    initresponses();
    previous = signal(signum, (sighandler_t)detectsignal);
    if (previous == SIG_ERR) {
        rval = -1;
    } else {
        if (signum < NSIGS)
            signalresponses[signum] = signum;
        if (previous == SIG_DFL)
            rval = 0;
        else 
            rval = 1;
    }
    return rval;
}

Having looked at some of the minimum working examples. such as awvwgk’s MWE in the Meson issues, and considering the earlier suggestion by @nncarlson, I am asking myself whether the best way to proceed would be to use iso_c_bindings to link it in?

Using nm on libsignal gives me the following output:

signals.C.o:
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 r .LC0
0000000000000005 r .LC1
000000000000000a r .LC2
                 U signal
                 U strtoul
0000000000000010 T _Z12watchsignal_Pi
00000000000001b0 T _Z14getlastsignal_v
00000000000001e0 T _Z16sigwatchversion_v
0000000000000090 T _Z16watchsignalname_PcPii
0000000000000000 b _ZL10lastsignal
0000000000000000 t _ZL12detectsignali
0000000000000000 d _ZL15signalresponses

Meson is currently configured with the following Fortran flags:

['-Ofast', '-faggressive-loop-optimizations', '-fno-range-check', '-ftree-vectorize', '-funroll-all-loops', '-fallow-invalid-boz', '-cpp', '-march=znver2', '-mtune=znver2', '-fdefault-real-8']

The static library is built without any static flags, and the linker is given the following arguments:

['-g', '-lquadmath', '-lstdc++', '--verbose']

I am only using gnu-compilers here, i.e. gfortran, g++ & gcc.

It seems to me that you are calling C++ routines as if they were C routines, from the name decorations in the output of nm .

1 Like

I would also recommend using bind(C) in Fortran and extern "C" in C++. An example of this is in fs.cpp and fs_cpp.f90 in fortran-pathlib/src at main · scivision/fortran-pathlib · GitHub
There, the Fortran code calls C++ functions and the C++ functions can also call each other.

1 Like

The remarks of @nncarlson, and @mecej4 point in the right direction of what was happening here. After a lot of debugging pain I have found out that my C-files got compiled as C++ after I changed their file-suffixes from .c to .C as I supposed that would force them to be preprocessed, as is happening for all of the Fortran files.

This assumption turned out to be catastrophic as the C-files were compiled as C++ files, the name-mangling occurred and the routines could not be linked into the code I am working with.

bind(c), i.e. iso-C-bindings do not work here as I am dealing with a mix of F90, and F77 files.

After some off-time, I was able to finally get it fixed last night.