1 <!--===- docs/FIRArrayOperations.md
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
9 # Design: FIR Array operations
19 The array operations in FIR model the copy-in/copy-out semantics over Fortran
22 Fortran language semantics sometimes require the compiler to make a temporary
23 copy of an array or array slice. Situations where this can occur include:
25 * Passing a non-contiguous array to a procedure that does not declare it as
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.
32 There are currently the following operations:
34 - `fir.array_merge_store`
40 `array_load`(s) and `array_merge_store` are a pairing that brackets the lifetime
43 `array_fetch` and `array_update` are defined to work as getter/setter pairs on
44 values of elements from loaded array copies. These have "GEP-like" syntax and
47 Fortran arrays are implicitly memory bound as are some other Fortran type/kind
48 entities. For entities that can be atomically promoted to the value domain,
49 we use `array_fetch` and `array_update`.
51 `array_access` and `array_amend` are defined to work as getter/setter pairs on
52 references to elements in loaded array copies. `array_access` has "GEP-like"
53 syntax. `array_amend` annotates which loaded array copy is being written to.
54 It is invalid to update an array copy without `array_amend`; doing so will
55 result in undefined behavior.
56 For those type/kinds that cannot be promoted to values, we must leave them in a
57 memory reference domain, and we use `array_access` and `array_amend`.
61 This operation taken with `array_merge_store` captures Fortran's
62 copy-in/copy-out semantics. One way to think of this is that array_load
63 creates a snapshot copy of the entire array. This copy can then be used
64 as the "original value" of the array while the array's new value is
65 computed. The `array_merge_store` operation is the copy-out semantics, which
66 merge the updates with the original array value to produce the final array
67 result. This abstracts the copy operations as opposed to always creating
68 copies or requiring dependence analysis be performed on the syntax trees
69 and before lowering to the IR.
71 Load an entire array as a single SSA value.
79 One can use `fir.array_load` to produce an ssa-value that captures an
80 immutable value of the entire array `a`, as in the Fortran array expression
81 shown above. Subsequent changes to the memory containing the array do not
82 alter its composite value. This operation lets one load an array as a
83 value while applying a runtime shape, shift, or slice to the memory
84 reference, and its semantics guarantee immutability.
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
95 The `array_merge_store` operation stores a merged array value to memory.
104 One can use `fir.array_merge_store` to merge/copy the value of `a` in an
105 array expression as shown above.
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>>
113 This operation merges the original loaded array value, `%v`, with the
114 chained updates, `%r`, and stores the result to the array at address, `%a`.
116 This operation taken with `array_load`'s captures Fortran's
117 copy-in/copy-out semantics. The first operands of `array_merge_store` is the
118 result of the initial `array_load` operation. While this value could be
119 retrieved by reference chasing through the different array operations it is
120 useful to have it on hand directly for analysis passes since this directly
121 defines the "bounds" of the Fortran statement represented by these operations.
122 The intention is to allow copy-in/copy-out regions to be easily delineated,
123 analyzed, and optimized.
127 The `array_fetch` operation fetches the value of an element in an array value.
136 One can use `fir.array_fetch` to fetch the (implied) value of `a(i,j)` in
137 an array expression as shown above. It can also be used to extract the
138 element `a(r,s+1)` in the second expression.
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
148 It is only possible to use `array_fetch` on an `array_load` result value or a
149 value that can be trace back transitively to an `array_load` as the dominating
150 source. Other array operation such as `array_update` can be in between.
154 The `array_update` operation is used to update the value of an element in an
155 array value. A new array value is returned where all element values of the input
156 array are identical except for the selected element which is the value passed in
165 One can use `fir.array_update` to update the (implied) value of `a(i,j)`
166 in an array expression as shown above.
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>>
178 An array value update behaves as if a mapping function from the indices
179 to the new value has been added, replacing the previous mapping. These
180 mappings can be added to the ssa-value, but will not be materialized in
181 memory 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
183 will be changed at the accessed position when `fir.array_merge_store` is
188 The `array_access` provides a reference to a single element from an array value.
189 This is *not* a view in the immutable array, otherwise it couldn't be stored to.
190 It can be see as a logical copy of the element and its position in the array.
191 Tis reference can be written to and modified withoiut changing the original
194 The `array_access` operation is used to fetch the memory reference of an element
204 One can use `fir.array_access` to recover the implied memory reference to
205 the element `a(i,j)` in an array expression `a` as shown above. It can also
206 be used to recover the reference element `a(r,s+1)` in the second
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>
217 It is only possible to use `array_access` on an `array_load` result value or a
218 value that can be trace back transitively to an `array_load` as the dominating
219 source. Other array operation such as `array_amend` can be in between.
221 `array_access` if mainly used with `character`'s arrays and arrays of derived
222 types where because they might have a non-compile time sizes that would be
223 useless too load entirely or too big to load.
225 Here is a simple example with a `character` array assignment.
229 subroutine foo(c1, c2, n)
231 character(n) :: c1(:), c2(:)
236 It results in this cleaned-up FIR:
238 func @_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,?>>
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,?>>>
259 func private @llvm.memmove.p0i8.p0i8.i64(!fir.ref<i8>, !fir.ref<i8>, i64, i1)
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
265 reside in the memory reference domain. `fir.array_access` captures the array
266 access semantics and `fir.array_amend` denotes which `fir.array_access` is the
269 We do not want to start loading the entire `!fir.ref<!fir.char<1,?>>` here since
270 it has dynamic length, and even if constant, could be too long to do so.
274 The `array_amend` operation marks an array value as having been changed via a
275 reference obtain by an `array_access`. It acts as a logical transaction log
276 that is used to merge the final result back with an `array_merge_store`
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
284 // mark the array value
285 %new_v = fir.array_amend %v, %2 : (!fir.array<?x?xT>, !fir.ref<T>) -> !fir.array<?x?xT>
290 Here is an example of a FIR code using several array operations together. The
291 example below is a simplified version of the FIR code comiing from the
292 following Fortran code snippet.
308 func @_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}>>
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}>>
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}>>
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}>>
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}>>
335 // Forward the current updated array to the next iteration.
336 fir.result %updated_array_a : !fir.array<?x!fir.type<_QFsTt{m:i32}>>
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}>>>