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