gfortranstatic-linkingintel-mkl

How to statically link MKL into a shared object?


My general task:

I have a Fortran Tool which uses the functions dgemm, dgemv, dgesvx and dsyev from the MKL Library. I need to compile my code as a shared object (.so) to make it work on a different pc. I am using the gfortran compiler and linker. The linking of the MKL Library must be static as neither the MKL nor gfortran is installed on the other pc.

My .so is not an executable by itself but is part of a Python-Tool which accesses its functions - hence there is nothing like a main() Function in the Fortran code. (I had several errors refering to this, so I wanted to mention it here...)

What I tried:

(1) I used the Intel Link Line Advisor to determin the required flags. The resulting linker (LDFLAGS) and compiler flags (FFLAGS) are:

(2) I added the following Flags due to my personal requirements: FFLAGS:

(3) I combined everything into a Makefile.

OUTPUT_LIBRARY := OI.so

MKLROOT := /opt/intel/oneapi/mkl/latest/

# Name of Compiler
FC := gfortran

# Compileroptions
FFLAGS := -Wall -m64 -fPIC -fdefault-integer-8 -I"${MKLROOT}/include"  # -fdec-math ist veraltet, wird daher entfernt
FFLAGS_RELEASE := -O3
FFLAGS_DEBUG := -g

## Linkeroptions
core := $(MKLROOT)/lib/libmkl_core.a
ilp := $(MKLROOT)/lib/libmkl_gf_ilp64.a
sequential := $(MKLROOT)/lib/libmkl_sequential.a
LDFLAGS := -Wall -m64 -shared -Wl,--start-group $(core) $(ilp) $(sequential) -Wl,--end-group -lpthread -lm -ldl

# Source-Files
SRC := S_Module_ivu.for\
 s_analyse_IVU.for\
 covsrt.for\
 gaussj.for\
 mrqcof.for\
 mrqmin.for\
 nrrank.f\
 S_acsurf_ivu.for\
 S_diff_ivu.for\
 S_expo_fit_neu_ivu.for\
 S_getac_ivu.for\
 S_justl.for\
 S_ord_ivu.for\
 S_PERCENTILE_ivu.for\
 getKanal.f90\
 S_fileprocs_ivu.f90\
 S_setoiparams_ivu.f90\
 SS_oi_ivu.f90\
 S_ANA_VARIANZ_IVU.FOR\
 S_CORRECS_ivu.FOR\
 S_FUNC_DEF_ivu.FOR\
 S_LOCGEO_ivu.FOR\
 S_NORMC_IVU.FOR\
 S_SCALCWTSW_ivu.FOR

SRC_for := $(filter %.for,$(SRC))
SRC_FOR := $(filter %.FOR,$(SRC))
SRC_f := $(filter %.f,$(SRC))
SRC_f90 := $(filter %.f90,$(SRC))

OBJ_for := $(patsubst %.for,%.o,$(SRC_for))
OBJ_FOR := $(patsubst %.FOR,%.o,$(SRC_FOR))
OBJ_f := $(patsubst %.f,%.o,$(SRC_f))
OBJ_f90 := $(patsubst %.f90,%.o,$(SRC_f90))
OBJ_ALL := $(OBJ_for) $(OBJ_FOR) $(OBJ_f) $(OBJ_f90)

# Default-Target: .so in release mode
all: release

release: FFLAGS += $(FFLAGS_RELEASE)
debug: FFLAGS += $(FFLAGS_DEBUG)

release: $(OUTPUT_LIBRARY)
debug: $(OUTPUT_LIBRARY)

# Compile
$(OBJ_for): %.o: %.for
    $(FC) $(FFLAGS) -c $< -o $@
$(OBJ_FOR): %.o: %.FOR
    $(FC) $(FFLAGS) -c $< -o $@
$(OBJ_f): %.o: %.f
    $(FC) $(FFLAGS) -c $< -o $@
$(OBJ_f90): %.o: %.f90
    $(FC) $(FFLAGS) -c $< -o $@

# Link
$(OUTPUT_LIBRARY): $(OBJ_ALL)
    $(FC) $(LDFLAGS) -o $@ $^

# Option for cleaning up
.PHONY: clean
clean:
    rm -f $(OBJ_ALL)
    rm -f *.mod
    rm -f $(OUTPUT_LIBRARY)

What's not working:

When running make release, I get no error messages and the OI.so is successfully created. Nevertheless, when checking the undefined symbols in OI.so (using nm -u OI.so), they list all the required MKL Functions as undefined. My program doesn't work due to these undefined functions.

Any suggestions what I'm doing wrong here?


Solution

  • Sorry if I'm too late. The problem is that Your linkage consumes libraries before the object files that refer to them.1 The application of that answer to your Makefile warrants clarification.

    The GNU/Linux linker consumes input files (object files and libraries) in their commandline order, never revisiting an input file with the one exception that it will cyclically revisit a sequence of static libraries enclosed by the --start-group/--end-group options, resolving undefined references until no new ones arise. In all cases, including that exception, it will only consider a library (either static or shared) to find definitions of undefined references that are already in hand. That is, undefined symbols introduced by object files already input (either object files explicitly named on the commandline or ones extracted from static libraries). In contrast with libraries, any explicitly named object file that is input is linked into the output file unconditionally.

    This means that until the some explicitly named object file is input to the linkage there are 0 undefined references to resolve because nothing has been linked, and the linker will ignore all libraries - even though they may provide the definitions for undefined references that the linker has yet to see. Such undefined references will survive until the end of linkage (unless the necessary libraries are input again) and it will end in failure.

    These facts break your particular linkage because your Makefile is mis-coded, specifically the linkage rule:

    $(OUTPUT_LIBRARY): $(OBJ_ALL)
        $(FC) $(LDFLAGS) -o $@ $^
        
    

    where LDFLAGS is:

    LDFLAGS := -Wall -m64 -shared -Wl,--start-group $(core) $(ilp) $(sequential) -Wl,--end-group -lpthread -lm -ldl
    

    This assignment of LDFLAGS inputs all the libraries before all the object files, $^, with the result that they are ignored.

    By convention for GNU Make, LDFLAGS should be assigned linkage options other than library options, and library options should be assigned to LDLIBS. To correct your Makefile, assign:

    LDFLAGS := -Wall -m64 -shared 
    LDLIBS := -Wl,--start-group $(core) $(ilp) $(sequential) -Wl,--end-group -lpthread -lm -ldl
    

    and change your linkage recipe to:

    $(OUTPUT_LIBRARY): $(OBJ_ALL)
        $(FC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
    

    1. One of my answers to the question What is an undefined reference/unresolved external symbol error and how do I fix it?.