| # 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. | |
| `!fir.class` and `!fir.box` are based on a same `BaseBoxType` similar to the | |
| `BaseMemRefType` done for MemRef. | |
| **Fortran** | |
| ```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 `none` as | |
| element type. | |
| **Fortran** | |
| ```fortran | |
| subroutine bar(x) | |
| class(*) :: x | |
| ! code of the subroutine | |
| end subroutine | |
| ``` | |
| **FIR** | |
| ``` | |
| func.func @bar(%x : !fir.class<none>) | |
| ``` | |
| ### 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`. | |
| 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>`. | |
| ### 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** | |
| ```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. A `CLASS 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`). | |
| ```cpp | |
| // 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`). | |
| ```cpp | |
| // 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. | |
| ```fortran | |
| 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 | |
| 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](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** | |
| ```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** | |
| ```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) | |
| ``` | |
| ### Passing polymorphic entities as argument | |
| **Fortran** | |
| ```fortran | |
| TYPE t1 | |
| END TYPE | |
| TYPE, EXTENDS(t1) :: t2 | |
| END TYPE | |
| ``` | |
| 1) Dummy argument is fixed type and actual argument is fixed type. | |
| - `TYPE(t1)` to `TYPE(t1)`: Nothing special to take into consideration. | |
| 2) 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)` to `CLASS(t1)` | |
| - `TYPE(t2)` to `CLASS(t1)` | |
| - `TYPE(t1)` to `CLASS(t2)` - Invalid | |
| - `TYPE(t2)` to `CLASS(t2)` | |
| 3) 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)` to `TYPE(t1)` | |
| - `CLASS(t2)` to `TYPE(t1)` - Invalid | |
| - `CLASS(t1)` to `TYPE(t2)` - Invalid | |
| - `CLASS(t2)` to `TYPE(t2)` | |
| 4) 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)` to `CLASS(t1)` | |
| - `CLASS(t2)` to `CLASS(t1)` | |
| - `CLASS(t1)` to `CLASS(t2)` - Invalid | |
| - `CLASS(t2)` to `CLASS(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** | |
| ```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** | |
| ```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** | |
| ``` | |
| fir.call @_FortranADestroy(%desc) : (!fir.box<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** | |
| ```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** | |
| ```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** | |
| ```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** | |
| ```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** | |
| ```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 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** | |
| ```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` | |
| - 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/Bridge.cpp:448` not yet implemented: create polymorphic host associated copy | |
| - `flang/lib/Lower/CallInterface.cpp:795` not yet implemented: support for polymorphic types | |
| - `flang/lib/Lower/ConvertType.cpp:237` not yet implemented: support for polymorphic types | |
| --- | |
| Resources: | |
| - [1] https://www.pgroup.com/blogs/posts/f03-oop-part1.htm | |
| - [2] https://www.pgroup.com/blogs/posts/f03-oop-part2.htm | |
| - [3] https://www.pgroup.com/blogs/posts/f03-oop-part3.htm | |
| - [4] https://www.pgroup.com/blogs/posts/f03-oop-part4.htm | |
| - [5] Modern Fortran explained |