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