| # Parameterized Derived Types (PDTs) |
| |
| Derived types can be parameterized with type parameters. A type parameter is |
| either a kind type parameter or a length type parameter. Both kind and length |
| type parameters are of integer type. |
| |
| This document aims to give insights at the representation of PDTs in FIR and how |
| PDTs 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 parameterized derived types. |
| |
| - 7.2 Type parameters |
| - C701 |
| - C702 |
| - 9.4.5: Type parameter inquiry |
| - 9.7.1: ALLOCATE statement |
| - 9.7.2: NULLIFY |
| - 9.7.3: DEALLOCATE |
| |
| The constraints are implemented and tested in flang. |
| |
| ## The two types of PDTs |
| |
| ### PDT with kind type parameter |
| |
| PDTs with kind type parameter are already implemented in flang. Since the kind |
| type parameter shall be a constant expression, it can be determined at |
| compile-time and is folded in the type itself. Kind type parameters also play |
| a role in determining a specific type instance according to the Fortran |
| standard. |
| |
| **Fortran** |
| ```fortran |
| type t(k) |
| integer, kind :: k |
| end type |
| |
| type(t(1)) :: tk1 |
| type(t(2)) :: tk2 |
| ``` |
| |
| In the example above, `tk1` and `tk2` have distinct types. |
| |
| Lowering makes the distinction between the two types by giving them different |
| names `@_QFE.kp.t.1` and `@_QFE.kp.t.2`. More information about the unique names |
| can be found here: `flang/docs/BijectiveInternalNameUniquing.md` |
| |
| ### PDT with length type parameter |
| |
| Two PDTs with the same derived type and the same kind type parameters but |
| different length type parameters are not distinct types. Unlike the kind type |
| parameter, the length type parameters do not play a role in determining a |
| specific type instance. |
| PDTs with length type parameter can be seen as dependent types[1]. |
| |
| In the example below, `tk1` and `tk2` have the same type but may have different |
| layout in memory. They have different value for the length type parameter `l`. |
| `tk1` and `tk2` are not convertible unlike `CHARACTER` types. |
| Assigning `tk2` to `tk1` is not a valid program. |
| |
| **Fortran** |
| ```fortran |
| type t(k,l) |
| integer, kind :: k |
| integer, len :: l |
| end type |
| |
| type(t(1, i+1)) :: tk1 |
| type(t(1, i+2)) :: tk2 |
| |
| ! This is invalid |
| tk2 = tk1 |
| ``` |
| |
| Components with length type parameters cannot be folded into the type at |
| compile-time like the one with kind type parameters since their size is not |
| known. There are multiple ways to implement length type parameters and here are |
| two possibilities. |
| |
| 1. Directly encapsulate the components in the derived type. This will be referred |
| as the "inlined" solution in the rest of the document. The size of the |
| descriptor will not be fixed and be computed at runtime. Size, offset need |
| to be computed at runtime as well. |
| |
| 2. Use a level of indirection for the components outside of the descriptor. This |
| will be referred as the "outlined" solution in the rest of the document. |
| The descriptor size will then remain the same. |
| |
| These solutions have pros and cons and more details are given in the next few |
| sections. |
| |
| #### Implementing PDT with inlined components |
| |
| In case of `len_type1`, the size, offset, etc. of `fld1` and `fld2` depend on |
| the runtime values of `i` and `j` when the components are inlined into the |
| derived type. At runtime, this information needs to be computed to be retrieved. |
| While lowering the PDT, compiler generated functions can be created in order to |
| compute this information. |
| |
| Note: The type description tables generated by semantics and used throughout the |
| runtime have component offsets as constants. Inlining component would require |
| this representation to be extended. |
| |
| **Fortran** |
| ```fortran |
| ! PDT with one level of inlined components. |
| type len_type1(i, j) |
| integer, len :: i, j |
| character(i+j) :: fld1 |
| character(j-i+2) :: fld2 |
| end type |
| ``` |
| |
| #### Implementing PDT with outlined components |
| |
| A level of indirection can be used and `fld1` and `fld2` are then outlined |
| as shown in `len_type2`. _compiler_allocatable_ is here only to show which |
| components have an indirection. |
| |
| **Fortran** |
| ```fortran |
| ! PDT with one level of indirection. |
| type len_type2(i, j) |
| integer, len :: i, j |
| ! The two following components are not directly stored in the type but |
| ! allocatable components managed by the compiler. The |
| ! `compiler_managed_allocatable` is not a proper keyword but just added here |
| ! to have a better understanding. |
| character(i+j), compiler_managed_allocatable :: fld1 |
| character(j-i+2), compiler_managed_allocatable :: fld2 |
| end type |
| ``` |
| |
| This solution has performance drawback because of the added indirections. It |
| also has to deal with compiler managed allocation/deallocation of the components |
| pointed by the indirections. |
| |
| These indirections are more problematic when we deal with array slice of derived |
| types as it could require temporaries depending how the memory is allocated. |
| |
| The outlined solution is also problematic for unformatted I/O as the |
| indirections need to be followed correctly when reading or writing records. |
| |
| #### Example of nested PDTs |
| |
| PDTs can be nested. Here are some example used later in the document. |
| |
| **Fortran** |
| ```fortran |
| ! PDT with second level of inlined components. |
| type len_type3(i, j) |
| integer, len :: i, j |
| character(2*j) :: name |
| type(len_type1(i*2, j+4)) :: field |
| end type |
| |
| ! PDT with second level of indirection |
| type len_type4(i, j) |
| integer, len :: i, j |
| character(2*j), compiler_allocatable :: name |
| type(len_type2(i-1, 2**j)), compiler_allocatable :: field |
| end type |
| ``` |
| |
| #### Example with array slice |
| |
| Let's take an example with an array slice to see the advantages and |
| disadvantages of the two solutions. |
| |
| For all derived types that do not have LEN type parameter (only have |
| compile-time constants) a standard descriptor can be set with the correct offset |
| and strides such that `array%field%fld2` can be encoded in the descriptor, is |
| not contiguous, and does not require a copy. This is what is implemented in |
| flang. |
| |
| **Fortran** |
| ```fortran |
| ! Declare arrays of PDTs |
| type(len_type3(exp1,exp2)) :: pdt_inlined_array(exp3) |
| type(len_type4(exp1,exp2)) :: pdt_outlined_array(exp3) |
| |
| ! Passing/accessing a slice of PDTs array |
| pdt_inlined_array%field%fld2 |
| ``` |
| |
| For a derived type with length type parameters inlined the expression |
| `pdt_inlined_array%field%fld2` can be encoded in the standard descriptor because |
| the components of `pdt_inlined_array` are inlined such that the array is laid |
| out with all its subcomponents in a contiguous range of memory. |
| |
| For the `pdt_outlined_array` array, the implementation has to insert several |
| level of indirections and therefore cannot be encoded in the standard |
| descriptor. |
| The different indirections levels break the property of the large contiguous |
| block in memory if the allocation is done for each components. This would make |
| the `pdt_outlined_array` a ragged array. The memory can also be allocated for |
| components with length type parameters while allocating the base object (in this |
| case the `pdt_outlined_array`). |
| |
| For each non-allocatable/non-pointer leaf automatic component of a PDT base |
| entity (`pdt_outlined_array` here) or a base entity containing PDTs, the |
| initialization will allocate a single block in memory for all the leaf |
| components reachable in the base entity (`pdt_outlined_array(i)%field%fld1`). |
| The size of this block will be `N * sizeof(leaf-component)` where `N` is the |
| multiplication of the size of each part-ref from the base entity to the leaf |
| component. The descriptor for each leaf component can then point to the correct |
| location in the block `block[i*sizeof(leaf-component)]`. |
| |
| Outlining the components has the advantage that the size of the PDTs are |
| compile-time constant as each field is encoded as a descriptor pointing to the |
| data. It has a disadvantage to require non-standard descriptors and comes with |
| additional runtime cost. |
| |
| With components inlining, the size of the PDTs are not compile-time constant. |
| This solution has the advantage to not add a performance drawback with |
| additional indirections but requires to compute the size of the descriptor |
| at runtime. |
| The size of the PDTs need to be computed at runtime. This is already the case |
| for dynamic allocation sizes since it is possible for arrays to have dynamic |
| shapes, etc. |
| |
| ### Support of PDTs in other compilers |
| |
| 1) Nested PDTs |
| 2) Array of PDTs |
| 3) Allocatable array of PDTs |
| 4) Pointer to array section |
| 5) Formatted I/O |
| 6) Unformatted I/O |
| 7) User-defined I/O |
| 8) FINAL subroutine |
| 9) ELEMENTAL FINAL subroutine |
| |
| | Compiler | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
| | --------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | |
| | gfortran | crash | ok | crash | ok | ok | ok | no | no | no | |
| | nag | ok | ok | ok | crash | ok | ok | ok | no | no | |
| | nvfortran | crash | ok | ok | ok | ok | ok | ok | ok | no | |
| | xlf | ok | ok | ok | ok | wrong | ok | wrong | no | no | |
| | ifort | ok | ok | ok | ok | ok | ok | ok | crash | crash | |
| |
| _Legends of results in the table_ |
| ``` |
| ok = compile + run + good result |
| wrong = compile + run + wrong result |
| crash = compiler crash or runtime crash |
| no = doesn't compile with no crash |
| ``` |
| |
| #### Field inlining in lowering |
| |
| A PDT with length type parameters has a list of 1 or more type parameters that |
| are runtime values. These length type parameter values can be present in |
| specification of other type parameters, array bounds expressions, etc. |
| All these expressions are integer specifications expressions and can be |
| evaluated at any given point with the length type parameters value of the PDT |
| instance. This is possible because constraints C750 and C754 from Fortran 2018 |
| standard that restrict what can appear in the specification expression. |
| |
| _note: C750 and C754 are partially enforced in the semantic at the moment._ |
| |
| These expressions can be lowered into small simple functions. For example, |
| the offset of `fld1` in `len_type1` could be 0; its size would be computed as |
| `sizeof(char) * (i+j)`. `size` can be lowered into a compiler generated |
| function. |
| |
| **FIR** |
| ``` |
| // Example of compiler generated functions to compute offsets, size, etc. |
| // This is just an example and actual implementation might have more functions. |
| |
| // name field offset. |
| func.func @_len_type3.offset.name() -> index { |
| %0 = arith.constant 0 : index |
| return %0 : index |
| } |
| |
| // size for `name`: sizeof(char) * (2 * i) + padding |
| func.func @_len_type3.memsize.name(%i: index, %j: index) -> index { |
| %0 = arith.constant 2 : index |
| %1 = arith.constant 8 : index |
| %2 = arith.muli %0, %i : index |
| %3 = arith.muli %1, %2 : index |
| // padding not added here |
| return %3 : index |
| } |
| |
| // `fld` field offset. |
| func.func @_len_type3.offset.field(%i: index, %j: index) -> index { |
| %0 = call @_len_type3.offset.name() : () -> index |
| %1 = call @_len_type3.memsize.name(%i, %j) : (index, index) -> index |
| %2 = arith.addi %0, %1 : index |
| return %2 : index |
| } |
| |
| // 1st type parameter used for field `fld`: i*2 |
| func.func @_len_type3.field.typeparam.1(%i : index, %j : index) -> index { |
| %0 = arith.constant 2 : index |
| %1 = arith.muli %0, %i : index |
| return %1 : index |
| } |
| |
| // 2nd type parameter used for field `fld`: j+4 |
| func.func @_len_type3.field.typeparam.2(%i : index, %j : index) -> index { |
| %0 = arith.constant 4 : index |
| %1 = arith.addi %j, %0 : index |
| return %1 : index |
| } |
| |
| // `fld1` offset in `len_type1`. |
| func.func @_len_type1.offset.fld1() -> index { |
| %0 = arith.constant 0 : index |
| return %0 : index |
| } |
| |
| // size for `fld1`. |
| func.func @_len_type1.memsize.fld1(%i : index, %j : index) -> index { |
| %0 = arith.constant 8 : index |
| %1 = arith.addi %i, %j : index |
| %2 = arith.muli %0, %1 : index |
| return %2 : index |
| } |
| |
| // `fld2` offset in `len_type1`. |
| func.func @_len_type1.offset.fld2(%i : index, %j : index) -> index { |
| %0 = call @_len_type1.offset.fld1() : () -> index |
| %1 = call @_len_type1.memsize.fld1(%i, %j) : (index, index) -> index |
| %2 = arith.addi %0, %1 : index |
| return %2 : index |
| } |
| ``` |
| |
| Access a field |
| ```fortran |
| pdt_inlined_array(1)%field%fld2 |
| ``` |
| |
| Example of offset computation in the PDTs. |
| ``` |
| %0 = call @_len_type3.field.typeparam.1(%i, %j) : (index, index) -> index |
| %1 = call @_len_type3.field.typeparam.2(%i, %j) : (index, index) -> index |
| %2 = call @_len_type3.offset.fld(%i, %j) : (index, index) -> index |
| %3 = call @_len_type1.offset.fld2(%0, %1) : (index, index) -> index |
| %offset_of_1st_element = arith.addi %2, %3 : index |
| // Use the value computed offset_of_1st_element |
| ``` |
| |
| In the case where the length type parameters values `(i,j)` are compile-time |
| constants then function inlining and constant folding will transform these |
| dependent types into statically defined types with no runtime cost. |
| |
| **Fortran** |
| ```fortran |
| type t(l) |
| integer, len :: l |
| integer :: i(l) |
| end type |
| |
| type(t(n)), target :: a(10) |
| integer, pointer :: p(:) |
| p => a(:)%i(5) |
| ``` |
| |
| When making a new descriptor like for pointer association, the `field_index` |
| operation can take the length type parameters needed for size/offset |
| computation. |
| |
| **FIR** |
| ``` |
| %5 = fir.field_index i, !fir.type<_QMmod1Tt{l:i32,i:!fir.array<?xi32>}>(%n : i32) |
| ``` |
| |
| ### Length type parameter with expression |
| |
| The component of a PDT can be defined with expressions including the length |
| type parameters. |
| |
| **Fortran** |
| ```fortran |
| type t1(n, m) |
| integer, len :: n = 2 |
| integer, len :: m = 4 |
| real :: data(n*m) |
| end type |
| ``` |
| |
| The idea would be to replace the expression with an extra length type parameter |
| with a compiler generated name and a default value of `n*m`. All instance of the |
| expression would then reference the new name. |
| |
| **Fortran** |
| ```fortran |
| type t1(n, m) |
| integer, len :: n = 2 |
| integer, len :: m = 4 |
| integer, len :: t1_n_m_ = 8 ! hidden extra length type parameter |
| real :: data(t1_n_m_) |
| end type |
| ``` |
| |
| At any place where the a PDT is initialized, the lowering would make the |
| evaluation and their values saved in the addendum and pointed to by the |
| descriptor. |
| |
| ### `ALLOCATE`/`DEALLOCATE` statements |
| |
| The allocation and deallocation of PDTs are delegated to the runtime. |
| |
| The corresponding function can be found in |
| `flang/include/flang/Runtime/allocatable.h` and |
| `flang/include/flang/Runtime/pointer.h` for pointer allocation. |
| |
| `ALLOCATE` |
| |
| The `ALLOCATE` statement is lowered to a sequence of function calls as shown in |
| the example below. |
| |
| **Fortran** |
| ```fortran |
| type t1(i) |
| integer, len :: i = 4 |
| character(i) :: c |
| end type |
| |
| type(t1), allocatable :: t |
| type(t1), pointer :: p |
| |
| allocate(t1(2)::t) |
| allocate(t1(2)::p) |
| ``` |
| |
| **FIR** |
| ``` |
| // For allocatable |
| %5 = fir.call @_FortranAAllocatableInitDerived(%desc, %type) : (!fir.box<none>, ) -> () |
| // The AllocatableSetDerivedLength functions is called for each length type parameters. |
| %6 = fir.call @_FortranAAllocatableSetDerivedLength(%desc, %pos, %value) : (!fir.box<none>, i32, i64) -> () |
| %7 = fir.call @_FortranAAllocatableAllocate(%3) : (!fir.box<none>) -> () |
| |
| // For pointer |
| %5 = fir.call @_FortranAPointerNullifyDerived(%desc, %type) : (!fir.box<none>, ) -> () |
| // The PointerSetDerivedLength functions is called for each length type parameters. |
| %6 = fir.call @_FortranAPointerSetDerivedLength(%desc, %pos, %value) : (!fir.box<none>, i32, i64) -> () |
| %7 = fir.call @_FortranAPointerAllocate(%3) : (!fir.box<none>) -> () |
| ``` |
| |
| `DEALLOCATE` |
| |
| The `DEALLOCATE` statement is lowered to a runtime call to |
| `AllocatableDeallocate` and `PointerDeallocate` for pointers. |
| |
| **Fortran** |
| ```fortran |
| deallocate(pdt1) |
| ``` |
| |
| **FIR** |
| ``` |
| // For allocatable |
| %8 = fir.call @_FortranAAllocatableDeallocate(%desc1) : (!fir.box<none>) -> (i32) |
| |
| // For pointer |
| %8 = fir.call @_FortranAPointerDeallocate(%desc1) : (!fir.box<none>) -> (i32) |
| ``` |
| |
| ### `NULLIFY` |
| |
| The `NULLIFY` statement is lowered to a call to the corresponding runtime |
| function `PointerNullifyDerived` in `flang/include/flang/Runtime/pointer.h`. |
| |
| **Fortran** |
| ```fortran |
| NULLIFY(p) |
| ``` |
| |
| **FIR** |
| ``` |
| %0 = fir.call @_FortranAPointerNullifyDerived(%desc, %type) : (!fir.box<none>, !fir.tdesc) -> () |
| ``` |
| |
| ### Formatted I/O |
| |
| The I/O runtime internals are described in this file: |
| `flang/docs/IORuntimeInternals.md`. |
| |
| When an I/O statement with a derived-type is encountered in lowering, the |
| derived-type is emboxed in a descriptor if it is not already and a call to the |
| runtime library is issued with the descriptor (as shown in the example below). |
| The function is `_FortranAioOutputDescriptor`. The call make a call to |
| `FormattedDerivedTypeIO` in `flang/runtime/descriptor-io.h` for derived-type. |
| This function will need to be updated to support the chosen solution for PDTs. |
| |
| **Fortran** |
| ```fortran |
| type t |
| integer, len :: l |
| integer :: i(l) = 42 |
| end type |
| |
| ! ... |
| |
| subroutine print_pdt |
| type(t(10)) :: x |
| print*, x |
| end subroutine |
| ``` |
| |
| **FIR** |
| ``` |
| func.func @_QMpdtPprint_pdt() { |
| %l = arith.constant = 10 |
| %0 = fir.alloca !fir.type<_QMpdtTt{l:i32,i:!fir.array<?xi32>}> (%l : i32) {bindc_name = "x", uniq_name = "_QMpdt_initFlocalEx"} |
| %1 = fir.embox %0 : (!fir.ref<!fir.type<_QMpdtTt{l:i32,i:!fir.array<?xi32>}>>) (typeparams %l : i32) -> !fir.box<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<2xi32>}>> |
| %2 = fir.address_of(@_QQcl.2E2F6669725F7064745F6578616D706C652E66393000) : !fir.ref<!fir.char<1,22>> |
| %c8_i32 = arith.constant 8 : i32 |
| %3 = fir.convert %1 : (!fir.box<!fir.type<_QMpdtTt{l:i32,i:!fir.array<?xi32>}>>) -> !fir.box<none> |
| %4 = fir.convert %2 : (!fir.ref<!fir.char<1,22>>) -> !fir.ref<i8> |
| %5 = fir.call @_FortranAInitialize(%3, %4, %c8_i32) : (!fir.box<none>, !fir.ref<i8>, i32) -> none |
| %c-1_i32 = arith.constant -1 : i32 |
| %6 = fir.address_of(@_QQcl.2E2F6669725F7064745F6578616D706C652E66393000) : !fir.ref<!fir.char<1,22>> |
| %7 = fir.convert %6 : (!fir.ref<!fir.char<1,22>>) -> !fir.ref<i8> |
| %c10_i32 = arith.constant 10 : i32 |
| %8 = fir.call @_FortranAioBeginExternalListOutput(%c-1_i32, %7, %c10_i32) : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8> |
| %9 = fir.embox %0 : (!fir.ref<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>) (typeparams %l : i32) -> !fir.box<!fir.type<_QMpdtTt{l:i32,i:!fir.array<?xi32>}>> |
| %10 = fir.convert %9 : (!fir.box<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>) -> !fir.box<none> |
| %11 = fir.call @_FortranAioOutputDescriptor(%8, %10) : (!fir.ref<i8>, !fir.box<none>) -> i1 |
| %12 = fir.call @_FortranAioEndIoStatement(%8) : (!fir.ref<i8>) -> i32 |
| return |
| } |
| ``` |
| |
| ### Unformatted I/O |
| |
| The entry point in the runtime for unformatted I/O is similar than the one for |
| formatted I/O. A call to `_FortranAioOutputDescriptor` with the correct |
| descriptor is also issued by the lowering. For unformatted I/O, the runtime is |
| calling `UnformattedDescriptorIO` from `flang/runtime/descriptor-io.h`. |
| This function will need to be updated to support the chosen solution for PDTs. |
| |
| ### Default component initialization of local variables |
| |
| Default initializers for components with length type parameters need to be |
| processed as the derived type instance is created. |
| The length parameters block must also be created and attached to the addendum. |
| See _New f18addendum_ section for more information. |
| |
| ### Assignment |
| |
| As mentioned in 10.2.1.2 (8), for an assignment, each length type parameter of |
| the variable shall have the same value as the corresponding type parameter |
| unless the lhs is allocatable. |
| |
| **Fortran** |
| ```fortran |
| type t(l) |
| integer, len :: l |
| integer :: i(l) |
| end type |
| |
| ! ... |
| |
| type(t(10)) :: a, b |
| type(t(20)) :: c |
| type(t(:)), allocatable :: d |
| a = b ! Legal assignment |
| c = b ! Illegal assignment because `c` does not have the same length type |
| ! parameter value than `b`. |
| d = c ! Legal because `d` is allocatable |
| ``` |
| |
| A simple intrinsic assignment without allocatable or pointer follows the same |
| path than the traditional derived-type (addressing of component is different) |
| since the length type parameter values are identical and do not need to be |
| copied or reallocated. The length type parameters values are retrieved when |
| copying the data. |
| |
| Assignment of PDTs with allocatable or pointer components are done with the help |
| of the runtime. A call to `_FortranAAssign` is done with the lhs and rhs |
| descriptors. The length type parameters are available in the descriptors. |
| |
| For allocatable PDTs, if the rhs side has different length type parameters than |
| the lhs, it is deallocated first and allocated with the rhs length type |
| parameters information (F'2018 10.2.1.3(3)). There is code in the runtime to |
| handle this already. It will need to be updated for the new f18addendum. |
| |
| ### Finalization |
| |
| A final subroutine is called for a PDT if the subroutine has the same kind type |
| parameters and rank as the entity to be finalized. The final subroutine is |
| called with the entity as the actual argument. |
| If there is an elemental final subroutine whose dummy argument has the same kind |
| type parameters as the entity to be finalized, or a final subroutine whose dummy |
| argument is assumed-rank with the same kind type parameters as the entity to be |
| finalized, the subroutine is called with the entity as the actual argument. |
| Otherwise, no subroutine is called. |
| |
| **Example from the F2018 standard** |
| ```fortran |
| module m |
| |
| type t(k) |
| integer, kind :: k |
| real(k), pointer :: vector(:) => NULL() |
| contains |
| final :: finalize_t1s, finalize_t1v, finalize_t2e |
| end type |
| |
| contains |
| |
| subroutine finalize_t1s(x) |
| type(t(kind(0.0))) x |
| if (associated(x%vector)) deallocate(x%vector) |
| END subroutine |
| |
| subroutine finalize_t1v(x) |
| type(t(kind(0.0))) x(:) |
| do i = lbound(x,1), ubound(x,1) |
| if (associated(x(i)%vector)) deallocate(x(i)%vector) |
| end do |
| end subroutine |
| |
| elemental subroutine finalize_t2e(x) |
| type(t(kind(0.0d0))), intent(inout) :: x |
| if (associated(x%vector)) deallocate(x%vector) |
| end subroutine |
| end module |
| |
| subroutine example(n) |
| use m |
| |
| type(t(kind(0.0))) a, b(10), c(n,2) |
| type(t(kind(0.0d0))) d(n,n) |
| ... |
| ! Returning from this subroutine will effectively do |
| ! call finalize_t1s(a) |
| ! call finalize_t1v(b) |
| ! call finalize_t2e(d) |
| ! No final subroutine will be called for variable C because the user |
| ! omitted to define a suitable specific procedure for it. |
| end subroutine |
| ``` |
| |
| ### Type parameter inquiry |
| |
| Type parameter inquiry is used to get the value of a type parameter in a PDT. |
| |
| **Fortran** |
| ```fortran |
| module t |
| type t1(i, j) |
| integer, len :: i = 4 |
| integer, len :: j = 2 |
| character(i*j) :: c |
| end type |
| end |
| |
| program main |
| use t |
| type(t1(2, 2)) :: ti |
| print*, ti%c%len |
| print*, ti%i |
| print*, ti%j |
| end |
| |
| ! Should print: |
| ! 4 |
| ! 2 |
| ! 2 |
| ``` |
| |
| These values are present in the `f18Addendum` and can be retrieved from it with |
| the correct index. If the length type parameter for a field is an expression, |
| a compiler generated function is used to computed its value. |
| The length type parameters are indexed in declaration order; i.e., 0 is the |
| first length type parameter in the deepest base type. |
| |
| ### PDTs and polymorphism |
| |
| In some cases with polymorphic entities, it is necessary to copy the length |
| type parameters from a descriptor to another. With the current design this is |
| not possible since the descriptor cannot be reallocated and the addendum is |
| allocated with a fixed number of length type parameters. |
| |
| **Fortran** |
| ```fortran |
| ! The example below illustrates a case where the number of length type |
| ! parameters are different and need to be copied to an existing descriptor |
| ! addendum. |
| module m1 |
| type t1 |
| integer :: i |
| end type |
| |
| ! This type could be defined in another compilation unit. |
| type, extends(t1) :: t2(l1, l2) |
| integer, len :: l1, l2 |
| end type |
| |
| contains |
| |
| subroutine reallocate(x) |
| class(t1), allocatable :: x |
| allocate(t2(l1=1, l2=2):: x) |
| end subroutine |
| |
| end module |
| |
| program p |
| use m1 |
| |
| class(t1), allocatable :: x |
| |
| call reallocate(x) |
| ! The new length type parameters need to be propagated at this point. |
| |
| ! rest of code using `x` |
| end program |
| ``` |
| |
| The proposed solution is to add indirection in the `f18Addendum` and store the |
| length type parameters in a separate block instead of directly in the addendum. |
| At the moment the storage for the length type parameters is allocated once as |
| a `std::int64_t` array. |
| |
| **New f18Addendum** |
| ```cpp |
| {*derivedType_, *lenParamValues_} |
| ``` |
| |
| Adding the indirection in the descriptor's addendum requires to manage the |
| lifetime of the block holding the length type parameter values. |
| |
| Here are some thoughts of how to manage it: |
| - For allocatables, the space for the LEN parameters can be allocated as part of |
| the same malloc as the payload data. |
| - For automatics, same thing, if we implement automatics as allocatables. |
| - For monomorphic local variables, the LEN parameters would be in a little array |
| on the stack. Or we could treat any variable or component with LEN parameters |
| as being automatic even when it's monomorphic. |
| - For pointers and dummy arguments, we can just copy the pointer in the addendum |
| from the target to the pointer or dummy descriptor. |
| - For dynamically allocated descriptors, the LEN parameter values could just |
| follow the addendum in the same malloc. |
| |
| The addendum of an array sections/sub-objects would point to the same block than |
| the base object. |
| |
| In some special cases, a descriptor needs to be passed between the caller and |
| the callee. This includes array of PDTs and derived-type with PDT components. |
| The example describe one of the corner case where the length type parameter |
| would be lost if the descriptor is not passed. |
| |
| ### Example that require a descriptor |
| |
| Because of the length type parameters store in the addendum, it is required in |
| some case to pass the PDT with a descriptor to preserve the length type |
| parameters information. The example below illustrates such a case. |
| |
| **Fortran** |
| ```fortran |
| module m |
| type t |
| integer :: i |
| end type |
| |
| type, extends(t) :: t2(l) |
| integer, len :: l |
| real :: x(l) |
| end type |
| |
| type base |
| type(t2(20)) :: pdt_component |
| end type |
| |
| class(t), pointer :: p(:) |
| |
| contains |
| |
| subroutine foo(x, n) |
| integer :: n |
| type(base), target :: x(n) |
| ! Without descriptor, the actual argument is a zero-sized array. The length |
| ! type parameters of `x(n)%pdt_component` are not propagated from the caller. |
| |
| ! A descriptor local to this function is created to pass the array section |
| ! in bar. |
| call bar(x%pdt_component) |
| end subroutine |
| |
| subroutine bar(x) |
| type(t2(*)), target :: x(:) |
| p => x |
| end subroutine |
| |
| subroutine test() |
| type(base), target :: x(100) |
| call foo(x(1:-1:1), 0) |
| select type (p) |
| type is (t2(*)) |
| ! This type parameters of x(1:60:3) in foo must still live here |
| print *, p%l |
| class default |
| print *, "something else" |
| end select |
| end subroutine |
| end module |
| |
| use m |
| call test() |
| end |
| ``` |
| |
| Because of the use case described above, PDTs, array of PDTs or derived-type |
| with PDT components will be passed by descriptor. |
| |
| ## FIR operations with length type parameters |
| |
| Couple of operations have length type parameters as operands already in their |
| design. For some operations, length type parameters are likely needed with |
| the two proposed solution. Some other operation like the array operations, the |
| operands are not needed when dealing with a descriptor since the length type |
| parameters are in it. |
| |
| The operations will be updated if needed during the implementation of the |
| chosen solution. |
| |
| ### `fir.alloca` |
| |
| This primitive operation is used to allocate an object on the stack. When |
| allocating a PDT, the length type parameters are passed to the |
| operation so its size can be computed accordingly. |
| |
| **FIR** |
| ``` |
| %i = arith.constant 10 : i32 |
| %0 = fir.alloca !fir.type<_QMmod1Tpdt{i:i32,data:!fir.array<?xf32>}> (%i : i32) |
| // %i is the ssa value of the length type parameter |
| ``` |
| |
| ### `fir.allocmem` |
| |
| This operation is used to create a heap memory reference suitable for storing a |
| value of the given type. When creating a PDT, the length type parameters are |
| passed so the size can be computed accordingly. |
| |
| **FIR** |
| ``` |
| %i = arith.constant 10 : i32 |
| %0 = fir.alloca !fir.type<_QMmod1Tpdt{i:i32,data:!fir.array<?xf32>}> (%i : i32) |
| // ... |
| fir.freemem %0 : !fir.type<_QMmod1Tpdt{i:i32,data:!fir.array<?xf32>}> |
| ``` |
| |
| ### `fir.embox` |
| |
| The `fir.embox` operation create a boxed reference value. In the case of PDTs |
| the length type parameters can be passed as well to the operation. |
| |
| **Fortran** |
| ```fortran |
| subroutine local() |
| type(t(2)) :: x ! simple local PDT |
| ! ... |
| end subroutine |
| ``` |
| |
| **FIR** |
| ``` |
| func.func @_QMpdt_initPlocal() { |
| %c2_i32 = arith.constant 2 : i32 |
| %0 = fir.alloca !fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}> (%c2 : i32) |
| {bindc_name = "x", uniq_name = "_QMpdt_initFlocalEx"} |
| // The fir.embox operation is responsible to place the provided length type |
| // parameters in the descriptor addendum so they are available to the runtime |
| // call later. |
| %1 = fir.embox %0 : (!fir.ref<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>) (typeparams %c2 : i32) |
| -> !fir.box<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>> |
| %2 = fir.address_of(@_QQcl.2E2F6669725F7064745F6578616D706C652E66393000) : !fir.ref<!fir.char<1,22>> |
| %c8_i32 = arith.constant 8 : i32 |
| %3 = fir.convert %1 : (!fir.box<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>) -> !fir.box<none> |
| %4 = fir.convert %2 : (!fir.ref<!fir.char<1,22>>) -> !fir.ref<i8> |
| %5 = fir.call @_FortranAInitialize(%3, %4, %c8_i32) : (!fir.box<none>, !fir.ref<i8>, i32) -> none |
| return |
| } |
| ``` |
| |
| ### `fir.field_index` |
| |
| The `fir.field_index` operation is used to generate a field offset value from |
| a field identifier in a derived-type. The operation takes length type parameter |
| values with a PDT so it can compute a correct offset. |
| |
| **FIR** |
| ``` |
| %l = arith.constant 10 : i32 |
| %1 = fir.field_index i, !fir.type<_QMpdt_initTt{l:i32,i:i32}> (%l : i32) |
| %2 = fir.coordinate_of %ref, %1 : (!fir.type<_QMpdt_initTt{l:i32,i:i32}>, !fir.field) -> !fir.ref<i32> |
| %3 = fir.load %2 : !fir.ref<i32> |
| return %3 |
| ``` |
| |
| ### `fir.len_param_index` |
| |
| This operation is used to get the length type parameter offset in from a PDT. |
| |
| **FIR** |
| ``` |
| func.func @_QPpdt_len_value(%arg0: !fir.box<!fir.type<t1{l:i32,!fir.array<?xi32>}>>) -> i32 { |
| %0 = fir.len_param_index l, !fir.box<!fir.type<t1{l:i32,!fir.array<?xi32>}>> |
| %1 = fir.coordinate_of %arg0, %0 : (!fir.box<!fir.type<t1{l:i32,!fir.array<?xi32>}>>, !fir.len) -> !fir.ref<i32> |
| %2 = fir.load %1 : !fir.ref<i32> |
| return %2 : i32 |
| } |
| ``` |
| |
| ### `fir.save_result` |
| |
| Save the result of a function returning an array, box, or record type value into |
| a memory location given the shape and LEN parameters of the result. Length type |
| parameters is passed if the PDT is not boxed. |
| |
| **FIR** |
| ``` |
| func.func @return_pdt(%buffer: !fir.ref<!fir.type<t2(l1:i32,l2:i32){x:f32}>>) { |
| %l1 = arith.constant 3 : i32 |
| %l2 = arith.constant 5 : i32 |
| %res = fir.call @foo() : () -> !fir.type<t2(l1:i32,l2:i32){x:f32}> |
| fir.save_result %res to %buffer typeparams %l1, %l2 : !fir.type<t2(l1:i32,l2:i32){x:f32}>, !fir.ref<!fir.type<t2(l1:i32,l2:i32){x:f32}>>, i32, i32 |
| return |
| } |
| ``` |
| |
| ### `fir.array_*` operations |
| |
| The current design of the different `fir.array_*` operations include length type |
| parameters operands. This is designed to use PDT without descriptor directly in |
| FIR. |
| |
| **FIR** |
| ``` |
| // Operation used with a boxed PDT does not need the length type parameters as |
| // they are directly retrieved from the box. |
| %0 = fir.array_coor %boxed_pdt, %i, %j (fir.box<fir.array<?x?xfir.type<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>>>, index, index) -> !fir.ref<fir.type<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>> |
| |
| // In case the PDT would not be boxed, the length type parameters are needed to |
| // compute the correct addressing. |
| %0 = fir.array_coor %pdt_base, %i, %j typeparams %l (fir.ref<fir.array<?x?xfir.type<!fir.type<_QMpdt_initTt{l:i32,i:!fir.array<?xi32>}>>>>, index, index, index) -> !fir.ref<fir.type<PDT>> |
| ``` |
| |
| --- |
| |
| ## Implementation choice |
| |
| While both solutions have pros and cons, we want to implement the outlined |
| solution. |
| - The runtime was implemented with this solution in mind. |
| - The size of the descriptor does not need to be computed at runtime. |
| |
| --- |
| |
| # Testing |
| |
| - Lowering part is tested with LIT tests in tree |
| - PDTs 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:461` not yet implement: derived type length parameters in allocate |
| - `flang/lib/Lower/Allocatable.cpp:645` not yet implement: deferred length type parameters |
| - `flang/lib/Lower/Bridge.cpp:454` not yet implemented: get length parameters from derived type BoxValue |
| - `flang/lib/Lower/ConvertExpr.cpp:341` not yet implemented: copy derived type with length parameters |
| - `flang/lib/Lower/ConvertExpr.cpp:993` not yet implemented: component with length parameters in structure constructor |
| - `flang/lib/Lower/ConvertExpr.cpp:1063` not yet implemented: component with length parameters in structure constructor |
| - `flang/lib/Lower/ConvertExpr.cpp:1146` not yet implemented: type parameter inquiry |
| - `flang/lib/Lower/ConvertExpr.cpp:2424` not yet implemented: creating temporary for derived type with length parameters |
| - `flang/lib/Lower/ConvertExpr.cpp:3742` not yet implemented: gather rhs LEN parameters in assignment to allocatable |
| - `flang/lib/Lower/ConvertExpr.cpp:4725` not yet implemented: derived type array expression temp with LEN parameters |
| - `flang/lib/Lower/ConvertExpr.cpp:6400` not yet implemented: PDT size |
| - `flang/lib/Lower/ConvertExpr.cpp:6419` not yet implemented: PDT offset |
| - `flang/lib/Lower/ConvertExpr.cpp:6679` not yet implemented: array expr type parameter inquiry |
| - `flang/lib/Lower/ConvertExpr.cpp:7135` not yet implemented: need to adjust type parameter(s) to reflect the final component |
| - `flang/lib/Lower/ConvertType.cpp:334` not yet implemented: parameterized derived types |
| - `flang/lib/Lower/ConvertType.cpp:370` not yet implemented: derived type length parameters |
| - `flang/lib/Lower/ConvertVariable.cpp:169` not yet implemented: initial-data-target with derived type length parameters |
| - `flang/lib/Lower/ConvertVariable.cpp:197` not yet implemented: initial-data-target with derived type length parameters |
| - `flang/lib/Lower/VectorSubscripts.cpp:121` not yet implemented: threading length parameters in field index op |
| - `flang/lib/Optimizer/Builder/BoxValue.cpp:60` not yet implemented: box value is missing type parameters |
| - `flang/lib/Optimizer/Builder/BoxValue.cpp:67` not yet implemented: mutable box value is missing type parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:688` not yet implemented: read fir.box with length parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:746` not yet implemented: generate code to get LEN type parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:779` not yet implemented: derived type with type parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:905` not yet implemented: allocatable and pointer components non deferred length parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:917` not yet implemented: array component shape depending on length parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:924` not yet implemented: get character component length from length type parameters |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:934` not yet implemented: lower component ref that is a derived type with length parameter |
| - `flang/lib/Optimizer/Builder/FIRBuilder.cpp:956` not yet implemented: get length parameters from derived type BoxValue |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:70` not yet implemented: updating mutablebox of derived type with length parameters |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:168` not yet implemented: read allocatable or pointer derived type LEN parameters |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:310` not yet implemented: update allocatable derived type length parameters |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:505` not yet implemented: pointer assignment to derived with length parameters |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:597` not yet implemented: pointer assignment to derived with length parameters |
| - `flang/lib/Optimizer/Builder/MutableBox.cpp:740` not yet implemented: reallocation of derived type entities with length parameters |
| |
| |
| Current list of TODOs in code generation: |
| |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:1034` not yet implemented: fir.allocmem codegen of derived type with length parameters |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:1581` not yet implemented: generate call to calculate size of PDT |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:1708` not yet implemented: fir.embox codegen of derived with length parameters |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:1749` not yet implemented: reboxing descriptor of derived type with length parameters |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:2229` not yet implemented: derived type with type parameters |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:2256` not yet implemented: compute size of derived type with type parameters |
| - `flang/lib/Optimizer/CodeGen/TypeConverter.h:257` not yet implemented: extended descriptor derived with length parameters |
| |
| Current list of TODOs in optimizations: |
| |
| - `flang/lib/Optimizer/Transforms/ArrayValueCopy.cpp:1007` not yet implemented: unhandled dynamic type parameters |
| |
| --- |
| |
| Resources: |
| - [0] Fortran standard |
| - [1] https://en.wikipedia.org/wiki/Dependent_type |