1<!--===- docs/FIRArrayOperations.md
2
3   Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4   See https://llvm.org/LICENSE.txt for license information.
5   SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
7-->
8
9# Design: FIR Array operations
10
11```eval_rst
12.. contents::
13   :local:
14```
15
16## General
17
18The array operations in FIR model the copy-in/copy-out semantics over Fortran
19statements.
20
21Fortran language semantics sometimes require the compiler to make a temporary
22copy of an array or array slice. Situations where this can occur include:
23
24* Passing a non-contiguous array to a procedure that does not declare it as
25  assumed-shape.
26* Array expressions, especially those involving `RESHAPE`, `PACK`, and `MERGE`.
27* Assignments of arrays where the array appears on both the left and right-hand
28  sides of the assignment.
29* Assignments of `POINTER` arrays.
30
31There are currently the following operations:
32- `fir.array_load`
33- `fir.array_merge_store`
34- `fir.array_fetch`
35- `fir.array_update`
36- `fir.array_access`
37- `fir.array_amend`
38
39`array_load`(s) and `array_merge_store` are a pairing that brackets the lifetime
40of the array copies.
41
42`array_fetch` and `array_update` are defined to work as getter/setter pairs on
43values of elements from loaded array copies. These have "GEP-like" syntax and
44semantics.
45
46Fortran arrays are implicitly memory bound as are some other Fortran type/kind
47entities. For entities that can be atomically promoted to the value domain,
48we use `array_fetch` and `array_update`.
49
50`array_access` and `array_amend` are defined to work as getter/setter pairs on
51references to elements in loaded array copies. `array_access` has "GEP-like"
52syntax. `array_amend` annotates which loaded array copy is being written to.
53It is invalid to update an array copy without `array_amend`; doing so will
54result in undefined behavior.
55For those type/kinds that cannot be promoted to values, we must leave them in a
56memory reference domain, and we use `array_access` and `array_amend`.
57
58## array_load
59
60This operation taken with `array_merge_store` captures Fortran's
61copy-in/copy-out semantics. One way to think of this is that array_load
62creates a snapshot copy of the entire array. This copy can then be used
63as the "original value" of the array while the array's new value is
64computed. The `array_merge_store` operation is the copy-out semantics, which
65merge the updates with the original array value to produce the final array
66result. This abstracts the copy operations as opposed to always creating
67copies or requiring dependence analysis be performed on the syntax trees
68and before lowering to the IR.
69
70Load an entire array as a single SSA value.
71
72```fortran
73  real :: a(o:n,p:m)
74  ...
75  ... = ... a ...
76```
77
78One can use `fir.array_load` to produce an ssa-value that captures an
79immutable value of the entire array `a`, as in the Fortran array expression
80shown above. Subsequent changes to the memory containing the array do not
81alter its composite value. This operation lets one load an array as a
82value while applying a runtime shape, shift, or slice to the memory
83reference, and its semantics guarantee immutability.
84
85```mlir
86%s = fir.shape_shift %lb1, %ex1, %lb2, %ex2 : (index, index, index, index) -> !fir.shape<2>
87// load the entire array 'a'
88%v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32>
89// a fir.store here into array %a does not change %v
90```
91
92# array_merge_store
93
94The `array_merge_store` operation stores a merged array value to memory.
95
96
97```fortran
98  real :: a(n,m)
99  ...
100  a = ...
101```
102
103One can use `fir.array_merge_store` to merge/copy the value of `a` in an
104array expression as shown above.
105
106```mlir
107  %v = fir.array_load %a(%shape) : ...
108  %r = fir.array_update %v, %f, %i, %j : (!fir.array<?x?xf32>, f32, index, index) -> !fir.array<?x?xf32>
109  fir.array_merge_store %v, %r to %a : !fir.ref<!fir.array<?x?xf32>>
110```
111
112This operation merges the original loaded array value, `%v`, with the
113chained updates, `%r`, and stores the result to the array at address, `%a`.
114
115This operation taken with `array_load`'s captures Fortran's
116copy-in/copy-out semantics. The first operands of `array_merge_store` is the
117result of the initial `array_load` operation. While this value could be
118retrieved by reference chasiing through the different array operations it is
119useful to have it on hand directly for analysis passes since this directly
120defines the "bounds" of the Fortran statement represented by these operations.
121The intention is to allow copy-in/copy-out regions to be easily delineated,
122analyzed, and optimized.
123
124## array_fetch
125
126The `array_fetch` operation fetches the value of an element in an array value.
127
128```fortran
129  real :: a(n,m)
130  ...
131  ... a ...
132  ... a(r,s+1) ...
133```
134
135One can use `fir.array_fetch` to fetch the (implied) value of `a(i,j)` in
136an array expression as shown above. It can also be used to extract the
137element `a(r,s+1)` in the second expression.
138
139```mlir
140  %s = fir.shape %n, %m : (index, index) -> !fir.shape<2>
141  // load the entire array 'a'
142  %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32>
143  // fetch the value of one of the array value's elements
144  %1 = fir.array_fetch %v, %i, %j : (!fir.array<?x?xf32>, index, index) -> f32
145```
146
147It is only possible to use `array_fetch` on an `array_load` result value or a
148value that can be trace back transitively to an `array_load` as the dominating
149source. Other array operation such as `array_update` can be in between.
150
151## array_update
152
153The `array_update` operation is used to update the value of an element in an
154array value. A new array value is returned where all element values of the input
155array are identical except for the selected element which is the value passed in
156the update.
157
158```fortran
159  real :: a(n,m)
160  ...
161  a = ...
162```
163
164One can use `fir.array_update` to update the (implied) value of `a(i,j)`
165in an array expression as shown above.
166
167```mlir
168  %s = fir.shape %n, %m : (index, index) -> !fir.shape<2>
169  // load the entire array 'a'
170  %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32>
171  // update the value of one of the array value's elements
172  // %r_{ij} = %f  if (i,j) = (%i,%j),   %v_{ij} otherwise
173  %r = fir.array_update %v, %f, %i, %j : (!fir.array<?x?xf32>, f32, index, index) -> !fir.array<?x?xf32>
174  fir.array_merge_store %v, %r to %a : !fir.ref<!fir.array<?x?xf32>>
175```
176
177An array value update behaves as if a mapping function from the indices
178to the new value has been added, replacing the previous mapping. These
179mappings can be added to the ssa-value, but will not be materialized in
180memory until the `fir.array_merge_store` is performed.
181`fir.array_update` can be seen as an array access with a notion that the array
182will be changed at the accessed position when `fir.array_merge_store` is
183performed.
184
185## array_access
186
187The `array_access` provides a reference to a single element from an array value.
188This is *not* a view in the immutable array, otherwise it couldn't be stored to.
189It can be see as a logical copy of the element and its position in the array.
190Tis reference can be written to and modified withoiut changing the original
191array.
192
193The `array_access` operation is used to fetch the memory reference of an element
194in an array value.
195
196```fortran
197  real :: a(n,m)
198  ...
199  ... a ...
200  ... a(r,s+1) ...
201```
202
203One can use `fir.array_access` to recover the implied memory reference to
204the element `a(i,j)` in an array expression `a` as shown above. It can also
205be used to recover the reference element `a(r,s+1)` in the second
206expression.
207
208```mlir
209  %s = fir.shape %n, %m : (index, index) -> !fir.shape<2>
210  // load the entire array 'a'
211  %v = fir.array_load %a(%s) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>) -> !fir.array<?x?xf32>
212  // fetch the value of one of the array value's elements
213  %1 = fir.array_access %v, %i, %j : (!fir.array<?x?xf32>, index, index) -> !fir.ref<f32>
214```
215
216It is only possible to use `array_access` on an `array_load` result value or a
217value that can be trace back transitively to an `array_load` as the dominating
218source. Other array operation such as `array_amend` can be in between.
219
220`array_access` if mainly used with `character`'s arrays and arrays of derived
221types where because they might have a non-compile time sizes that would be
222useless too load entirely or too big to load.
223
224Here is a simple example with a `character` array assignment.
225
226Fortran
227```
228subroutine foo(c1, c2, n)
229  integer(8) :: n
230  character(n) :: c1(:), c2(:)
231  c1 = c2
232end subroutine
233```
234
235It results in this cleaned-up FIR:
236```
237func @_QPfoo(%arg0: !fir.box<!fir.array<?x!fir.char<1,?>>>, %arg1: !fir.box<!fir.array<?x!fir.char<1,?>>>, %arg2: !fir.ref<i64>) {
238    %0 = fir.load %arg2 : !fir.ref<i64>
239    %c0 = arith.constant 0 : index
240    %1:3 = fir.box_dims %arg0, %c0 : (!fir.box<!fir.array<?x!fir.char<1,?>>>, index) -> (index, index, index)
241    %2 = fir.array_load %arg0 : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> !fir.array<?x!fir.char<1,?>>
242    %3 = fir.array_load %arg1 : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> !fir.array<?x!fir.char<1,?>>
243    %c1 = arith.constant 1 : index
244    %4 = arith.subi %1#1, %c1 : index
245    %5 = fir.do_loop %arg3 = %c0 to %4 step %c1 unordered iter_args(%arg4 = %2) -> (!fir.array<?x!fir.char<1,?>>) {
246      %6 = fir.array_access %3, %arg3 : (!fir.array<?x!fir.char<1,?>>, index) -> !fir.ref<!fir.char<1,?>>
247      %7 = fir.array_access %arg4, %arg3 : (!fir.array<?x!fir.char<1,?>>, index) -> !fir.ref<!fir.char<1,?>>
248      %false = arith.constant false
249      %8 = fir.convert %7 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
250      %9 = fir.convert %6 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
251      fir.call @llvm.memmove.p0i8.p0i8.i64(%8, %9, %0, %false) : (!fir.ref<i8>, !fir.ref<i8>, i64, i1) -> ()
252      %10 = fir.array_amend %arg4, %7 : (!fir.array<?x!fir.char<1,?>>, !fir.ref<!fir.char<1,?>>) -> !fir.array<?x!fir.char<1,?>>
253      fir.result %10 : !fir.array<?x!fir.char<1,?>>
254    }
255    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,?>>>
256    return
257  }
258  func private @llvm.memmove.p0i8.p0i8.i64(!fir.ref<i8>, !fir.ref<i8>, i64, i1)
259}
260```
261
262`fir.array_access` and `fir.array_amend` split the two purposes of
263`fir.array_update` into two distinct operations to work on type/kind that must
264reside in the memory reference domain. `fir.array_access` captures the array
265access semantics and `fir.array_amend` denotes which `fir.array_access` is the
266lhs.
267
268We do not want to start loading the entire `!fir.ref<!fir.char<1,?>>` here since
269it has dynamic length, and even if constant, could be too long to do so.
270
271## array_amend
272
273The `array_amend` operation marks an array value as having been changed via a
274reference obtain by an `array_access`. It acts as a logical transaction log
275that is used to merge the final result back with an `array_merge_store`
276operation.
277
278```mlir
279  // fetch the value of one of the array value's elements
280  %1 = fir.array_access %v, %i, %j : (!fir.array<?x?xT>, index, index) -> !fir.ref<T>
281  // modify the element by storing data using %1 as a reference
282  %2 = ... %1 ...
283  // mark the array value
284  %new_v = fir.array_amend %v, %2 : (!fir.array<?x?xT>, !fir.ref<T>) -> !fir.array<?x?xT>
285```
286
287## Example
288
289Here is an example of a FIR code using several array operations together. The
290example below is a simplified version of the FIR code comiing from the
291following Fortran code snippet.
292
293```fortran
294subroutine s(a,l,u)
295  type t
296    integer m
297  end type t
298  type(t) :: a(:)
299  integer :: l, u
300  forall (i=l:u)
301    a(i) = a(u-i+1)
302  end forall
303end
304```
305
306```
307func @_QPs(%arg0: !fir.box<!fir.array<?x!fir.type<_QFsTt{m:i32}>>>, %arg1: !fir.ref<i32>, %arg2: !fir.ref<i32>) {
308  %l = fir.load %arg1 : !fir.ref<i32>
309  %l_index = fir.convert %l : (i32) -> index
310  %u = fir.load %arg2 : !fir.ref<i32>
311  %u_index = fir.convert %u : (i32) -> index
312  %c1 = arith.constant 1 : index
313  // This is the "copy-in" array used on the RHS of the expression. It will be indexed into and loaded at each iteration.
314  %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}>>
315
316  // This is the "seed" for the "copy-out" array on the LHS. It'll flow from iteration to iteration and gets
317  // updated at each iteration.
318  %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}>>
319
320  %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}>>) {
321    // Compute indexing for the RHS and array the element.
322    %u_minus_i = arith.subi %u_index, %i : index // u-i
323    %u_minus_i_plus_one = arith.addi %u_minus_i, %c1: index // u-i+1
324    %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}>>
325    %a_src_elt = fir.load %a_src_ref : !fir.ref<!fir.type<_QFsTt{m:i32}>>
326
327    // Get the reference to the element in the array on the LHS
328    %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}>>
329
330    // Store the value, and update the array
331    fir.store %a_src_elt to %a_dst_ref : !fir.ref<!fir.type<_QFsTt{m:i32}>>
332    %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}>>
333
334    // Forward the current updated array to the next iteration.
335    fir.result %updated_array_a : !fir.array<?x!fir.type<_QFsTt{m:i32}>>
336  }
337  // Store back the result by merging the initial value loaded before the loop
338  // with the final one produced by the loop.
339  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}>>>
340  return
341}
342```
343