How are type-bound procedures linked to their derived types in Fortran?

477 Views Asked by At

Coming from C++, I know that non-virtual member functions are tied to their classes through name-mangling. I'm trying to learn modern Fortran and my question is how are Fortran type-bound procedures linked to their types?

For example, consider the derived type

module shape_mod

    type shape
        integer :: color
        logical :: filled
    contains
        procedure :: isFilled
    end type shape

contains

    logical function isFilled(this)
        class(shape) :: this
        isFilled = this%filled
    end function isFilled
 
end module

My question is about the internals of procedure dispatch. Does the memory layout of an instance shp of type shape hold a pointer to a function or vtable that needs to be de-referenced when calling shp%isFilled(), as happens with virtual member functions in C++? Or is the name of the type-bound procedure shp%isFilled() mangled into something like _Z5shape_isFilled(shp), as happens with non-virtual member functions in C++?

I know that Fortran uses name mangling for procedures in modules see wiki for example.

1

There are 1 best solutions below

1
On

There is no need for (additional) name mangling here at all.

The procedures you write are completely ordinary normal module procedures. They just have a polymorphic dummy argument. You do not have to call them as type-bound, you can use them in a normal way.

What makes them type-bound is the creation of the binding in the type declaration.

 procedure :: isFilled

that means there will be a virtual table and a pointer to the procedure in the virtual table and so on and it will point the right type-bound procedure for the right actual type.

But the procedures are completely ordinary ones. At least conceptually, I did not study the compiler, I am describing the standard concept.

In your case you can call

   type(shape) :: instance

   print *, isFilled(instance)

and it will work perfectly fine, there is nothing special about the procedure itself.

Note that when you are extending the type

type, extends(shape) :: outlined_shape
    integer :: outline_color
contains
    procedure :: isFilled => isFilled_outlined shape
end type shape

you have to use a different name for the procedure. Hence no name mangling is necessary, you already have a procedure with a different name

logical function isFilled_outlined_shape(this)
        class(outlined_shape) :: this

Again, you can call it as a type bound procedure:

type(outlined_shape) :: instance

print *, instance%isFilled()

but you can also call it directly (non-virtually)

  print *, isFilled_outlined_shape(instance)

and you can even call the parent one directly

 print *, isFilled(instance)

The reason why C++ needs name mangling is because it uses the same name fir the virtual function in each type. It does not have these normal procedures that have different names as in Fortran. Because if that, C++ needs name-mangling.


Consider this example:

module m

  type t1
  contains
    procedure :: s => s_t1
  end type

  type t2
  contains
    procedure :: s => s_t2
  end type
  
contains

  subroutine s_t1(self)
    class(t1) :: self
  end subroutine

  subroutine s_t2(self)
    class(t2) :: self
  end subroutine

end module

In gfortran, the result contains these symbols:

000000000000000f T __m_MOD___copy_m_T1
0000000000000000 T __m_MOD___copy_m_T2
0000000000000000 B __m_MOD___def_init_m_T1
0000000000000001 B __m_MOD___def_init_m_T2
0000000000000029 T __m_MOD_s_t1
000000000000001e T __m_MOD_s_t2
0000000000000000 R __m_MOD___vtab_m_T1
0000000000000040 R __m_MOD___vtab_m_T2

their meaning is the following:

0000000000000029 T __m_MOD_s_t1
000000000000001e T __m_MOD_s_t2

the subroutine, you can call them directly this way (non-virtually).

000000000000000f T __m_MOD___copy_m_T1
0000000000000000 T __m_MOD___copy_m_T2

the intrinsic assignments of the two types.

0000000000000000 B __m_MOD___def_init_m_T1
0000000000000001 B __m_MOD___def_init_m_T2

the default constructors of the two types

0000000000000000 R __m_MOD___vtab_m_T1
0000000000000040 R __m_MOD___vtab_m_T2

the virtual table functions, one per type (not per procedure, it is not name-mangling of the procedures).

When you add more procedures, https://godbolt.org/z/3q4KWf you can see how the virtual tables contains pointers to them:

__m_MOD___vtab_m_T1:
        .long   69979407
        .zero   4
        .quad   0
        .quad   0
        .quad   __m_MOD___def_init_m_T1
        .quad   __m_MOD___copy_m_T1
        .quad   0
        .quad   0
        .quad   __m_MOD_r_t1
        .quad   __m_MOD_s_t1
__m_MOD___vtab_m_T2:
        .long   69979408
        .zero   4
        .quad   0
        .quad   0
        .quad   __m_MOD___def_init_m_T2
        .quad   __m_MOD___copy_m_T2
        .quad   0
        .quad   0
        .quad   __m_MOD_s_t2
        .quad   __m_MOD_r_t2