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