diff options
author | jeanPerier <jperier@nvidia.com> | 2023-11-17 11:10:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-17 11:10:15 +0100 |
commit | 915f6c3d6a4377e2672a95c656374d71df62e95a (patch) | |
tree | be892d4cbbe9d3633f71d0d106f59af3e3961b99 | |
parent | 58253dcbcdfafb1fb1fb5ffc43d6f11a31f35e2a (diff) | |
download | llvm-915f6c3d6a4377e2672a95c656374d71df62e95a.zip llvm-915f6c3d6a4377e2672a95c656374d71df62e95a.tar.gz llvm-915f6c3d6a4377e2672a95c656374d71df62e95a.tar.bz2 |
[flang][RFC] Adding a design document for assumed-rank objects (#71959)
This patch adds a document describing assumed-rank objects and the
related features as well as how they will be implemented in Flang.
-rw-r--r-- | flang/docs/AssumedRank.md | 688 | ||||
-rw-r--r-- | flang/docs/index.md | 1 |
2 files changed, 689 insertions, 0 deletions
diff --git a/flang/docs/AssumedRank.md b/flang/docs/AssumedRank.md new file mode 100644 index 0000000..c5d2c3e --- /dev/null +++ b/flang/docs/AssumedRank.md @@ -0,0 +1,688 @@ +<!--===- 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. diff --git a/flang/docs/index.md b/flang/docs/index.md index 5c9b889..a619507 100644 --- a/flang/docs/index.md +++ b/flang/docs/index.md @@ -41,6 +41,7 @@ on how to get in touch with us and to learn more about the current status. Aliasing AliasingAnalysisFIR ArrayComposition + AssumedRank BijectiveInternalNameUniquing Calls Character |