29 KiB
Polymorphic Entities
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.
Fortran standard
Here is a list of the sections and constraints of the Fortran standard involved for polymorphic entities.
- 7.3.2.1 - 7.3.2.2: TYPE specifier (TYPE(*))
- C708
- C709
- C710
- C711
- 7.3.2.3: CLASS specifier
- 7.5.4.5: The passed-object dummy argument
- C760
- 9.7.1: ALLOCATE statement
- C933
- 9.7.2: NULLIFY statement
- When a NULLIFY statement is applied to a polymorphic pointer (7.3.2.3), its dynamic type becomes the same as its declared type.
- 10.2.2.3: Data pointer assignment
- 11.1.3: ASSOCIATE construct
- 11.1.11: SELECT TYPE construct
- C1157
- C1158
- C1159
- C1160
- C1161
- C1162
- C1163
- C1164
- C1165
- 16.9.76 EXTENDS_TYPE_OF (A, MOLD)
- 16.9.165 SAME_TYPE_AS (A, B)
- 16.9.184 STORAGE_SIZE (A [, KIND])
- C.10.5 Polymorphic Argument Association (15.5.2.9)
Representation in FIR
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.
A specific BoxTypeInterface
(TypeInterface) can be introduced to share the
same API for both types where it is necessary. !fir.class
and !fir.box
can
also be 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}>>)
Unlimited polymorphic entities CLASS(*)
The unlimited polymorphic entity is represented as a class type with *
.
Fortran
subroutine bar(x)
class(*) :: x
! code of the subroutine
end subroutine
FIR
func.func @bar(%x : !fir.class<*>)
Assumed-type 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
. It's not part of polymorphic entities directly but it's not
currently implemented in flang.
Assumed-type is represented as !fir.type<*>
.
SELECT TYPE construct
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*,
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 %p : !fir.class<!fir.type<_QTpoint{x:f32,y:f32}>> [
#fir.class_is<!fir.type<_QTpoint{x:f32,y:f32}>>, ^bb1,
#fir.type_is<!fir.type<_QTpoint_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 this two
functions ignore the values of KIND
type parameters. A version of these
functions that does not ignore the value of the KIND
type parameters will
be implemented for the SELECT TYPE
type guards testing.
Currently available functions for the EXTENDS_TYPE_OF
and SAME_TYPE_AS
intrinsics (flang/include/flang/Evaluate/type.h
).
std::optional<bool> ExtendsTypeOf(const DynamicType &) const;
std::optional<bool> SameTypeAs(const DynamicType &) const;
FIR (lower level FIR/MLIR after conversion to an if-then-else ladder)
module {
func @f(%arg0: !fir.class<*>) -> i32 {
%c4_i32 = arith.constant 4 : i32
%c8_i32 = arith.constant 8 : i32
%c16_i32 = arith.constant 16 : i32
%0 = fir.gentypedesc !fir.tdesc<!fir.type<!fir.type<_QTpoint{x:f32,y:f32}>>>
%1 = fir.convert %arg0 : (!fir.class<!fir.type<_QTpoint{x:f32,y:f32}>>) -> !fir.box<none>
%2 = fir.convert %0 : (!fir.tdesc<!fir.type<!fir.type<_QTpoint{x:f32,y:f32}>>>) -> !fir.ref<none>
%3 = fir.call @ExtendsTypeOfWithKind(%1, %2) : (!fir.box<none>, !fir.ref<none>) -> i1
cond_br %3, ^bb2(%c4_i32 : i32), ^bb1
^bb1: // pred: ^bb0
%4 = fir.gentypedesc !fir.type<_QTpoint_3d{x:f32,y:f32,z:f32}>
%5 = fir.convert %arg0 : (!fir.class<!fir.type<_QTpoint{x:f32,y:f32}>>) -> !fir.box<none>
%6 = fir.convert %4 : (!fir.tdesc<!fir.type<_QTpoint_3d{x:f32,y:f32,z:f32}>>) -> !fir.ref<none>
%7 = fir.call @SameTypeAsWithKind(%5, %6) : (!fir.box<none>, !fir.ref<none>) -> i1
cond_br %7, ^bb4(%c16_i32 : i32), ^bb3
^bb2(%8: i32): // pred: ^bb0
return %8 : i32
^bb3: // pred: ^bb1
br ^bb5(%c8_i32 : i32)
^bb4(%9: i32): // pred: ^bb1
%10 = arith.addi %9, %9 : i32
return %10 : i32
^bb5(%11: i32): // pred: ^bb3
%12 = arith.muli %11, %11 : i32
return %12 : i32
}
func private @ExactSameTypeAsWithKind(!fir.box<none>, !fir.ref<none>) -> i1
func private @SameTypeAsWithKind(!fir.box<none>, !fir.ref<none>) -> i1
}
Note: some dynamic type checks can be inlined for performance. Type check with intrinsic types when dealing with unlimited polymorphic entities is an ideal candidate for inlined checks.
Dynamic dispatch
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>>>) -> !fir.ref<!fir.box<none>>
%2 = fir.dispatch "get_area"(%1) : (!fir.ref<!fir.box<none>>) -> 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 then lowered to use 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.
LLVMIR
// Retrieve the bindings (vtable) from the type information from the descriptor
%1 = call %_QM__fortran_type_infoTbinding* @_FortranAGetBindings(%desc)
// Retrieve the position of the specific bindings in the table
%2 = call i32 @_FortranAGetBindingOffset(%1, "get_area")
// Get the binding from the table
%3 = getelementptr %_QM__fortran_type_infoTbinding, %_QM__fortran_type_infoTbinding* %1, i32 0, i32 %2
// Get the function pointer from the binding
%4 = getelementptr %_QM__fortran_builtinsT__builtin_c_funptr, %_QM__fortran_type_infoTbinding %3, i32 0, i32 0
// Cast func pointer
%5 = inttoptr i64 %4 to <procedure pointer>
// Load the function
%6 = load f32(%_QMgeometryTshape*)*, %5
// Perform the actual function call
%7 = call f32 %6(%_QMgeometryTshape* %shape)
Note: functions @_FortranAGetBindings
and @_FortranAGetBindingOffset
are
not available in the runtime and will need to be implemented.
@_FortranAGetBindings
retrieves the bindings from the descriptor. The descriptor holds the type information that holds the bindings.@_FortranAGetBindingOffset
retrieves the procedure offset in the bindings based on the binding name provided.
Retrieving the binding table and the offset are done separately so multiple dynamic dispatch on the same polymorphic entities can be optimized (the binding table is retrieved only once for multiple call).
Passing polymorphic entities as argument
Fortran
TYPE t1
END TYPE
TYPE, EXTENDS(t1) :: t2
END TYPE
- Dummy argument is fixed type and actual argument is fixed type.
TYPE(t1)
toTYPE(t1)
: Nothing special to take into consideration.
- Dummy argument is polymorphic and actual argument is fixed type. In these
cases, the actual argument need to be boxed to be passed to the
subroutine/function since those are expecting a descriptor.
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)
toCLASS(t1)
TYPE(t2)
toCLASS(t1)
TYPE(t1)
toCLASS(t2)
- InvalidTYPE(t2)
toCLASS(t2)
- Actual argument is polymorphic and dummy argument is fixed type. These case
are restricted to the declared type of the polymorphic entities.
- The simple case is when the actual argument is a scalar polymorphic entity passed to a non-PDT. The caller just extract the base address from the descriptor and pass it to the function.
- In other cases, the caller needs to perform a copyin/copyout since it
cannot just extract the base address of the
CLASS(T)
because it is likely not contiguous. CLASS(t1)
toTYPE(t1)
CLASS(t2)
toTYPE(t1)
- InvalidCLASS(t1)
toTYPE(t2)
- InvalidCLASS(t2)
toTYPE(t2)
- Both actual and dummy arguments are polymorphic. These particular cases are
straight forward. The function expect polymorphic entities already.
The boxed type is passed without change.
CLASS(t1)
toCLASS(t1)
CLASS(t2)
toCLASS(t1)
CLASS(t1)
toCLASS(t2)
- InvalidCLASS(t2)
toCLASS(t2)
User-Defined Derived Type Input/Output
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:
- Formatted READ
- Formatted WRITE
- Unformatted READ
- Unformatted WRITE
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.
Finalization
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).
Devirtualization
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
statements
The 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.alloca !fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>
%1 = fir.alloca !fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32}>>
%3 = fir.convert %0 : (!fir.ref<!fir.class<!fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32>>>) -> !fir.ref<!fir.box<none>>
%4 = fir.gentypedesc !fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32}>>
%5 = fir.call @_FortranAAllocatableInitDerived(%3, %4)
%6 = fir.convert %1 : (!fir.ref<!fir.class<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32}>>>) -> !fir.ref<!fir.box<none>>
%7 = fir.gentypedesc !fir.type<_QMgeometryTtriangle{color:i32,isFilled:!fir.logical<4>,base:f32,height:f32}>> %8 = fir.call @_FortranAAllocatableInitDerived(%6, %7)
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
intrinsics
EXTENDS_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.
Assignment / Pointer assignment
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
.
User defined assignment and operator
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
.
Impact on existing FIR operations dealing with descriptors
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
- Currently a
fir.load
of afir.box
is a special case. In the code generation no copy is made. This could be problematic with polymorphic entities. When afir.load
is performed on afir.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
andb
might be different. The dynamic type ofa
must be copied when it is associated onb
.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}>>>>
- Currently a
-
fir.embox
- The embox operation is used to create a descriptor from a reference. With polymorphic entities, it is used to create a polymorphic descriptor from a derived type. The declared type of the descriptor and the derived type are identical. The dynamic type of the descriptor must be set when it is created. This is already handled by lowering.
-
fir.rebox
- The rebox operation is used to create a new descriptor from a another descriptor with new optional dimension. If the original descriptor is a polymorphic entities its dynamic type must be propagated to the new descriptor.
%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<>>>
Testing
- Lowering part is tested with LIT tests in tree
- Polymorphic entities involved a lot of runtime information so executable tests will be useful for full testing.
Current TODOs
Current list of TODOs in lowering:
flang/lib/Lower/Allocatable.cpp:465
not yet implemented: SOURCE allocationflang/lib/Lower/Allocatable.cpp:468
not yet implemented: MOLD allocationflang/lib/Lower/Allocatable.cpp:471
not yet implemented: polymorphic entity allocationflang/lib/Lower/Bridge.cpp:448
not yet implemented: create polymorphic host associated copyflang/lib/Lower/Bridge.cpp:2185
not yet implemented: assignment to polymorphic allocatableflang/lib/Lower/Bridge.cpp:2288
not yet implemented: pointer assignment involving polymorphic entityflang/lib/Lower/Bridge.cpp:2316
not yet implemented: pointer assignment involving polymorphic entityflang/lib/Lower/CallInterface.cpp:795
not yet implemented: support for polymorphic typesflang/lib/Lower/ConvertType.cpp:237
not yet implemented: support for polymorphic types
Current list of TODOs in code generation:
flang/lib/Optimizer/CodeGen/CodeGen.cpp:897
not yet implemented: fir.dispatch codegenflang/lib/Optimizer/CodeGen/CodeGen.cpp:911
not yet implemented: fir.dispatch_table codegenflang/lib/Optimizer/CodeGen/CodeGen.cpp:924
not yet implemented: fir.dt_entry codegenflang/lib/Optimizer/CodeGen/CodeGen.cpp:2651
not yet implemented: fir.gentypedesc codegen
Resources: