Engee documentation
Notebook

Integrating Fortran and Engee

This project addresses the issue of integrating legacy Fortran and Engee code.

Two scenarios are considered:

  • Integration into a technical calculation environment
  • Integration into a modelling environment

Suppose the following code needs to be integrated:

subroutine vect_add(a,b,res,len)

    integer len
    integer i
    
    real, intent(inout) :: res(len)
    real a(len)
    real b(len)
    
    do concurrent (i = 1:len)
        res(i) = a(i) + b(i)
    end do
    
    return
end subroutine vect_add

This subroutine adds two vectors. Our task is to use this subroutine in our calculations.

Preparing for work

Before starting the work, let us note the following: We will need to modify the code of the subroutine so that it can correctly bind to the C or C++ name:

subroutine vect_add(a,b,res,len) bind ( C, name="vect_add" )

In order to use our subroutine correctly, it must be compiled into a shared library. To do this, we will use the gfortran compiler

In [ ]:
run(`gfortran vect_add.f90 -shared -fPIC -o vect_add.so`)
Out[0]:
Process(`gfortran vect_add.f90 -shared -fPIC -o vect_add.so`, ProcessExited(0))

Let's make sure that we can work with the compiled procedure by executing the following command:

In [ ]:
run(`objdump -x vect_add.so`)
vect_add.so:     file format elf64-x86-64
vect_add.so
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000000000

Program Header:
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000000420 memsz 0x0000000000000420 flags r--
    LOAD off    0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12
         filesz 0x00000000000001e9 memsz 0x00000000000001e9 flags r-x
    LOAD off    0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**12
         filesz 0x00000000000000a8 memsz 0x00000000000000a8 flags r--
    LOAD off    0x0000000000002e80 vaddr 0x0000000000003e80 paddr 0x0000000000003e80 align 2**12
         filesz 0x00000000000001a0 memsz 0x00000000000001a8 flags rw-
 DYNAMIC off    0x0000000000002e90 vaddr 0x0000000000003e90 paddr 0x0000000000003e90 align 2**3
         filesz 0x0000000000000150 memsz 0x0000000000000150 flags rw-
    NOTE off    0x0000000000000238 vaddr 0x0000000000000238 paddr 0x0000000000000238 align 2**2
         filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
EH_FRAME off    0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**2
         filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
   RELRO off    0x0000000000002e80 vaddr 0x0000000000003e80 paddr 0x0000000000003e80 align 2**0
         filesz 0x0000000000000180 memsz 0x0000000000000180 flags r--

Dynamic Section:
  INIT                 0x0000000000001000
  FINI                 0x00000000000011dc
  INIT_ARRAY           0x0000000000003e80
  INIT_ARRAYSZ         0x0000000000000008
  FINI_ARRAY           0x0000000000003e88
  FINI_ARRAYSZ         0x0000000000000008
  GNU_HASH             0x0000000000000260
  STRTAB               0x0000000000000318
  SYMTAB               0x0000000000000288
  STRSZ                0x000000000000005e
  SYMENT               0x0000000000000018
  PLTGOT               0x0000000000004000
  RELA                 0x0000000000000378
  RELASZ               0x00000000000000a8
  RELAENT              0x0000000000000018
  RELACOUNT            0x0000000000000003

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .note.gnu.build-id 00000024  0000000000000238  0000000000000238  00000238  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .gnu.hash     00000024  0000000000000260  0000000000000260  00000260  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .dynsym       00000090  0000000000000288  0000000000000288  00000288  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynstr       0000005e  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .rela.dyn     000000a8  0000000000000378  0000000000000378  00000378  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .init         0000001b  0000000000001000  0000000000001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  6 .plt          00000010  0000000000001020  0000000000001020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  7 .plt.got      00000008  0000000000001030  0000000000001030  00001030  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  8 .text         0000019a  0000000000001040  0000000000001040  00001040  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  9 .fini         0000000d  00000000000011dc  00000000000011dc  000011dc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 10 .eh_frame_hdr 00000024  0000000000002000  0000000000002000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .eh_frame     00000080  0000000000002028  0000000000002028  00002028  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 12 .init_array   00000008  0000000000003e80  0000000000003e80  00002e80  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 13 .fini_array   00000008  0000000000003e88  0000000000003e88  00002e88  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 14 .dynamic      00000150  0000000000003e90  0000000000003e90  00002e90  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 15 .got          00000020  0000000000003fe0  0000000000003fe0  00002fe0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 16 .got.plt      00000018  0000000000004000  0000000000004000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 17 .data         00000008  0000000000004018  0000000000004018  00003018  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .bss          00000008  0000000000004020  0000000000004020  00003020  2**0
                  ALLOC
 19 .comment      0000002b  0000000000000000  0000000000000000  00003020  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 crtstuff.c
