mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 12:06:06 +00:00
689 lines
30 KiB
Markdown
689 lines
30 KiB
Markdown
![]() |
<!--===- 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.
|