| # 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** |
| ``` |
| %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** |
| ```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 |