| <!--===- docs/AssumedRank.md |
| |
| Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| See https://llvm.org/LICENSE.txt for license information. |
| SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| --> |
| # Assumed-Rank Objects |
| |
| An assumed-rank dummy data object is a dummy argument that takes its rank from |
| its effective argument. It is a dummy argument, or the associated entity of a |
| SELECT RANK in the `RANK DEFAULT` block. Its rank is not known at compile |
| time. The rank can be anything from 0 (scalar) to the maximum allowed rank in |
| Fortran (currently 15 according to Fortran 2018 standard section 5.4.6 point |
| 1). |
| |
| This document summarizes the contexts where assumed-rank objects can appear, |
| and then describes how they are implemented and lowered to HLFIR and FIR. All |
| section references are made to the Fortran 2018 standard. |
| |
| ## Fortran Standard References |
| |
| Here is a list of sections and constraints from the Fortran standard involving |
| assumed-ranks. |
| |
| - 7.3.2.2 TYPE |
| - C711 |
| - 7.5.6.1 FINAL statement |
| - C789 |
| - 8.5.7 CONTIGUOUS attribute |
| - C830 |
| - 8.5.8 DIMENSION attribute |
| - 8.5.8.7 Assumed-rank entity |
| - C837 |
| - C838 |
| - C839 |
| - 11.1.10 SELECT RANK |
| - 15.5.2.13 Restrictions on entities associated with dummy arguments |
| - 1 (3) (b) and (c) |
| - 1 (4) (b) and (c) |
| - 15.5.2.4 Ordinary dummy variables - point 17 |
| - 18 Interoperability with C |
| - 18.3.6 point 2 (5) |
| |
| ### Summary of the constraints: |
| |
| Assumed-rank can: |
| - be pointers, allocatables (or have neither of those atttributes). |
| - be monomorphic or polymorphic (both `TYPE(*)` and `CLASS(*)`) |
| - have all the attributes, except VALUE and CODIMENSION (C837). Notably, they |
| can have the CONTIGUOUS or OPTIONAL attributes (C830). |
| - appear as an actual argument of an assumed-rank dummy (C838) |
| - appear as the selector of SELECT RANK (C838) |
| - appear as the argument of C_LOC and C_SIZEOF from ISO_C_BINDING (C838) |
| - appear as the first argument of inquiry intrinsic functions (C838). These |
| inquiry functions listed in table 16.1 are detailed in the "Assumed-rank |
| features" section below. |
| - appear in BIND(C) and non BIND(C interface (18.1 point 3) |
| - be finalized on entry as INTENT(OUT) under some conditions that prevents the |
| assumed-rank to be associated with an assumed-size. |
| - be associated with any kind of scalars and arrays, including assumed-size. |
| |
| Assumed-rank cannot: |
| - be coarrays (C837) |
| - have the VALUE attribute (C837) |
| - be something that is not a named variable (they cannot be the result of a |
| function or a component reference) |
| - appear in a designator other than the case listed above (C838). Notably, they |
| cannot be directly addressed, they cannot be used in elemental operations or |
| transformational intrinsics, they cannot be used in IO, they cannot be |
| assigned to.... |
| - be finalized on entry as INTENT(OUT) if it could be associated with an |
| assumed-size (C839). |
| - be used in a reference to a procedure without an explicit interface |
| (15.4.2.2. point 3 (c)). |
| |
| With regard to aliasing, assumed-rank dummy objects follow the same rules as |
| for assumed shapes, with the addition of 15.5.2.13 (c) which adds a rule when |
| the actual is a scalar (adding that TARGET assumed-rank may alias if the actual |
| argument is a scalar even if they have the CONTIGUOUS attribute, while it is OK |
| to assume that CONTIGUOUS TARGET assumed shape do not alias with other |
| dummies). |
| |
| --- |
| |
| ## Assumed-Rank Representations in Flang |
| |
| ### Representation in Semantics |
| In semantics (there is no concept of assumed-rank expression needed in |
| `evaluate::Expr`). Such symbols have either `semantics::ObjectEntityDetails` ( |
| dummy data objects) with a `semantics::ArraySpec` that encodes the |
| "assumed-rank-shape" (can be tested with IsAssumedRank()), or they have |
| `semantics::AssocEntityDetails` (associated entity in the RANK DEFAULT case). |
| |
| Inside a select rank, a `semantics::Symbol` is created for the associated |
| entity with `semantics::AssocEntityDetails` that points to the the selector |
| and holds the rank outside of the RANK DEFAULT case. |
| |
| Assumed-rank dummies are also represented in the |
| `evaluate::characteristics::TypeAndShape` (with the AssumedRank attribute) to |
| represent assumed-rank in procedure characteristics. |
| |
| ### Runtime Representation of Assumed-Ranks |
| Assumed-ranks are implemented as CFI_cdesc_t (18.5.3) with the addition of an |
| f18 specific addendum when required for the type. This is the usual f18 |
| descriptor, and no changes is required to represent assumed-ranks in this data |
| structure. In fact, there is no difference between the runtime descriptor |
| created for an assumed shape and the runtime descriptor created when the |
| corresponding entity is passed as an assumed-rank. |
| |
| This means that any descriptor can be passed to an assumed-rank dummy (with |
| care to ensure that the POINTER/ALLOCATABLE attribute match the dummy argument |
| attributes as usual). Notably, any runtime interface that takes descriptor |
| arguments of any ranks already work with assumed-rank entities without any |
| changes or special cases. |
| |
| This also implies that the runtime cannot tell that an entity is an |
| assumed-rank based on its descriptor, but there seems to be not need for this |
| so far ("rank based" dispatching for user defined assignments and IO is not |
| possible with assumed-ranks, and finalization is possible, but there is no need |
| for the runtime to distinguish between finalization of an assumed-rank and |
| finalization of other entities: only the runtime rank matters). |
| |
| The only difference and difficulty is that descriptor storage size of |
| assumed-rank cannot be precisely known at compile time, and this impacts the |
| way descriptor copies are generated in inline code. The size can still be |
| maximized using the maximum rank, which the runtime code already does when |
| creating temporary descriptor in many cases. Inline code also needs care if it |
| needs to access the descriptor addendum (like the type descriptor), since its |
| offset will not be a compile time constant as usual. |
| |
| Note that an alternative to maximizing the allocation of assumed-rank temporary |
| descriptor could be to use automatic allocation based on the rank of the input |
| descriptor, but this would make stack allocation analysis more complex (tools |
| will likely not have the Fortran knowledge that this allocation size is bounded |
| for instance) while the stack "over" allocation is likely reasonable (24 bytes |
| per dimension). Hence the selection of the simple approach using static size |
| allocation to the maximum rank. |
| |
| ### Representation in FIR and HLFIR |
| SSA values for assumed-rank entities have an MLIR type containing a |
| `!fir.array<*xT>` sequence type wrapped in a `!fir.box` or `!fir.class` type |
| (additionally wrapped in a `!fir.ref` type for pointers and allocatables). |
| |
| Examples: |
| `INTEGER :: x(..)` -> `!fir.box<!fir.array<* x i32>>` |
| `CLASS(*) :: x(..)` -> `!fir.class<!fir.array<* x none>>` |
| `TYPE(*) :: x(..)` -> `!fir.box<!fir.array<* x none>>` |
| `REAL, ALLOCATABLE :: x(..)` -> `!fir.ref<!fir.box<!fir.heap<!fir.array<* x f32>>>>` |
| `TYPE(t), POINTER :: x(..)` -> `!fir.ref<!fir.box<!fir.ptr<!fir.array<* x !fir.type<t>>>>>` |
| |
| All these FIR types are implemented as the address of a CFI_cdesc_t in code |
| generation. |
| |
| There is no need to allow assumed-rank "expression" in HLFIR (hlfir.expr) since |
| assumed-rank cannot appear in expressions (except as the actual argument to an |
| assumed-rank dummy). Assumed-rank are variables. Also, since they cannot have |
| the VALUE attribute, there is no need to use the hlfir.as_expr + |
| hlfir.associate idiom to make copies for them. |
| |
| FIR/HLFIR operation where assumed-rank may appear: |
| - as `hlfir.declare` and `fir.declare` operand and result. |
| - as `fir.convert` operand and/or result. |
| - as `fir.load` operand and result (POINTER and ALLOCATABLE dereference). |
| - as a block argument (dummy argument). |
| - as `fir.rebox_assumed_rank` operand/result (new operation to change some |
| fields of assumed-rank descriptors). |
| - as `fir.box_rank` operand (rank inquiry). |
| - as `fir.box_dim` operand (brutal user inquiry about the bounds of an |
| assumed-rank in a compile time constant dimension). |
| - as `fir.box_addr` operand (to get the base address in inlined code for |
| C_LOC). |
| - as `fir.box_elesize` operand (to implement LEN and STORAGE_SIZE). |
| - as `fir.absent` result (passing absent actual to OPTIONAL assumed-rank dummy) |
| - as `fir.is_present` operand (PRESENT inquiry) |
| - as `hlfir.copy_in` and `hlfir.copy_out` operand and result (copy in and |
| copy-out of assumed-rank) |
| - as `fir.alloca` type and result (when creating an assumed-rank POINTER dummy |
| from a non POINTER dummy). |
| - as `fir.store` operands (same case as `fir.alloca`). |
| |
| FIR/HLFIR Operations that should not need to accept assumed-ranks but where it |
| could still be relevant: |
| - `fir.box_tdesc` and `fir.box_typecode` (polymorphic assumed-rank cannot |
| appear in a SELECT TYPE directly without using a SELECT RANK). Given the |
| CFI_cdesc_t structure, no change would be needed for `fir.box_typecode` to |
| support assumed-ranks, but `fir.box_tdesc` would require change since the |
| position of the type descriptor pointer depends on the rank. |
| - as `fir.allocmem` / `fir.global` result (assumed-ranks are never local/global |
| entities). |
| - as `fir.embox` result (When creating descriptor for an explicit shape, the |
| descriptor can be created with the entity rank, and then casted via |
| `fir.convert`). |
| |
| It is not expected for any other FIR or HLFIR operations to handle assumed-rank |
| SSA values. |
| |
| #### Summary of the impact in FIR |
| One new operation is needed, `fir.rebox_assumed_rank`, the rational being that |
| fir.rebox codegen is already quite complex and not all the aspects of fir.rebox |
| matters for assumed-ranks (only simple field changes are required with |
| assumed-ranks). Also, this operation will be allowed to take an operand in |
| memory to avoid expensive fir.load of pointer/allocatable inputs. The operation |
| will also allow creating rank-one assumed-size descriptor from an input |
| assumed-rank descriptor to cover the SELECT RANK `RANK(*)` case. |
| |
| It is proposed that the FIR descriptor inquiry operation (fir.box_addr, |
| fir.box_rank, fir.box_dim, fir.box_elesize at least) be allowed to take |
| fir.ref<fir.box> arguments (allocatable and pointer descriptors) directly |
| instead of generating a fir.load first. A conditional "read" effect will be |
| added in such case. Again, the purpose is to avoid generating descriptor copies |
| for the sole purpose of satisfying the SSA IR constraints. This change will |
| likely benefit the non assumed-rank case too (even though LLVM is quite good at |
| removing pointless descriptor copies in these cases). |
| |
| It will be ensured that all the operation listed above accept assumed-rank |
| operands (both the verifiers and coedgen). The codegen of `fir.load`, |
| `fir.alloca`, `fir.store`, `hlfir.copy_in` and `hlfir.copy_out` will need |
| special handling for assumed-ranks. |
| |
| ### Representation in LLVM IR |
| |
| Assumed-rank descriptor types are lowered to the LLVM type of a CFI_cdesc_t |
| descriptor with no dimension array field and no addendum. That way, any inline |
| code attempt to directly access dimensions and addendum with constant offset |
| will be invalid for more safety, but it will still be easy to generate LLVM GEP |
| to address the first descriptor fields in LLVM (to get the base address, rank, |
| type code and attributes). |
| |
| `!fir.box<!fir.array<* x i32>>` -> `!llvm.struct<(ptr, i64, i32, i8, i8, i8, i8>` |
| |
| ## Assumed-rank Features |
| |
| This section list the different Fortran features where assumed-rank objects are |
| involved and describes the related implementation design. |
| |
| ### Assumed-rank in procedure references |
| Assumed-rank arguments are implemented as being the address of a CFI_cdesc_t. |
| |
| When passing an actual argument to an assumed-rank dummy, the following points |
| need special attention and are further described below: |
| - Copy-in/copy-out when required |
| - Creation of forwarding of the assumed-rank dummy descriptor (including when |
| the actual is an assumed-size). |
| - Finalization, deallocation, and initialization of INTENT(OUT) assumed-rank |
| dummy. |
| |
| OPTIONAL assumed-ranks are implemented like other non assumed-rank OPTIONAL |
| objects passed by descriptor: an absent assumed-rank is represented by a null |
| pointer to a CFI_cdesc_t. |
| |
| The passing interface for assumed-rank described above and below is compliant |
| by default with the BIND(C) case, except for the assumed-rank dummy descriptor |
| lower bounds, which are only set to zeros in BIND(C) interface because it |
| implies in most of the cases to create a new descriptor. |
| |
| VALUE is forbidden for assumed-rank dummies, so there is nothing to be done for |
| it (although since copy-in/copy-out is possible, the compiler must anyway deal |
| with creating assumed-rank copies, so it would likely not be an issue to relax |
| this constraint). |
| |
| #### Copy-in and Copy out |
| Copy-in and copy-out is required when passing an actual that is not contiguous |
| to a non POINTER CONTIGUOUS assumed-rank. |
| |
| When the actual argument is ranked, the copy-in/copy-out can be performed on |
| the ranked actual argument where the dynamic type has been aligned with the |
| dummy type if needed (passing CLASS(T) to TYPE(T)) as illustrated below. |
| |
| ```Fortran |
| module m |
| type t |
| Â integer :: i |
| end type |
| contains |
| subroutine foo(x) |
| Â class(t) :: x(:) |
| Â interface |
| Â subroutine bar(x) |
| Â Â import :: t |
| Â Â type(t), contiguous :: x(..) |
| Â end subroutine |
| Â end interface |
| Â ! copy-in and copy-out is required aroud bar |
| Â call bar(x) |
| end |
| end module |
| ``` |
| |
| When the actual is also an assumed-rank special the same copy-in/copy-out need |
| may arise, and the `hlfir.copy_in` and `hlfir.copy_out` are also used to cover |
| this case. The `hlfir.copy_in`operation is implemented using the `IsContiguous` |
| runtime (can be used as-is) and the `AssignTemporary` temporary runtime. |
| |
| The difference with the ranked case is that more care is needed to create the |
| output descriptor passed to `AssignTemporary`: it must be allocated to the |
| maximum rank with the same type as the input descriptor and only the descriptor |
| fields prior to the array dimensions will be initialized to those of an |
| unallocated descriptor prior to the runtime call (`AssignTemporary` copies the |
| addendum if needed). |
| |
| ```Fortran |
| subroutine foo2(x) |
| Â class(t) :: x(..) |
| Â interface |
| Â subroutine bar(x) |
| Â Â import :: t |
| Â Â type(t), contiguous :: x(..) |
| Â end subroutine |
| Â end interface |
| Â ! copy-in and copy-out is required aroud bar |
| Â call bar(x) |
| end |
| ``` |
| #### Creating the descriptor for assumed-rank dummies |
| |
| There are four cases to distinguish: |
| 1. Actual does not have a descriptor (and is therefore ranked) |
| 2. Actual has a descriptor that can be forwarded for the dummy |
| 3. Actual has a ranked descriptor that cannot be forwarded for the dummy |
| 4. Actual has an assumed-rank descriptor that cannot be forwarded for the dummy |
| |
| For the first case, a descriptor will be created for the dummy with `fir.embox` |
| has if it has the rank of the actual argument. This is the same logic as when |
| dealing with assumed shape or INTENT(IN) POINTER dummy arguments, except that |
| an extra cast to the assumed-rank descriptor type is added (no-op at runtime). |
| Care must be taken to set the final dimension extent to -1 in the descriptor |
| created for an assumed-size actual argument. Note that the descriptor created |
| for an assumed-size still has the rank of the assumed-size, a rank-one |
| descriptor will be created for it if needed in a RANK(*) block (nothing says |
| that an assumed-size should be passed as a rank-one array in 15.5.2.4 point 17). |
| |
| For the second case, a cast is added to assumed-rank descriptor type if it is |
| not one already and the descriptor is forwarded. |
| |
| For the third case, a new ranked descriptor with the dummy attribute/lower |
| bounds is created from the actual argument descriptor with `fir.rebox` as it is |
| done when passing to an assume shape dummy, and a cast to the assumed-rank |
| descriptor is added . |
| |
| The last case is the same as the third one, except the that the descriptor |
| manipulation is more complex since the storage size of the descriptor is |
| unknown. `fir.rebox` codegen is already quite complex since it deals with |
| creating descriptor for descriptor based array sections and pointer remapping. |
| Both of those are meaningless in this case where the output descriptor is the |
| same as the input one, except for the lower bounds, attribute, and derived type |
| pointer field that may need to be changed to match the values describing the |
| dummy. A simpler `fir.rebox_assumed_rank` operation is added for this use case. |
| Notably, this operation can take fir.ref<fir.box> inputs to avoid creating an |
| expensive and useless fir.load of POINTER/ALLOCATABLE descriptors. |
| |
| Fortran requires the compiler to fall in the 3rd and 4th case and create |
| descriptor temporary for the dummy a lot more than one would think and hope. An |
| annex section below discusses cases that force the compiler to create a new |
| descriptor for the dummy even if the actual already has a descriptor. These are |
| the same situations than with non assumed-rank arguments, but when passing |
| assumed-rank to assumed-ranks, the cost of this extra copy is higher. |
| |
| #### Intent(out) assumed-rank finalization, deallocation, initialization |
| |
| The standard prevents INTENT(OUT) assumed-rank requiring finalization to be |
| associated with assumed-size arrays (C839) because there would be no way to |
| finalize such entities. But INTENT(OUT) finalization is still possible if the |
| actual is not an assumed-size and not a nonpointer nonallocatable assumed-rank. |
| |
| Flang therefore needs to implement finalization, deallocation and |
| initialization of INTENT(OUT) as usual. Non pointer non allocatable INTENT(OUT) |
| finalization is done via a call to `Destroy` runtime API that takes a |
| descriptor and can be directly used with an assumed-rank descriptor with no |
| change. The initialization is done via a call to the `Initialize` runtime API |
| that takes a descriptor and can also directly be used with an assumed |
| descriptor. Conditional deallocation of INTENT(OUT) allocatable is done via an |
| inline allocation status check and either an inline deallocate for intrinsic |
| types, or a runtime call to `Deallocate` for the other cases. For |
| assumed-ranks, the runtime call is always used regardless of the type to avoid |
| inline descriptor manipulations. `Deallocate` runtime API also works with |
| assumed-rank descriptors with no changes (like any runtime API taking |
| descriptors of any rank). |
| |
| ```Fortran |
| subroutine foo(x) |
| Â class(*), allocatable :: x(..) |
| Â interface |
| Â subroutine bar(x) |
| Â Â class(*), intent(out) :: x(..) |
| Â end subroutine |
| Â end interface |
| Â ! x may require finalization and initialization on bar entry. |
| Â call bar(x) |
| end |
| subroutine bar(x) |
| Â class(*), intent(out) :: x(..) |
| end subroutine |
| ``` |
| ### Select Rank |
| |
| Select rank is implemented with a rank inquiry (and last extent for `RANK(*)`), |
| followed by a jump in the related block where the selector descriptor is cast |
| to a descriptor with the associated entity rank for the current block for the |
| `RANK(cst)` cases. In the `RANK DEFAULT`, the input descriptor is kept with no |
| cast, and in the RANK(*), a rank-one descriptor is created with the same |
| dynamic type as the input. |
| These new descriptor values are mapped to the associated entity symbol and |
| lowering precede as usual. This is very similar to how Select Type is |
| implemented. The `RANK(*)` is a bit odd, it detects assumed-ranks associated |
| with an assumed-size arrays regardless of the rank, and takes precedence over |
| any rank based matching. |
| |
| Note that `-1` is a magic extent number that encodes that a descriptor describes |
| an entity that is an assumed-size (user specified extents of explicit shape |
| arrays are always normalized to zero when negative, so `-1` is a safe value to |
| identify a descriptor created for an assumed-size). It is actually well |
| specified for the BIND(C) (18.5.2 point 1.) and is always used as such in flang |
| descriptors. |
| |
| The implementation of SELECT RANK is done as follow: |
| - Read the rank `r` in the descriptor |
| - If there is a `RANK(*)`, read the extent in dimension `r`. If it is `-1`, |
| jump to the `RANK(*)` block. Otherwise, continue to the steps below. |
| - For each `RANK(constant)` case, compare `constant` to `r`. Stop at first |
| match and jump to related block. The order of the comparisons does not matter |
| (there cannot be more than one match). |
| - Jump to `RANK DEFAULT` block is any. Otherwise jump to the end of the |
| construct. |
| |
| The blocks for each cases jumps at the end of the construct at the end. As |
| opposed to SELECT TYPE, no clean-up should be needed at the construct level |
| since the select-rank selector is a named entity and cannot be a temporary with |
| a lifetime of the construct. |
| |
| Except for the `RANK(*)` case, the branching logic is implemented in FIR with a |
| `fir.select_case` operating on the rank. |
| |
| Example: |
| |
| ```Fortran |
| subroutine test(x) |
| interface |
| subroutine assumed_size(x) |
| real :: x(*) |
| end subroutine |
| subroutine scalar(x) |
| real :: x |
| end subroutine |
| subroutine rank_one(x) |
| real :: x(:) |
| end subroutine |
| subroutine many_dim_array(x) |
| real :: x(..) |
| end subroutine |
| end interface |
| |
| real :: x(..) |
| select rank (y => x) |
| rank(*) |
| call assumed_size(y) |
| rank(0) |
| call scalar(y) |
| rank(1) |
| call rank_one(y) |
| rank default |
| call many_dim_array(y) |
| end select |
| end subroutine |
| ``` |
| |
| Pseudo FIR for the example (some converts and SSA constants creation are not shown for more clarity): |
| |
| ``` |
| func.func @_QPtest(%arg0: !fir.box<!fir.array<?xf32>>) { |
| %x:2 = hlfir.declare %arg0 {uniq_name = "_QFtestEx"} : (!fir.box<!fir.array<*xf32>>) -> (!fir.box<!fir.array<*xf32>>, !fir.box<!fir.array<*xf32>>) |
| %r = fir.box_rank %x#1 : (!fir.box<!fir.array<*xf32>>) -> i32 |
| %last_extent = fir.call @_FortranASizeDim(%x#1, %r, %sourcename, %sourceline) |
| %is_assumed_size = arith.cmpi eq %last_extent, %c-1: (i64, i64) -> i1 |
| cf.cond_br %is_assumed_size, ^bb_assumed_size, ^bb_not_assumed_size |
| ^bb_assumed_size: |
| %r1_box = fir.rebox_assumed_rank %x#0 : (!fir.box<!fir.array<*xf32>>) -> !fir.box<!fir.array<?xf32>> |
| %addr = fir.box_addr %addr, !fir.box<!fir.array<?xf32>> -> !fir.ref<!fir.array<?xf32>> |
| fir.call @_QPassumed_size(%addr) (!fir.ref<!fir.array<?xf32>>) -> () |
| cf.br ^bb_end |
| ^bb_not_assumed_size: |
| fir.select_case %3 : i32 [#fir.point, %c0, ^bb_scalar, #fir.point, %c1, ^bb_rank1, unit, ^bb_default] |
| ^bb_scalar: |
| %scalar_cast = fir.convert %x#1 : (!fir.box<!fir.array<*xf32>>) -> !fir.box<f32> |
| %x_scalar = fir.box_addr %scalar_cast: (!fir.box<f32>) -> !fir.ref<f32> |
| fir.call @_QPscalar(%x_scalar) (!fir.ref<f32>) -> () |
| cf.br ^bb_end |
| ^bb_rank1: |
| %rank1_cast = fir.convert %x#1 : (!fir.box<!fir.array<*xf32>>) -> !fir.box<!fir.array<?xf32>> |
| fir.call @_QPrank_one(%rank1_cast) (!fir.box<!fir.array<?xf32>>) -> () |
| cf.br ^bb_end |
| ^bb_default: |
| fir.call @_QPmany_dim_array(%x#1) (!fir.box<!fir.array<*xf32>>) -> () |
| cf.br ^bb_end |
| ^bb_end |
| return |
| } |
| ``` |
| |
| ### Inquiry intrinsic functions |
| #### ALLOCATED and ASSOCIATED |
| Implemented inline with `fir.box_addr` (reading the descriptor first address |
| inline). Currently, FIR descriptor inquiry happens at the "descriptor value" |
| level (require a fir.load of the POINTER or ALLOCATABLE !fir.ref<!fir.box<>>), |
| to satisfy the SSA value semantics, the fir.load creates a copy of the |
| underlying descriptor storage. With assume ranks, this copy will be "expensive" |
| and harder to optimize out given the descriptor storage size is not a compile |
| time constant. To avoid this extra cost, ALLOCATABLE and POINTER assumed-ranks |
| will be cast to scalar descriptors before the `fir.load`. |
| |
| ```Fortran |
| real, allocatable :: x(..) |
| print *, allocated(x) |
| ``` |
| |
| ``` |
| %1 = fir.convert %x : (!fir.ref<!fir.box<!fir.heap<!fir.array<* x f32>>>>) -> !fir.ref<!fir.box<!fir.heap<f32>>> |
| %2 = fir.load %x : !fir.ref<!fir.box<!fir.heap<f32>>> |
| %addr = fir.box_addr %2 : (!fir.box<!fir.heap<f32>>) -> fir.ref<f32> |
| # .... "addr != null" as usual |
| ``` |
| #### LEN and STORAGE_SIZE |
| Implemented inline with `fir.box_elesize` with the same approach as |
| ALLOCATED/ASSOCIATED when dealing with fir.box load for POINTERS and |
| ALLOCATABLES. |
| |
| ```Fortran |
| character(*) :: x(..) |
| print *, len(x) |
| ``` |
| |
| ``` |
| %ele_size = fir.box_elesize %x : (!fir.box<!fir.array<*x!fir.char<?>>>) -> i64 |
| # .... divide by character KIND byte size if needed as usual |
| ``` |
| #### PRESENT |
| Implemented inline with `fir.is_present` which ends-up implemented as a check |
| that the descriptor address is not null just like with OPTIONAL assumed shapes |
| and OPTIONAL pointers and allocatables. |
| |
| ```Fortran |
| real, optional :: x(..) |
| print *, present(x) |
| ``` |
| |
| ``` |
| %is_present = fir.is_prent %x : (!fir.box<!fir.array<*xf32>>) -> i1 |
| ``` |
| #### RANK |
| Implemented inline with `fir.box_rank` which simply reads the descriptor rank |
| field. |
| |
| ```Fortran |
| real :: x(..) |
| print *, len(x) |
| ``` |
| |
| ``` |
| %rank = fir.box_rank %x : (!fir.box<!fir.array<*xf32>>) -> i32 |
| ``` |
| #### SIZE |
| Using the runtime can be queried as it is done for assumed shapes. When DIM is |
| present and is constant, `fir.box_dim` can also be used with the option to add |
| a runtime check that RANK <= DIM. Pointers and allocatables are dereferenced, |
| which in FIR currently creates a descriptor copy that cannot be simplified |
| like for the previous inquiries by inserting a cast before the fir.load (the |
| dimension info must be correctly copied). |
| |
| #### LBOUND, SHAPE, and UBOUND |
| When DIM is present an is present, the runtime can be used as it is currently |
| with assumed shapes. When DIM is absent, the result is a rank-one array whose |
| extent is the rank. The runtime has an entry for UBOUND that takes a descriptor |
| and allocate the result as needed, so the same logic as for assumed shape can |
| be used. |
| |
| There is no such entry for LBOUND/SHAPE currently, it would likely be best to |
| add one rather than to jungle with inline code. Pointers and allocatables |
| dereference is similar as with SIZE. |
| |
| #### EXTENDS_TYPE_OF, SAME_TYPE_AS, and IS_CONTIGUOUS |
| Using the runtime as it is done currently with assumed shapes. Pointers and |
| allocatables dereference is similar as with SIZE. |
| |
| #### C_LOC from ISO_C_BINDING |
| Implemented with `fir.box_addr` as with other C_LOC cases for entities that |
| have descriptors. |
| |
| #### C_SIZE_OF from ISO_C_BINDING |
| Implemented as STORAGE_SIZE * SIZE. |
| |
| #### Floating point inquiries and NEW_LINE |
| BIT_SIZE, DIGITS, EPSILON, HUGE, KIND, MAXEXPONENT, MINEXPONENT, NEW_LINE, |
| PRECISION, RADIX, RANGE, TINY all accept assumed-rank, but are always constant |
| folded by semantics based on the type and lowering does not need to deal with |
| them. |
| |
| #### Coarray inquiries |
| Assumed-rank cannot be coarrays (C837), but they can still technically appear |
| in COSHAPE (which should obviously return zero). They cannot appear in LBOUND, |
| LCOBOUND, UBOUND, UCOBOUND that require the argument to be a coarray. |
| |
| ## Annex 1 - Descriptor temporary for the dummy arguments |
| |
| When passing an actual argument that is descriptor to a dummy that must be |
| passed by descriptor, one could expect that the descriptor of the actual can |
| just be forwarded to the dummy, but this is unfortunately not possible in quite |
| some cases. This is not specific to assumed-ranks, but since the cost of |
| descriptor temporaries is higher for assumed-ranked, it is discussed here. |
| |
| Below are the reasons for which a new descriptor may be required: |
| 1. passing a POINTER to a non POINTER |
| 2. setting the descriptor CFI_cdesc_t `attribute` according to the dummy |
| POINTER/ALLOCATABLE attributes (18.3.6 point 4 for the BIND(C) case). |
| 3. setting the CFI_cdesc_t lower bounds to zero for a BIND(C) assumed |
| shape/rank dummy (18.5.3 point 3). |
| 4. setting the derived type pointer to the dummy dynamic type when passing a |
| CLASS() actual to a TYPE() dummy. |
| |
| Justification of 1.: |
| When passing a POINTER to a non POINTER, the target of the pointer is passed, |
| and nothing prevents the association status of the actual argument to change |
| during the call (e.g. if the POINTER is another argument of the call, or is a |
| module variable, it may be re-associated in the call). These association status |
| change of the actual should not impact the dummy, so they must not share the |
| same descriptor. |
| |
| Justification of 2.: |
| In the BIND(C) case, this is required by 18.3.6 point 4. Outside of the BIND(C) |
| case, this should still be done because any runtime call where the dummy |
| descriptor is forwarded may misbehave if the ALLOCATABLE/POINTER attribute is |
| not the one of the dummy (e.g. reallocation could be triggered instead of |
| padding/trimming characters). |
| |
| Justification of 3: |
| 18.5.3 point 3. |
| |
| Justification of 4: |
| If the descriptor derived type info pointer is not the one of the dummy dynamic |
| type, many runtime call like IO and assignment will misbehave when being |
| provided the dummy descriptor. |
| |
| For point 2., 3., and 4., one could be tempted to change the descriptor fields |
| before and after the call, but this is risky since this would assume nothing |
| will access the actual argument descriptor during the call. And even without |
| bringing any potential asynchronous behavior of OpenMP/OpenACC/Cuda Fortran |
| extensions, the actual argument descriptor may be passed inside a call in |
| another arguments with "different" lower bounds POINTER or ALLOCATABLE (but |
| could also be accessed via host of use association in general). |
| |
| |
| ## Annex 2 - Assumed-Rank Objects and IGNORE_TKR |
| |
| It is possible to: |
| - Set IGNORE_TKR(TK) on assumed-rank dummies (but TYPE(*) is better when |
| possible). |
| - Pass an assumed-rank to an IGNORE_TKR(R) dummy that is not passed |
| by descriptor (explicit shape and assumed-size). Note that copy-in and |
| copy-out will be performed for the dummy |
| |
| It is not possible to: |
| - Set IGNORE_TKR(R) on an assumed-rank dummy. |
| |
| Example: |
| |
| ```Fortran |
| subroutine test(assumed_rank_actual) |
| interface |
| subroutine assumed_size_dummy(x) |
| !dir$ ignore_tkr(tkr) x |
| integer :: x(*) |
| end subroutine |
| subroutine any_type_assumed_rank(x) |
| !dir$ ignore_tkr(tk) x |
| integer :: x(..) |
| end subroutine |
| end interface |
| real :: assumed_rank_actual(..) |
| call assumed_size_dummy(assumed_rank_actual) !OK |
| call any_type_assumed_rank(assumed_rank_actual) !OK |
| end subroutine |
| ``` |
| |
| ## Annex 3 - Test Plan |
| |
| MPI_f08 module makes usage of assumed-rank (see |
| https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report.pdf). As such compiling |
| MPI_f08 modules of MPI libraries and some applications making usage of MPI_f08 |
| will be a good test for the implementation of this feature. |