0000000000001040 l     F .text	0000000000000000 deregister_tm_clones
0000000000001070 l     F .text	0000000000000000 register_tm_clones
00000000000010b0 l     F .text	0000000000000000 __do_global_dtors_aux
0000000000004020 l     O .bss	0000000000000001 completed.0
0000000000003e88 l     O .fini_array	0000000000000000 __do_global_dtors_aux_fini_array_entry
00000000000010f0 l     F .text	0000000000000000 frame_dummy
0000000000003e80 l     O .init_array	0000000000000000 __frame_dummy_init_array_entry
0000000000000000 l    df *ABS*	0000000000000000 vect_add.f90
0000000000000000 l    df *ABS*	0000000000000000 crtstuff.c
00000000000020a4 l     O .eh_frame	0000000000000000 __FRAME_END__
0000000000000000 l    df *ABS*	0000000000000000 
0000000000003e90 l     O .dynamic	0000000000000000 _DYNAMIC
0000000000004020 l     O .data	0000000000000000 __TMC_END__
0000000000004018 l     O .data	0000000000000000 __dso_handle
0000000000001000 l     F .init	0000000000000000 _init
0000000000002000 l       .eh_frame_hdr	0000000000000000 __GNU_EH_FRAME_HDR
00000000000011dc l     F .fini	0000000000000000 _fini
0000000000004000 l     O .got.plt	0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000  w      *UND*	0000000000000000 __cxa_finalize
00000000000010f9 g     F .text	00000000000000e1 vect_add
0000000000000000  w      *UND*	0000000000000000 _ITM_registerTMCloneTable
0000000000000000  w      *UND*	0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000  w      *UND*	0000000000000000 __gmon_start__


Out[0]:
Process(`objdump -x vect_add.so`, ProcessExited(0))

This command outputs the symbols in the binary, and we can see that the vect_add symbol exists in our library:

0000000000000000000010f9 g F .text 00000000000000000000000000e1 vect_add

Integration into the technical calculation environment

For proper integration, we will be using Dynamic Linker. This will allow us to load and unload our shared library as needed.

In [ ]:
using Pkg; Pkg.add("Libdl")
using Libdl
   Resolving package versions...
   Installed MPItrampoline_jll ─ v5.5.1+2
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Let's load our library:

In [ ]:
lib = Libdl.dlopen("./vect_add.so");
foo = Libdl.dlsym(lib, :vect_add);

Create test data and call our subroutine using the ccall function.

The peculiarity of ccall operation with fortran is that all parameters of fortran subroutines are passed by reference.

Julia has a special type for this purpose Ref.

In [ ]:
Vect_in = Vector{Float32}([1.0,2.0,3.0,4.0,5.0])
Vect_out = copy(Vect_in);
ccall(foo,Nothing,(Ref{Float32},Ref{Float32},Ref{Float32},Ref{Int}), Vect_in, Vect_in, Vect_out, 5)
println("Результат работы кода Fortran : $Vect_out")
Результат работы кода Fortran : Float32[2.0, 4.0, 6.0, 8.0, 10.0]

After we have finished working with the library, it should be unloaded:

In [ ]:
Libdl.dlclose(lib)
Out[0]:
true

Integration into the modelling environment

To integrate the compiled library into the modelling environment we will use the C Function block. As for any library, a header file is required to work with library functions. As you can easily notice, we don't have one. However, gfortran allows us to generate it automatically. To do this, recompile our code by additionally specifying the -fc-prototypes key and writing the output to the file vect_add.h

In [ ]:
res = read(`gfortran vect_add.f90 -shared -o libvect_add.so -fPIC -fc-prototypes`,String);
write("vect_add.h",res);

Open the model fort_integration:

In [ ]:
demoroot = @__DIR__
demo = joinpath("$demoroot","fortran_caller.engee");
mdl = engee.load(demo)
Out[0]:
Model(
	name: fortran_caller
	id: 953bdeb1-1e6b-46a7-b5b4-f6b171e07232
)

Additionally configure paths to header files and libraries for the C Function block:

In [ ]:
engee.set_param!("fortran_caller/Fortran_is_Here","IncludeDirectories"=>demoroot)
engee.set_param!("fortran_caller/Fortran_is_Here","LibraryDirectories"=>demoroot)

The call to our procedure is contained in the C Function block, in the output code section. Since its input is a pointer, we must pass the addresses of the input signals.

image.png

Additionally, we must add the generated header file and library to the assembly information:

image_2.png

Let's run the simulation and see the results:

In [ ]:
engee.run(mdl);
In [ ]:
println("Входной вектор: $(simout["fortran_caller/V_in"].value[1])")
println("Результаты работа Fortran: $(simout["fortran_caller/res"].value[1])")
Входной вектор: Float32[1.0, 2.0, 3.0, 4.0, 5.0]
Результаты работа Fortran: Float32[2.0, 4.0, 6.0, 8.0, 10.0]

Conclusions

Fortran code integration is performed by first compiling that code into a shared library and calling that library from a modelling environment or interactive script. This approach allows previous work to be utilised without the cost of rewriting code in Julia

Blocks used in example