A polymorphic entity is a data entity that can be of different type during the execution of a program.
This document aims to give insights at the representation of polymorphic entities in FIR and how polymorphic related constructs and features are lowered to FIR.
Here is a list of the sections and constraints of the Fortran standard involved for polymorphic entities.
CLASS(type1)
A polymorphic entity is represented as a class type in FIR. In the example below the dummy argument p
is passed to the subroutine foo
as a polymorphic entity with the extensible type point
. The type information captured in the class is the best statically available at compile time. !fir.class
is a new type introduced for polymorphic entities. It's similar to a box type but allows the distinction between a monomorphic and a polymorphic descriptor. !fir.class
and !fir.box
are based on a same BaseBoxType
similar to the BaseMemRefType
done for MemRef.
Fortran
type point real :: x, y end type point type, extends(point) :: point_3d real :: z end type subroutine foo(p) class(point) :: p ! code of the subroutine end subroutine
FIR
func.func @foo(%p : !fir.class<!fir.type<_QTpoint{x:f32,y:f32}>>)
CLASS(*)
The unlimited polymorphic entity is represented as a class type with none
as element type.
Fortran
subroutine bar(x) class(*) :: x ! code of the subroutine end subroutine
FIR
func.func @bar(%x : !fir.class<none>)
TYPE(*)
Assumed type is added in Fortran 2018 and it is available only for dummy arguments. It‘s mainly used for interfaces to non-Fortran code and is similar to C’s void
. An entity that is declared using the TYPE(*)
type specifier is assumed-type and is an unlimited polymorphic entity. It is not declared to have a type, and is not considered to have the same declared type as any other entity, including another unlimited polymorphic entity. Its dynamic type and type parameters are assumed from its effective argument (7.3.2.2 - 3).
Assumed-type is represented in FIR as !fir.box<none>
.
The SELECT TYPE
construct select for execution at most one of its constituent block. The selection is based on the dynamic type of the selector.
Fortran
type point real :: x, y end type point type, extends(point) :: point_3d real :: z end type point_3d type, extends(point) :: color_point integer :: color end type color_point type(point), target :: p type(point_3d), target :: p3 type(color_point), target :: c class(point), pointer :: p_or_c p_or_c => c select type ( a => p_or_c ) class is (point) print*, a%x, a%y type is (point_3d) print*, a%x, a%y, a%z class default print*,'default' end select
From the Fortran standard:
A
TYPE IS
type guard statement matches the selector if the dynamic type and kind type parameter values of the selector are the same as those specified by the statement. ACLASS IS
type guard statement matches the selector if the dynamic type of the selector is an extension of the type specified by the statement and the kind type parameter values specified by the statement are the same as the corresponding type parameter values of the dynamic type of the selector.
In the example above the CLASS IS
type guard is matched.
The construct is lowered to a specific FIR operation fir.select_type
. It is similar to other FIR “select” operations such as fir.select
and fir.select_rank
. The dynamic type of the selector value is matched against a list of type descriptor. The TYPE IS
type guard statement is represented by a #fir.type_is
attribute and the CLASS IS
type guard statement is represented by a #fir.class_is
attribute. The CLASS DEFAULT
type guard statement is represented by a unit
attribute.
FIR
fir.select_type %6 : !fir.class<!fir.ptr<!fir.type<_QFTpoint{x:f32,y:f32}>>> [ #fir.class_is<!fir.type<_QFTpoint{x:f32,y:f32}>>, ^bb1, #fir.type_is<!fir.type<_QFTpoint_3d{x:f32,y:f32,z:f32}>>, ^bb2, unit, ^bb3]
Lowering of the fir.select_type
operation will produce a if-then-else ladder. The testing of the dynamic type of the selector is done by calling runtime functions.
The runtime has two functions to compare dynamic types. Note that these two functions ignore the values of KIND
type parameters.
The functions for the EXTENDS_TYPE_OF
and SAME_TYPE_AS
intrinsics (flang/include/flang/Runtime/derived-api.h
).
// Perform the test of the SAME_TYPE_AS intrinsic. bool RTNAME(SameTypeAs)(const Descriptor &, const Descriptor &); // Perform the test of the EXTENDS_TYPE_OF intrinsic. bool RTNAME(ExtendsTypeOf)(const Descriptor &, const Descriptor &);
For the SELECT TYPE
construct, the KIND
type parameter is not ignored. The TYPE IS
type guard statement is lowered to an inlined comparison. The CLASS IS
type guard statement is lowered to a runtime function call.
The function ClassIs
implements the dynamic type comparison. (flang/include/flang/Runtime/derived-api.h
).
// Perform the test of the CLASS IS type guard statement of the SELECT TYPE // construct. bool RTNAME(ClassIs)(const Descriptor &, const typeInfo::DerivedType &);
FIR (lower level FIR/MLIR after conversion to an if-then-else ladder)
module { func @f(%arg0: !fir.class<!fir.ptr<!fir.type<_QFTpoint{x:f32,y:f32}>>>) -> () { // TYPE IS comparison done inlined. %0 = fir.address_of(@_QFE.dt.point_3d) : !fir.ref<!fir type<_QM__fortran_type_infoTderivedtype{}>> %1 = fir.box_tdesc %arg0 : (!fir.class<!fir.ptr<!fir.type<_QFTpoint{x:f32,y:f32}>>>) -> !fir.tdesc<none> %2 = fir.convert %0 : (!fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype{}>>) -> index %3 = fir.convert %1 : (!fir.tdesc<none>) -> index %4 = arith.cmpi eq, %2, %3 : index cf.cond_br %4, ^bb4, ^bb3 ^bb1: // pred: ^bb3 cf.br ^bb5 ^bb2: // pred: ^bb3 // CLASS IS block. cf.br ^bb6 ^bb3: // pred: ^bb0 // CLASS IS comparison done with a runtime function call. %24 = fir.address_of(@_QFE.dt.point) : !fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype{}>> %25 = fir.convert %24 : (!fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype{}>>) -> !fir.ref<none> %26 = fir.convert %6 : (!fir.class<!fir.ptr<!fir.type<_QFTpoint{x:f32,y:f32}>>>) -> !fir.box<none> %27 = fir.call @_FortranAClassIs(%26, %25) : (!fir.box<none>, !fir.ref<none>) -> i1 cf.cond_br %27, ^bb2, ^bb1 ^bb4: // pred: ^bb0 // TYPE IS block cf.br ^bb6 ^bb5: // pred: ^bb1 // CLASS DEFAULT block. cf.br ^bb6 ^bb6: // 3 preds: ^bb2, ^bb4, ^bb5 return } func.func private @_FortranAClassIs(!fir.box<none>, !fir.ref<none>) -> i1 }
Dynamic type comparisons are inlined for performance whenever possible. Dynamic type comparison for the TYPE IS
type guard is inlined and intrinsic types comparison when dealing with unlimited polymorphic entities are also inlined.
type is (integer(4))
%i32typecode = arith.constant 9 : i8 %typecode = fir.box_typecode %selector : (!fir.class<none>) -> i8 %isi32 = arith.cmpi eq, %typecode, %i32typecode : i8
Dynamic dispatch is the process of selecting which implementation of a polymorphic procedure to call at runtime. The runtime already has information to be used in this process (more information can be found here: RuntimeTypeInfo.md).
The declaration of the data structures are present in flang/runtime/type-info.h
.
In the example below, there is a basic type shape
with two type extensions triangle
and rectangle
. The two type extensions override the get_area
type-bound procedure.
UML
|---------------------| | Shape | |---------------------| | + color:integer | | + isFilled:logical | |---------------------| | + init() | | + get_area():real | |---------------------| /\ /__\ | |---------------------------------------------------| | | | | |---------------------| |---------------------| | triangle | | rectangle | |---------------------| |---------------------| | + base:real | | + length:real | | + height:real | | + width:real | |---------------------| |---------------------| | + get_area():real | | + get_area():real | |---------------------| |---------------------|
Fortran
module geometry type :: shape integer :: color logical :: isFilled contains procedure :: get_area => get_area_shape procedure :: init => init_shape end type shape type, extends(shape) :: triangle real :: base real :: height contains procedure :: get_area => get_area_triangle end type triangle type, extends(shape) :: rectangle real :: length real :: width contains procedure :: get_area => get_area_rectangle end type rectangle type shape_array class(shape), allocatable :: item end type contains function get_area_shape(this) real :: get_area_shape class(shape) :: this get_area_shape = 0.0 end function subroutine init_shape(this, color) class(shape) :: this integer :: color this%color = color this%isFilled = .false. end subroutine function get_area_triangle(this) real :: get_area_triangle class(triangle) :: this get_area_triangle = (this%base * this%height) / 2 end function function get_area_rectangle(this) real :: get_area_rectangle class(rectangle) :: this get_area_rectangle = this%length * this%width end function function get_all_area(shapes) real :: get_all_area type(shape_array) :: shapes(:) real :: sum integer :: i get_all_area = 0.0 do i = 1, size(shapes) get_all_area = get_all_area + shapes(i)%item%get_area() end do end function subroutine set_base_values(sh, v1, v2) class(shape) :: sh real, intent(in) :: v1, v2 select type (sh) type is (triangle) sh%base = v1 sh%height = v2 type is (rectangle) sh%length = v1 sh%width = v2 class default print*,'Cannot set values' end select end subroutine end module program foo use geometry real :: area type(shape_array), dimension(2) :: shapes allocate (triangle::shapes(1)%item) allocate (rectangle::shapes(2)%item) do i = 1, size(shapes) call shapes(i)%item%init(i) end do call set_base_values(shapes(1)%item, 2.0, 1.5) call set_base_values(shapes(2)%item, 5.0, 4.5) area = get_all_area(shapes) print*, area deallocate(shapes(1)%item) deallocate(shapes(2)%item) end program
The fir.dispatch
operation is used to perform a dynamic dispatch. This operation is comparable to the fir.call
operation but for polymorphic entities. Call to NON_OVERRIDABLE
type-bound procedure are resolved at compile time and a fir.call
operation is emitted instead of a fir.dispatch
. When the type of a polymorphic entity can be fully determined at compile time, a fir.dispatch
op can even be converted to a fir.call
op. This will be discussed in more detailed later in the document in the devirtualization section.
FIR Here is simple example of the fir.dispatch
operation. The operation specify the binding name of the type-bound procedure to be called and pass the descriptor as argument. If the NOPASS
attribute is set then the descriptor is not passed as argument when lowered. If PASS(arg-name)
is specified, the fir.pass
attribute is added to point to the PASS argument in the fir.dispatch
operation. fir.nopass
attribute is added for the NOPASS
. The descriptor still need to be present in the fir.dispatch
operation for the dynamic dispatch. The CodeGen will then omit the descriptor in the argument of the generated call.
The dispatch explanation focus only on the call to get_area()
as seen in the example.
Fortran
get_all_area = get_all_area + shapes(i)%item%get_area()
FIR
%1 = fir.convert %0 : !fir.ref<!fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>> %2 = fir.dispatch "get_area"(%1 : !fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>) -> f32
The type information is stored in the f18Addendum
of the descriptor. The format is defined in flang/runtime/type-info.h
and part of its representation in LLVM IR is shown below. The binding is comparable to a vtable. Each derived type has a complete type-bound procedure table in which all of the bindings of its ancestor types appear first.
LLVMIR
Representation of the derived type information with the bindings.
%_QM__fortran_type_infoTderivedtype = type { { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, { ptr, i64, i32, i8, i8, i8, i8 }, i64, { ptr, i64, i32, i8, i8, i8, i8, ptr, [1 x i64] }, { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]] }, { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]] }, { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, i32, i8, i8, i8, i8, [4 x i8] } %_QM__fortran_type_infoTbinding = type { %_QM__fortran_builtinsT__builtin_c_funptr, { ptr, i64, i32, i8, i8, i8, i8 } } %_QM__fortran_builtinsT__builtin_c_funptr = type { i64 }
The fir.dispatch
is lowered to FIR operations by the PolymorphicOpConversion
pass. It uses the runtime information to extract the correct function from the vtable and to perform the actual call. Here is what it can look like in pseudo LLVM IR code.
FIR
%2 = fir.box_tdesc %arg0 : (!fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>) -> !fir.tdesc<none> %3 = fir.box_tdesc %arg0 : (!fir.class<!fir.type<_QMdispatch1Tp1{a:i32,b:i32}>>) -> !fir.tdesc<none> %4 = fir.convert %3 : (!fir.tdesc<none>) -> !fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype{}>> %5 = fir.field_index binding, !fir.type<_QM__fortran_type_infoTderivedtype{}> %6 = fir.coordinate_of %4, %5 : (!fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype{}>>, !fir.field) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding{}>>>>> %7 = fir.load %6 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding{}>>>>> %8 = fir.box_addr %7 : (!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding{}>>>>) -> !fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding{}>>> %c0 = arith.constant 0 : index %9 = fir.coordinate_of %8, %c0 : (!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding{}>> %10 = fir.field_index proc, !fir.type<_QM__fortran_type_infoTbinding{proc:!fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}>,name:!fir.box<!fir.ptr<!fir.char<1,?>>>}> %11 = fir.coordinate_of %9, %10 : (!fir.ref<!fir.type<_QM__fortran_type_infoTbinding{}>>, !fir.field) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}>> %12 = fir.field_index __address, !fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}> %13 = fir.coordinate_of %11, %12 : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}>>, !fir.field) -> !fir.ref<i64> %14 = fir.load %13 : !fir.ref<i64> %15 = fir.convert %14 : (i64) -> ((!fir.class<!fir.type<_QMdispatch1Tp1{a:i32,b:i32}>>) -> ()) fir.call %15(%arg0) : (!fir.class<!fir.type<_QMdispatch1Tp1{a:i32,b:i32}>>) -> ()
LLVMIR
// Retrieve the derived type runtime information and the vtable. %14 = getelementptr %_QM__fortran_type_infoTderivedtype, ptr %13, i32 0, i32 0 %15 = load { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, ptr %14 store { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] } %15, ptr %8 %16 = getelementptr { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, ptr %8, i32 0, i32 0 %17 = load ptr, ptr %16 %18 = getelementptr %_QM__fortran_type_infoTbinding, ptr %17, i64 0 %19 = getelementptr %_QM__fortran_type_infoTbinding, ptr %18, i32 0, i32 0 %20 = getelementptr %_QM__fortran_builtinsT__builtin_c_funptr, ptr %19, i32 0, i32 0 // Load func address %21 = load i64, ptr %20 // Cast to func pointer %22 = inttoptr i64 %21 to ptr // Perform the actual function call call void %22(ptr %0)
Fortran
TYPE t1 END TYPE TYPE, EXTENDS(t1) :: t2 END TYPE
TYPE(t1)
to TYPE(t1)
: Nothing special to take into consideration.func.func @_QMmod1Ps(%arg0: !fir.class<!fir.type<_QMmod1Tshape{x:i32,y:i32}>>) func.func @_QQmain() { %0 = fir.alloca !fir.type<_QMmod1Tshape{x:i32,y:i32}> {uniq_name = "_QFEsh"} %1 = fir.embox %0 : (!fir.ref<!fir.type<_QMmod1Tshape{x:i32,y:i32}>>) -> !fir.class<!fir.type<_QMmod1Tshape{x:i32,y:i32}>> fir.call @_QMmod1Ps(%1) : (!fir.class<!fir.type<_QMmod1Tshape{x:i32,y:i32}>>) -> () return }
TYPE(t1)
to CLASS(t1)
TYPE(t2)
to CLASS(t1)
TYPE(t1)
to CLASS(t2)
- InvalidTYPE(t2)
to CLASS(t2)
CLASS(T)
because it is likely not contiguous.CLASS(t1)
to TYPE(t1)
CLASS(t2)
to TYPE(t1)
- InvalidCLASS(t1)
to TYPE(t2)
- InvalidCLASS(t2)
to TYPE(t2)
CLASS(t1)
to CLASS(t1)
CLASS(t2)
to CLASS(t1)
CLASS(t1)
to CLASS(t2)
- InvalidCLASS(t2)
to CLASS(t2)
User-Defined Derived Type Input/Output allows to define how a derived-type is read or written from/to a file.
There are 4 basic subroutines that can be defined:
Here are their respective interfaces:
Fortran
subroutine read_formatted(dtv, unit, iotype, v_list, iostat, iomsg) subroutine write_formatted(dtv, unit, iotype, v_list, iostat, iomsg) subroutine read_unformatted(dtv, unit, iotype, v_list, iostat, iomsg) subroutine write_unformatted(dtv, unit, iotype, v_list, iostat, iomsg)
When defined on a derived-type, these specific type-bound procedures are stored as special bindings in the type descriptor (see SpecialBinding
in flang/runtime/type-info.h
).
With a derived-type the function call to @_FortranAioOutputDescriptor
from IO runtime will be emitted in lowering.
Fortran
type(t) :: x write(10), x
FIR
%5 = fir.call @_FortranAioBeginUnformattedOutput(%c10_i32, %4, %c56_i32) : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8> %6 = fir.embox %2 : (!fir.ref<!fir.type<_QTt>>) -> !fir.class<!fir.type<_QTt>> %7 = fir.convert %6 : (!fir.class<!fir.type<_QTt>>) -> !fir.box<none> %8 = fir.call @_FortranAioOutputDescriptor(%5, %7) : (!fir.ref<i8>, !fir.box<none>) -> i1 %9 = fir.call @_FortranAioEndIoStatement(%5) : (!fir.ref<i8>) -> i32
When dealing with polymorphic entities the call to IO runtime can stay unchanged. The runtime function OutputDescriptor
can make the dynamic dispatch to the correct binding stored in the descriptor.
The FINAL
specifies a final subroutine that might be executed when a data entity of that type is finalized. Section 7.5.6.3 defines when finalization occurs.
Final subroutines like User-Defined Derived Type Input/Output are stored as special bindings in the type descriptor. The runtime is able to handle the finalization with a call the the @_FortranADestroy
function (flang/include/flang/Runtime/derived-api.h
).
FIR
%5 = fir.call @_FortranADestroy(%desc) : (!fir.box<none>) -> none
The @_FortranADestroy
function will take care to call the final subroutines and the ones from the parent type.
Appropriate call to finalization have to be lowered at the right places (7.5.6.3 When finalization occurs).
Sometimes there is enough information at compile-time to avoid going through a dynamic dispatch for a type-bound procedure call on a polymorphic entity. To be able to perform this optimization directly in FIR the dispatch table is also present statically with the fir.dispatch_table
and fir.dt_entry
operations.
Here is an example of these operations representing the dispatch tables for the same example than for the dynamic dispatch.
FIR
fir.dispatch_table @_QMgeometryE.dt.shape { fir.dt_entry init, @_QMgeometryPinit_shape fir.dt_entry get_area, @_QMgeometryPget_area_shape } fir.dispatch_table @_QMgeometryE.dt.rectangle { fir.dt_entry init, @_QMgeometryPinit_shape fir.dt_entry get_area, @_QMgeometryPget_area_rectangle } fir.dispatch_table @_QMgeometryE.dt.triangle { fir.dt_entry init, @_QMgeometryPinit_shape fir.dt_entry get_area, @_QMgeometryPget_area_triangle }
With this information, an optimization pass can replace fir.dispatch
operations with fir.call
operations to the correct functions when the type is know at compile time.
This is the case in a type is
type-guard block as illustrated below.
Fortran
subroutine get_only_triangle_area(sh) class(shape) :: sh real :: area select type (sh) type is (triangle) area = sh%get_area() class default area = 0.0 end select end subroutine
FIR
The call to get_area
in the type is (triangle)
guard can be replaced.
%3 = fir.dispatch "get_area"(%desc) // Replaced by %3 = fir.call @get_area_triangle(%desc)
Another example would be the one below. In this case as well, a dynamic dispatch is not necessary and a fir.call
can be emitted instead.
Fortran
real :: area class(shape), pointer :: sh type(triangle), target :: tr sh => tr area = sh%get_area()
Note that the frontend is already replacing some of the dynamic dispatch calls with the correct static ones. The optimization pass is useful for cases not handled by the frontend and especially cases showing up after some other optimizations are applied.
ALLOCATE
/DEALLOCATE
statementsThe allocation and deallocation of polymorphic entities are delegated to the runtime. The corresponding function signatures can be found in flang/include/flang/Runtime/allocatable.h
and in flang/include/flang/Runtime/pointer.h
for pointer allocation.
ALLOCATE
The ALLOCATE
statement is lowered to runtime calls as shown in the example below.
Fortran
allocate(triangle::shapes(1)%item) allocate(rectangle::shapes(2)%item)
FIR
%0 = fir.address_of(@_QMgeometryE.dt.triangle) : !fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype>> %1 = fir.convert %item1 : (!fir.ref<!fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>>) -> !fir.ref<!fir.box<none>> %2 = fir.call @_FortranAAllocatableInitDerived(%1, %0) %3 = fir.call @_FortranAAllocatableAllocate(%1, ...) %4 = fir.address_of(@_QMgeometryE.dt.rectangle) : !fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype>> %5 = fir.convert %item2 : (!fir.ref<!fir.class<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32}>>>) -> !fir.ref<!fir.box<none>> %6 = fir.call @_FortranAAllocatableInitDerived(%5, %4) %7 = fir.call @_FortranAAllocatableAllocate(%5, ...)
For pointer allocation, the PointerAllocate
function is used.
DEALLOCATE
The DEALLOCATE
statement is lowered to a runtime call to AllocatableDeallocate
and PointerDeallocate
for pointers.
Fortran
deallocate(shapes(1)%item) deallocate(shapes(2)%item)
FIR
%8 = fir.call @_FortranAAllocatableDeallocate(%desc1) %9 = fir.call @_FortranAAllocatableDeallocate(%desc2)
EXTENDS_TYPE_OF
/SAME_TYPE_AS
intrinsicsEXTENDS_TYPE_OF
and SAME_TYPE_AS
intrinsics have implementation in the runtime. Respectively SameTypeAs
and ExtendsTypeOf
in flang/include/flang/Evaluate/type.h
.
Both intrinsic functions are lowered to their respective runtime calls.
Intrinsic assignment of an object to another is already implemented in the runtime. The function @_FortranAAsssign
performs the correct operations.
Available in flang/include/flang/Runtime/assign.h
.
Fortran
module mod1 type t1 contains procedure :: assign_t1 generic :: assignment(=) => assign_t1 end type t1 type, extends(t1) :: t2 end type contains subroutine assign_t1(to, from) class(t1), intent(inout) :: to class(t1), intent(in) :: from ! Custom code for the assignment end subroutine subroutine assign_t2(to, from) class(t2), intent(inout) :: to class(t2), intent(in) :: from ! Custom code for the assignment end subroutine end module program main use mod class(t1), allocatable :: v1 class(t1), allocatable :: v2 allocate(t2::v1) allocate(t2::v2) v2 = v1 end program
In the example above the assignment v2 = v1
is done by a call to assign_t1
. This is resolved at compile time since t2
could not have a generic type-bound procedure for assignment with an interface that is not distinguishable. This is the same for user defined operators.
NULLIFY
When a NULLIFY
statement is applied to a polymorphic pointer (7.3.2.3), its dynamic type becomes the same as its declared type.
The NULLIFY
statement is lowered to a call to the corresponding runtime function PointerNullifyDerived
in flang/include/flang/Runtime/pointer.h
.
Currently, FIR has a couple of operations taking descriptors as inputs or producing descriptors as outputs. These operations might need to deal with the dynamic type of polymorphic entities.
fir.load
/fir.store
fir.load
of a fir.box
is a special case. In the code generation no copy is made. This could be problematic with polymorphic entities. When a fir.load
is performed on a fir.class
type, the dynamic can be copied.Fortran
module mod1 class(shape), pointer :: a contains subroutine sub1(a, b) class(shape) :: b associate (b => a) ! Some more code end associate end subroutine end module
In the example above, the dynamic type of a
and b
might be different. The dynamic type of a
must be copied when it is associated on b
.
FIR
// fir.load must copy the dynamic type from the pointer `a` %0 = fir.address_of(@_QMmod1Ea) : !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmod1Tshape{x:i32,y:i32}>>>> %1 = fir.load %0 : !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmod1Tshape{x:i32,y:i32}>>>>
fir.embox
fir.rebox
%0 = fir.slice %c10, %c33, %c2 : (index, index, index) -> !fir.slice<1> %1 = fir.shift %c0 : (index) -> !fir.shift<1> %2 = fir.rebox %x(%1)[%0] : (!fir.class<!fir.array<?x!fir.type<>>>, !fir.shift<1>, !fir.slice<1>) -> !fir.class<!fir.array<?x!fir.type<>>>
Current list of TODOs in lowering:
flang/lib/Lower/Bridge.cpp:448
not yet implemented: create polymorphic host associated copyflang/lib/Lower/CallInterface.cpp:795
not yet implemented: support for polymorphic typesflang/lib/Lower/ConvertType.cpp:237
not yet implemented: support for polymorphic typesResources: