|  | <!--===- docs/FIRArrayOperations.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 | 
|  |  | 
|  | --> | 
|  |  | 
|  | # Design: FIR Array operations | 
|  |  | 
|  | ```eval_rst | 
|  | .. contents:: | 
|  | :local: | 
|  | ``` | 
|  |  | 
|  | ## General | 
|  |  | 
|  | The array operations in FIR model the copy-in/copy-out semantics over Fortran | 
|  | statements. | 
|  |  | 
|  | Fortran language semantics sometimes require the compiler to make a temporary | 
|  | copy of an array or array slice. Situations where this can occur include: | 
|  |  | 
|  | * Passing a non-contiguous array to a procedure that does not declare it as | 
|  | assumed-shape. | 
|  | * Array expressions, especially those involving `RESHAPE`, `PACK`, and `MERGE`. | 
|  | * Assignments of arrays where the array appears on both the left and right-hand | 
|  | sides of the assignment. | 
|  | * Assignments of `POINTER` arrays. | 
|  |  | 
|  | There are currently the following operations: | 
|  | - `fir.array_load` | 
|  | - `fir.array_merge_store` | 
|  | - `fir.array_fetch` | 
|  | - `fir.array_update` | 
|  | - `fir.array_access` | 
|  | - `fir.array_amend` | 
|  |  | 
|  | `array_load`(s) and `array_merge_store` are a pairing that brackets the lifetime | 
|  | of the array copies. | 
|  |  | 
|  | `array_fetch` and `array_update` are defined to work as getter/setter pairs on | 
|  | values of elements from loaded array copies. These have "GEP-like" syntax and | 
|  | semantics. | 
|  |  | 
|  | Fortran arrays are implicitly memory bound as are some other Fortran type/kind | 
|  | entities. For entities that can be atomically promoted to the value domain, | 
|  | we use `array_fetch` and `array_update`. | 
|  |  | 
|  | `array_access` and `array_amend` are defined to work as getter/setter pairs on | 
|  | references to elements in loaded array copies. `array_access` has "GEP-like" | 
|  | syntax. `array_amend` annotates which loaded array copy is being written to. | 
|  | It is invalid to update an array copy without `array_amend`; doing so will | 
|  | result in undefined behavior. | 
|  | For those type/kinds that cannot be promoted to values, we must leave them in a | 
|  | memory reference domain, and we use `array_access` and `array_amend`. | 
|  |  | 
|  | ## array_load | 
|  |  | 
|  | This operation taken with `array_merge_store` captures Fortran's | 
|  | copy-in/copy-out semantics. One way to think of this is that array_load | 
|  | creates a snapshot copy of the entire array. This copy can then be used | 
|  | as the "original value" of the array while the array's new value is | 
|  | computed. The `array_merge_store` operation is the copy-out semantics, which | 
|  | merge the updates with the original array value to produce the final array | 
|  | result. This abstracts the copy operations as opposed to always creating | 
|  | copies or requiring dependence analysis be performed on the syntax trees | 
|  | and before lowering to the IR. | 
|  |  | 
|  | Load an entire array as a single SSA value. | 
|  |  | 
|  | ```fortran | 
|  | real :: a(o:n,p:m) | 
|  | ... | 
|  | ... = ... a ... | 
|  | ``` | 
|  |  | 
|  | One can use `fir.array_load` to produce an ssa-value that captures an | 
|  | immutable value of the entire array `a`, as in the Fortran array expression | 
|  | shown above. Subsequent changes to the memory containing the array do not | 
|  | alter its composite value. This operation lets one load an array as a | 
|  | value while applying a runtime shape, shift, or slice to the memory | 
|  | reference, and its semantics guarantee immutability. | 
|  |  | 
|  | ```mlir | 
|  | %s = fir.shape_shift %lb1, %ex1, %lb2, %ex2 : (index, index, index, index) -> !fir.shapeshift<2> | 
|  | // load the entire array 'a' | 
|  | %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shapeshift<2>) -> !fir.array<?x?xf32> | 
|  | // a fir.store here into array %a does not change %v | 
|  | ``` | 
|  |  | 
|  | # array_merge_store | 
|  |  | 
|  | The `array_merge_store` operation stores a merged array value to memory. | 
|  |  | 
|  |  | 
|  | ```fortran | 
|  | real :: a(n,m) | 
|  | ... | 
|  | a = ... | 
|  | ``` | 
|  |  | 
|  | One can use `fir.array_merge_store` to merge/copy the value of `a` in an | 
|  | array expression as shown above. | 
|  |  | 
|  | ```mlir | 
|  | %v = fir.array_load %a(%shape) : ... | 
|  | %r = fir.array_update %v, %f, %i, %j : (!fir.array<?x?xf32>, f32, index, index) -> !fir.array<?x?xf32> | 
|  | fir.array_merge_store %v, %r to %a : !fir.ref<!fir.array<?x?xf32>> | 
|  | ``` | 
|  |  | 
|  | This operation merges the original loaded array value, `%v`, with the | 
|  | chained updates, `%r`, and stores the result to the array at address, `%a`. | 
|  |  | 
|  | This operation taken with `array_load`'s captures Fortran's | 
|  | copy-in/copy-out semantics. The first operands of `array_merge_store` is the | 
|  | result of the initial `array_load` operation. While this value could be | 
|  | retrieved by reference chasing through the different array operations it is | 
|  | useful to have it on hand directly for analysis passes since this directly | 
|  | defines the "bounds" of the Fortran statement represented by these operations. | 
|  | The intention is to allow copy-in/copy-out regions to be easily delineated, | 
|  | analyzed, and optimized. | 
|  |  | 
|  | ## array_fetch | 
|  |  | 
|  | The `array_fetch` operation fetches the value of an element in an array value. | 
|  |  | 
|  | ```fortran | 
|  | real :: a(n,m) | 
|  | ... | 
|  | ... a ... | 
|  | ... a(r,s+1) ... | 
|  | ``` | 
|  |  | 
|  | One can use `fir.array_fetch` to fetch the (implied) value of `a(i,j)` in | 
|  | an array expression as shown above. It can also be used to extract the | 
|  | element `a(r,s+1)` in the second expression. | 
|  |  | 
|  | ```mlir | 
|  | %s = fir.shape %n, %m : (index, index) -> !fir.shape<2> | 
|  | // load the entire array 'a' | 
|  | %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32> | 
|  | // fetch the value of one of the array value's elements | 
|  | %1 = fir.array_fetch %v, %i, %j : (!fir.array<?x?xf32>, index, index) -> f32 | 
|  | ``` | 
|  |  | 
|  | It is only possible to use `array_fetch` on an `array_load` result value or a | 
|  | value that can be trace back transitively to an `array_load` as the dominating | 
|  | source. Other array operation such as `array_update` can be in between. | 
|  |  | 
|  | ## array_update | 
|  |  | 
|  | The `array_update` operation is used to update the value of an element in an | 
|  | array value. A new array value is returned where all element values of the input | 
|  | array are identical except for the selected element which is the value passed in | 
|  | the update. | 
|  |  | 
|  | ```fortran | 
|  | real :: a(n,m) | 
|  | ... | 
|  | a = ... | 
|  | ``` | 
|  |  | 
|  | One can use `fir.array_update` to update the (implied) value of `a(i,j)` | 
|  | in an array expression as shown above. | 
|  |  | 
|  | ```mlir | 
|  | %s = fir.shape %n, %m : (index, index) -> !fir.shape<2> | 
|  | // load the entire array 'a' | 
|  | %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32> | 
|  | // update the value of one of the array value's elements | 
|  | // %r_{ij} = %f  if (i,j) = (%i,%j),   %v_{ij} otherwise | 
|  | %r = fir.array_update %v, %f, %i, %j : (!fir.array<?x?xf32>, f32, index, index) -> !fir.array<?x?xf32> | 
|  | fir.array_merge_store %v, %r to %a : !fir.ref<!fir.array<?x?xf32>> | 
|  | ``` | 
|  |  | 
|  | An array value update behaves as if a mapping function from the indices | 
|  | to the new value has been added, replacing the previous mapping. These | 
|  | mappings can be added to the ssa-value, but will not be materialized in | 
|  | memory until the `fir.array_merge_store` is performed. | 
|  | `fir.array_update` can be seen as an array access with a notion that the array | 
|  | will be changed at the accessed position when `fir.array_merge_store` is | 
|  | performed. | 
|  |  | 
|  | ## array_access | 
|  |  | 
|  | The `array_access` provides a reference to a single element from an array value. | 
|  | This is *not* a view in the immutable array, otherwise it couldn't be stored to. | 
|  | It can be see as a logical copy of the element and its position in the array. | 
|  | Tis reference can be written to and modified withoiut changing the original | 
|  | array. | 
|  |  | 
|  | The `array_access` operation is used to fetch the memory reference of an element | 
|  | in an array value. | 
|  |  | 
|  | ```fortran | 
|  | real :: a(n,m) | 
|  | ... | 
|  | ... a ... | 
|  | ... a(r,s+1) ... | 
|  | ``` | 
|  |  | 
|  | One can use `fir.array_access` to recover the implied memory reference to | 
|  | the element `a(i,j)` in an array expression `a` as shown above. It can also | 
|  | be used to recover the reference element `a(r,s+1)` in the second | 
|  | expression. | 
|  |  | 
|  | ```mlir | 
|  | %s = fir.shape %n, %m : (index, index) -> !fir.shape<2> | 
|  | // load the entire array 'a' | 
|  | %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32> | 
|  | // fetch the value of one of the array value's elements | 
|  | %1 = fir.array_access %v, %i, %j : (!fir.array<?x?xf32>, index, index) -> !fir.ref<f32> | 
|  | ``` | 
|  |  | 
|  | It is only possible to use `array_access` on an `array_load` result value or a | 
|  | value that can be trace back transitively to an `array_load` as the dominating | 
|  | source. Other array operation such as `array_amend` can be in between. | 
|  |  | 
|  | `array_access` if mainly used with `character`'s arrays and arrays of derived | 
|  | types where because they might have a non-compile time sizes that would be | 
|  | useless too load entirely or too big to load. | 
|  |  | 
|  | Here is a simple example with a `character` array assignment. | 
|  |  | 
|  | Fortran | 
|  | ``` | 
|  | subroutine foo(c1, c2, n) | 
|  | integer(8) :: n | 
|  | character(n) :: c1(:), c2(:) | 
|  | c1 = c2 | 
|  | end subroutine | 
|  | ``` | 
|  |  | 
|  | It results in this cleaned-up FIR: | 
|  | ``` | 
|  | func @_QPfoo(%arg0: !fir.box<!fir.array<?x!fir.char<1,?>>>, %arg1: !fir.box<!fir.array<?x!fir.char<1,?>>>, %arg2: !fir.ref<i64>) { | 
|  | %0 = fir.load %arg2 : !fir.ref<i64> | 
|  | %c0 = arith.constant 0 : index | 
|  | %1:3 = fir.box_dims %arg0, %c0 : (!fir.box<!fir.array<?x!fir.char<1,?>>>, index) -> (index, index, index) | 
|  | %2 = fir.array_load %arg0 : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> !fir.array<?x!fir.char<1,?>> | 
|  | %3 = fir.array_load %arg1 : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> !fir.array<?x!fir.char<1,?>> | 
|  | %c1 = arith.constant 1 : index | 
|  | %4 = arith.subi %1#1, %c1 : index | 
|  | %5 = fir.do_loop %arg3 = %c0 to %4 step %c1 unordered iter_args(%arg4 = %2) -> (!fir.array<?x!fir.char<1,?>>) { | 
|  | %6 = fir.array_access %3, %arg3 : (!fir.array<?x!fir.char<1,?>>, index) -> !fir.ref<!fir.char<1,?>> | 
|  | %7 = fir.array_access %arg4, %arg3 : (!fir.array<?x!fir.char<1,?>>, index) -> !fir.ref<!fir.char<1,?>> | 
|  | %false = arith.constant false | 
|  | %8 = fir.convert %7 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8> | 
|  | %9 = fir.convert %6 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8> | 
|  | fir.call @llvm.memmove.p0i8.p0i8.i64(%8, %9, %0, %false) : (!fir.ref<i8>, !fir.ref<i8>, i64, i1) -> () | 
|  | %10 = fir.array_amend %arg4, %7 : (!fir.array<?x!fir.char<1,?>>, !fir.ref<!fir.char<1,?>>) -> !fir.array<?x!fir.char<1,?>> | 
|  | fir.result %10 : !fir.array<?x!fir.char<1,?>> | 
|  | } | 
|  | fir.array_merge_store %2, %5 to %arg0 : !fir.array<?x!fir.char<1,?>>, !fir.array<?x!fir.char<1,?>>, !fir.box<!fir.array<?x!fir.char<1,?>>> | 
|  | return | 
|  | } | 
|  | func private @llvm.memmove.p0i8.p0i8.i64(!fir.ref<i8>, !fir.ref<i8>, i64, i1) | 
|  | } | 
|  | ``` | 
|  |  | 
|  | `fir.array_access` and `fir.array_amend` split the two purposes of | 
|  | `fir.array_update` into two distinct operations to work on type/kind that must | 
|  | reside in the memory reference domain. `fir.array_access` captures the array | 
|  | access semantics and `fir.array_amend` denotes which `fir.array_access` is the | 
|  | lhs. | 
|  |  | 
|  | We do not want to start loading the entire `!fir.ref<!fir.char<1,?>>` here since | 
|  | it has dynamic length, and even if constant, could be too long to do so. | 
|  |  | 
|  | ## array_amend | 
|  |  | 
|  | The `array_amend` operation marks an array value as having been changed via a | 
|  | reference obtain by an `array_access`. It acts as a logical transaction log | 
|  | that is used to merge the final result back with an `array_merge_store` | 
|  | operation. | 
|  |  | 
|  | ```mlir | 
|  | // fetch the value of one of the array value's elements | 
|  | %1 = fir.array_access %v, %i, %j : (!fir.array<?x?xT>, index, index) -> !fir.ref<T> | 
|  | // modify the element by storing data using %1 as a reference | 
|  | %2 = ... %1 ... | 
|  | // mark the array value | 
|  | %new_v = fir.array_amend %v, %2 : (!fir.array<?x?xT>, !fir.ref<T>) -> !fir.array<?x?xT> | 
|  | ``` | 
|  |  | 
|  | ## Example | 
|  |  | 
|  | Here is an example of a FIR code using several array operations together. The | 
|  | example below is a simplified version of the FIR code comiing from the | 
|  | following Fortran code snippet. | 
|  |  | 
|  | ```fortran | 
|  | subroutine s(a,l,u) | 
|  | type t | 
|  | integer m | 
|  | end type t | 
|  | type(t) :: a(:) | 
|  | integer :: l, u | 
|  | forall (i=l:u) | 
|  | a(i) = a(u-i+1) | 
|  | end forall | 
|  | end | 
|  | ``` | 
|  |  | 
|  | ``` | 
|  | func @_QPs(%arg0: !fir.box<!fir.array<?x!fir.type<_QFsTt{m:i32}>>>, %arg1: !fir.ref<i32>, %arg2: !fir.ref<i32>) { | 
|  | %l = fir.load %arg1 : !fir.ref<i32> | 
|  | %l_index = fir.convert %l : (i32) -> index | 
|  | %u = fir.load %arg2 : !fir.ref<i32> | 
|  | %u_index = fir.convert %u : (i32) -> index | 
|  | %c1 = arith.constant 1 : index | 
|  | // This is the "copy-in" array used on the RHS of the expression. It will be indexed into and loaded at each iteration. | 
|  | %array_a_src = fir.array_load %arg0 : (!fir.box<!fir.array<?x!fir.type<_QFsTt{m:i32}>>>) -> !fir.array<?x!fir.type<_QFsTt{m:i32}>> | 
|  |  | 
|  | // This is the "seed" for the "copy-out" array on the LHS. It'll flow from iteration to iteration and gets | 
|  | // updated at each iteration. | 
|  | %array_a_dest_init = fir.array_load %arg0 : (!fir.box<!fir.array<?x!fir.type<_QFsTt{m:i32}>>>) -> !fir.array<?x!fir.type<_QFsTt{m:i32}>> | 
|  |  | 
|  | %array_a_final = fir.do_loop %i = %l_index to %u_index step %c1 unordered iter_args(%array_a_dest = %array_a_dest_init) -> (!fir.array<?x!fir.type<_QFsTt{m:i32}>>) { | 
|  | // Compute indexing for the RHS and array the element. | 
|  | %u_minus_i = arith.subi %u_index, %i : index // u-i | 
|  | %u_minus_i_plus_one = arith.addi %u_minus_i, %c1: index // u-i+1 | 
|  | %a_src_ref = fir.array_access %array_a_src, %u_minus_i_plus_one {Fortran.offsets} : (!fir.array<?x!fir.type<_QFsTt{m:i32}>>, index) -> !fir.ref<!fir.type<_QFsTt{m:i32}>> | 
|  | %a_src_elt = fir.load %a_src_ref : !fir.ref<!fir.type<_QFsTt{m:i32}>> | 
|  |  | 
|  | // Get the reference to the element in the array on the LHS | 
|  | %a_dst_ref = fir.array_access %array_a_dest, %i {Fortran.offsets} : (!fir.array<?x!fir.type<_QFsTt{m:i32}>>, index) -> !fir.ref<!fir.type<_QFsTt{m:i32}>> | 
|  |  | 
|  | // Store the value, and update the array | 
|  | fir.store %a_src_elt to %a_dst_ref : !fir.ref<!fir.type<_QFsTt{m:i32}>> | 
|  | %updated_array_a = fir.array_amend %array_a_dest, %a_dst_ref : (!fir.array<?x!fir.type<_QFsTt{m:i32}>>, !fir.ref<!fir.type<_QFsTt{m:i32}>>) -> !fir.array<?x!fir.type<_QFsTt{m:i32}>> | 
|  |  | 
|  | // Forward the current updated array to the next iteration. | 
|  | fir.result %updated_array_a : !fir.array<?x!fir.type<_QFsTt{m:i32}>> | 
|  | } | 
|  | // Store back the result by merging the initial value loaded before the loop | 
|  | // with the final one produced by the loop. | 
|  | fir.array_merge_store %array_a_dest_init, %array_a_final to %arg0 : !fir.array<?x!fir.type<_QFsTt{m:i32}>>, !fir.array<?x!fir.type<_QFsTt{m:i32}>>, !fir.box<!fir.array<?x!fir.type<_QFsTt{m:i32}>>> | 
|  | return | 
|  | } | 
|  | ``` |