annotate mlir/docs/ConversionToLLVMDialect.md @ 220:42394fc6a535

Added tag llvm12 for changeset 0572611fdcc8
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Tue, 15 Jun 2021 19:13:43 +0900
parents 0572611fdcc8
children 2e18cbf3894f
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 # Conversion to the LLVM Dialect
anatofuz
parents:
diff changeset
2
anatofuz
parents:
diff changeset
3 Conversion from the Standard to the [LLVM Dialect](Dialects/LLVM.md) can be
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
4 performed by the specialized dialect conversion pass by running:
150
anatofuz
parents:
diff changeset
5
anatofuz
parents:
diff changeset
6 ```shell
anatofuz
parents:
diff changeset
7 mlir-opt -convert-std-to-llvm <filename.mlir>
anatofuz
parents:
diff changeset
8 ```
anatofuz
parents:
diff changeset
9
anatofuz
parents:
diff changeset
10 It performs type and operation conversions for a subset of operations from
anatofuz
parents:
diff changeset
11 standard dialect (operations on scalars and vectors, control flow operations) as
anatofuz
parents:
diff changeset
12 described in this document. We use the terminology defined by the
anatofuz
parents:
diff changeset
13 [LLVM IR Dialect description](Dialects/LLVM.md) throughout this document.
anatofuz
parents:
diff changeset
14
anatofuz
parents:
diff changeset
15 [TOC]
anatofuz
parents:
diff changeset
16
anatofuz
parents:
diff changeset
17 ## Type Conversion
anatofuz
parents:
diff changeset
18
anatofuz
parents:
diff changeset
19 ### Scalar Types
anatofuz
parents:
diff changeset
20
anatofuz
parents:
diff changeset
21 Scalar types are converted to their LLVM counterparts if they exist. The
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
22 following conversions are currently implemented:
150
anatofuz
parents:
diff changeset
23
anatofuz
parents:
diff changeset
24 - `i*` converts to `!llvm.i*`
anatofuz
parents:
diff changeset
25 - `f16` converts to `!llvm.half`
anatofuz
parents:
diff changeset
26 - `f32` converts to `!llvm.float`
anatofuz
parents:
diff changeset
27 - `f64` converts to `!llvm.double`
anatofuz
parents:
diff changeset
28
anatofuz
parents:
diff changeset
29 Note: `bf16` type is not supported by LLVM IR and cannot be converted.
anatofuz
parents:
diff changeset
30
anatofuz
parents:
diff changeset
31 ### Index Type
anatofuz
parents:
diff changeset
32
anatofuz
parents:
diff changeset
33 Index type is converted to a wrapped LLVM IR integer with bitwidth equal to the
anatofuz
parents:
diff changeset
34 bitwidth of the pointer size as specified by the
anatofuz
parents:
diff changeset
35 [data layout](https://llvm.org/docs/LangRef.html#data-layout) of the LLVM module
anatofuz
parents:
diff changeset
36 [contained](Dialects/LLVM.md#context-and-module-association) in the LLVM Dialect
anatofuz
parents:
diff changeset
37 object. For example, on x86-64 CPUs it converts to `!llvm.i64`.
anatofuz
parents:
diff changeset
38
anatofuz
parents:
diff changeset
39 ### Vector Types
anatofuz
parents:
diff changeset
40
anatofuz
parents:
diff changeset
41 LLVM IR only supports *one-dimensional* vectors, unlike MLIR where vectors can
anatofuz
parents:
diff changeset
42 be multi-dimensional. Vector types cannot be nested in either IR. In the
anatofuz
parents:
diff changeset
43 one-dimensional case, MLIR vectors are converted to LLVM IR vectors of the same
anatofuz
parents:
diff changeset
44 size with element type converted using these conversion rules. In the
anatofuz
parents:
diff changeset
45 n-dimensional case, MLIR vectors are converted to (n-1)-dimensional array types
anatofuz
parents:
diff changeset
46 of one-dimensional vectors.
anatofuz
parents:
diff changeset
47
anatofuz
parents:
diff changeset
48 For example, `vector<4 x f32>` converts to `!llvm<"<4 x float>">` and `vector<4
anatofuz
parents:
diff changeset
49 x 8 x 16 x f32>` converts to `!llvm<"[4 x [8 x <16 x float>]]">`.
anatofuz
parents:
diff changeset
50
anatofuz
parents:
diff changeset
51 ### Memref Types
anatofuz
parents:
diff changeset
52
anatofuz
parents:
diff changeset
53 Memref types in MLIR have both static and dynamic information associated with
anatofuz
parents:
diff changeset
54 them. The dynamic information comprises the buffer pointer as well as sizes and
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
55 strides of any dynamically-sized dimensions. Memref types are normalized and
150
anatofuz
parents:
diff changeset
56 converted to a descriptor that is only dependent on the rank of the memref. The
anatofuz
parents:
diff changeset
57 descriptor contains:
anatofuz
parents:
diff changeset
58
anatofuz
parents:
diff changeset
59 1. the pointer to the data buffer, followed by
anatofuz
parents:
diff changeset
60 2. the pointer to properly aligned data payload that the memref indexes,
anatofuz
parents:
diff changeset
61 followed by
anatofuz
parents:
diff changeset
62 3. a lowered `index`-type integer containing the distance between the beginning
anatofuz
parents:
diff changeset
63 of the buffer and the first element to be accessed through the memref,
anatofuz
parents:
diff changeset
64 followed by
anatofuz
parents:
diff changeset
65 4. an array containing as many `index`-type integers as the rank of the memref:
anatofuz
parents:
diff changeset
66 the array represents the size, in number of elements, of the memref along
anatofuz
parents:
diff changeset
67 the given dimension. For constant MemRef dimensions, the corresponding size
anatofuz
parents:
diff changeset
68 entry is a constant whose runtime value must match the static value,
anatofuz
parents:
diff changeset
69 followed by
anatofuz
parents:
diff changeset
70 5. a second array containing as many 64-bit integers as the rank of the MemRef:
anatofuz
parents:
diff changeset
71 the second array represents the "stride" (in tensor abstraction sense), i.e.
anatofuz
parents:
diff changeset
72 the number of consecutive elements of the underlying buffer.
anatofuz
parents:
diff changeset
73
anatofuz
parents:
diff changeset
74 For constant memref dimensions, the corresponding size entry is a constant whose
anatofuz
parents:
diff changeset
75 runtime value matches the static value. This normalization serves as an ABI for
anatofuz
parents:
diff changeset
76 the memref type to interoperate with externally linked functions. In the
anatofuz
parents:
diff changeset
77 particular case of rank `0` memrefs, the size and stride arrays are omitted,
anatofuz
parents:
diff changeset
78 resulting in a struct containing two pointers + offset.
anatofuz
parents:
diff changeset
79
anatofuz
parents:
diff changeset
80 Examples:
anatofuz
parents:
diff changeset
81
anatofuz
parents:
diff changeset
82 ```mlir
anatofuz
parents:
diff changeset
83 memref<f32> -> !llvm<"{ float*, float*, i64 }">
anatofuz
parents:
diff changeset
84 memref<1 x f32> -> !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
85 memref<? x f32> -> !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
86 memref<10x42x42x43x123 x f32> -> !llvm<"{ float*, float*, i64, [5 x i64], [5 x i64] }">
anatofuz
parents:
diff changeset
87 memref<10x?x42x?x123 x f32> -> !llvm<"{ float*, float*, i64, [5 x i64], [5 x i64] }">
anatofuz
parents:
diff changeset
88
anatofuz
parents:
diff changeset
89 // Memref types can have vectors as element types
anatofuz
parents:
diff changeset
90 memref<1x? x vector<4xf32>> -> !llvm<"{ <4 x float>*, <4 x float>*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
91 ```
anatofuz
parents:
diff changeset
92
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
93 If the rank of the memref is unknown at compile time, the memref is converted to
150
anatofuz
parents:
diff changeset
94 an unranked descriptor that contains:
anatofuz
parents:
diff changeset
95
anatofuz
parents:
diff changeset
96 1. a 64-bit integer representing the dynamic rank of the memref, followed by
anatofuz
parents:
diff changeset
97 2. a pointer to a ranked memref descriptor with the contents listed above.
anatofuz
parents:
diff changeset
98
anatofuz
parents:
diff changeset
99 Dynamic ranked memrefs should be used only to pass arguments to external library
anatofuz
parents:
diff changeset
100 calls that expect a unified memref type. The called functions can parse any
anatofuz
parents:
diff changeset
101 unranked memref descriptor by reading the rank and parsing the enclosed ranked
anatofuz
parents:
diff changeset
102 descriptor pointer.
anatofuz
parents:
diff changeset
103
anatofuz
parents:
diff changeset
104 Examples:
anatofuz
parents:
diff changeset
105
anatofuz
parents:
diff changeset
106 ```mlir
anatofuz
parents:
diff changeset
107 // unranked descriptor
anatofuz
parents:
diff changeset
108 memref<*xf32> -> !llvm<"{i64, i8*}">
anatofuz
parents:
diff changeset
109 ```
anatofuz
parents:
diff changeset
110
anatofuz
parents:
diff changeset
111 **In function signatures,** `memref` is passed as a _pointer_ to the structured
anatofuz
parents:
diff changeset
112 defined above to comply with the calling convention.
anatofuz
parents:
diff changeset
113
anatofuz
parents:
diff changeset
114 Example:
anatofuz
parents:
diff changeset
115
anatofuz
parents:
diff changeset
116 ```mlir
anatofuz
parents:
diff changeset
117 // A function type with memref as argument
anatofuz
parents:
diff changeset
118 (memref<?xf32>) -> ()
anatofuz
parents:
diff changeset
119 // is transformed into the LLVM function with pointer-to-structure argument.
anatofuz
parents:
diff changeset
120 !llvm<"void({ float*, float*, i64, [1 x i64], [1 x i64]}*) ">
anatofuz
parents:
diff changeset
121 ```
anatofuz
parents:
diff changeset
122
anatofuz
parents:
diff changeset
123 ### Function Types
anatofuz
parents:
diff changeset
124
anatofuz
parents:
diff changeset
125 Function types get converted to LLVM function types. The arguments are converted
anatofuz
parents:
diff changeset
126 individually according to these rules. The result types need to accommodate the
anatofuz
parents:
diff changeset
127 fact that LLVM IR functions always have a return type, which may be a Void type.
anatofuz
parents:
diff changeset
128 The converted function always has a single result type. If the original function
anatofuz
parents:
diff changeset
129 type had no results, the converted function will have one result of the wrapped
anatofuz
parents:
diff changeset
130 `void` type. If the original function type had one result, the converted
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
131 function will also have one result converted using these rules. Otherwise, the result
150
anatofuz
parents:
diff changeset
132 type will be a wrapped LLVM IR structure type where each element of the
anatofuz
parents:
diff changeset
133 structure corresponds to one of the results of the original function, converted
anatofuz
parents:
diff changeset
134 using these rules. In high-order functions, function-typed arguments and results
anatofuz
parents:
diff changeset
135 are converted to a wrapped LLVM IR function pointer type (since LLVM IR does not
anatofuz
parents:
diff changeset
136 allow passing functions to functions without indirection) with the pointee type
anatofuz
parents:
diff changeset
137 converted using these rules.
anatofuz
parents:
diff changeset
138
anatofuz
parents:
diff changeset
139 Examples:
anatofuz
parents:
diff changeset
140
anatofuz
parents:
diff changeset
141 ```mlir
anatofuz
parents:
diff changeset
142 // zero-ary function type with no results.
anatofuz
parents:
diff changeset
143 () -> ()
anatofuz
parents:
diff changeset
144 // is converted to a zero-ary function with `void` result
anatofuz
parents:
diff changeset
145 !llvm<"void ()">
anatofuz
parents:
diff changeset
146
anatofuz
parents:
diff changeset
147 // unary function with one result
anatofuz
parents:
diff changeset
148 (i32) -> (i64)
anatofuz
parents:
diff changeset
149 // has its argument and result type converted, before creating the LLVM IR function type
anatofuz
parents:
diff changeset
150 !llvm<"i64 (i32)">
anatofuz
parents:
diff changeset
151
anatofuz
parents:
diff changeset
152 // binary function with one result
anatofuz
parents:
diff changeset
153 (i32, f32) -> (i64)
anatofuz
parents:
diff changeset
154 // has its arguments handled separately
anatofuz
parents:
diff changeset
155 !llvm<"i64 (i32, float)">
anatofuz
parents:
diff changeset
156
anatofuz
parents:
diff changeset
157 // binary function with two results
anatofuz
parents:
diff changeset
158 (i32, f32) -> (i64, f64)
anatofuz
parents:
diff changeset
159 // has its result aggregated into a structure type
anatofuz
parents:
diff changeset
160 !llvm<"{i64, double} (i32, f32)">
anatofuz
parents:
diff changeset
161
anatofuz
parents:
diff changeset
162 // function-typed arguments or results in higher-order functions
anatofuz
parents:
diff changeset
163 (() -> ()) -> (() -> ())
anatofuz
parents:
diff changeset
164 // are converted into pointers to functions
anatofuz
parents:
diff changeset
165 !llvm<"void ()* (void ()*)">
anatofuz
parents:
diff changeset
166 ```
anatofuz
parents:
diff changeset
167
anatofuz
parents:
diff changeset
168 ## Calling Convention
anatofuz
parents:
diff changeset
169
anatofuz
parents:
diff changeset
170 ### Function Signature Conversion
anatofuz
parents:
diff changeset
171
anatofuz
parents:
diff changeset
172 LLVM IR functions are defined by a custom operation. The function itself has a
anatofuz
parents:
diff changeset
173 wrapped LLVM IR function type converted as described above. The function
anatofuz
parents:
diff changeset
174 definition operation uses MLIR syntax.
anatofuz
parents:
diff changeset
175
anatofuz
parents:
diff changeset
176 Examples:
anatofuz
parents:
diff changeset
177
anatofuz
parents:
diff changeset
178 ```mlir
anatofuz
parents:
diff changeset
179 // zero-ary function type with no results.
anatofuz
parents:
diff changeset
180 func @foo() -> ()
anatofuz
parents:
diff changeset
181 // gets LLVM type void().
anatofuz
parents:
diff changeset
182 llvm.func @foo() -> ()
anatofuz
parents:
diff changeset
183
anatofuz
parents:
diff changeset
184 // function with one result
anatofuz
parents:
diff changeset
185 func @bar(i32) -> (i64)
anatofuz
parents:
diff changeset
186 // gets converted to LLVM type i64(i32).
anatofuz
parents:
diff changeset
187 func @bar(!llvm.i32) -> !llvm.i64
anatofuz
parents:
diff changeset
188
anatofuz
parents:
diff changeset
189 // function with two results
anatofuz
parents:
diff changeset
190 func @qux(i32, f32) -> (i64, f64)
anatofuz
parents:
diff changeset
191 // has its result aggregated into a structure type
anatofuz
parents:
diff changeset
192 func @qux(!llvm.i32, !llvm.float) -> !llvm<"{i64, double}">
anatofuz
parents:
diff changeset
193
anatofuz
parents:
diff changeset
194 // function-typed arguments or results in higher-order functions
anatofuz
parents:
diff changeset
195 func @quux(() -> ()) -> (() -> ())
anatofuz
parents:
diff changeset
196 // are converted into pointers to functions
anatofuz
parents:
diff changeset
197 func @quux(!llvm<"void ()*">) -> !llvm<"void ()*">
anatofuz
parents:
diff changeset
198 // the call flow is handled by the LLVM dialect `call` operation supporting both
anatofuz
parents:
diff changeset
199 // direct and indirect calls
anatofuz
parents:
diff changeset
200 ```
anatofuz
parents:
diff changeset
201
anatofuz
parents:
diff changeset
202 ### Result Packing
anatofuz
parents:
diff changeset
203
anatofuz
parents:
diff changeset
204 In case of multi-result functions, the returned values are inserted into a
anatofuz
parents:
diff changeset
205 structure-typed value before being returned and extracted from it at the call
anatofuz
parents:
diff changeset
206 site. This transformation is a part of the conversion and is transparent to the
anatofuz
parents:
diff changeset
207 defines and uses of the values being returned.
anatofuz
parents:
diff changeset
208
anatofuz
parents:
diff changeset
209 Example:
anatofuz
parents:
diff changeset
210
anatofuz
parents:
diff changeset
211 ```mlir
anatofuz
parents:
diff changeset
212 func @foo(%arg0: i32, %arg1: i64) -> (i32, i64) {
anatofuz
parents:
diff changeset
213 return %arg0, %arg1 : i32, i64
anatofuz
parents:
diff changeset
214 }
anatofuz
parents:
diff changeset
215 func @bar() {
anatofuz
parents:
diff changeset
216 %0 = constant 42 : i32
anatofuz
parents:
diff changeset
217 %1 = constant 17 : i64
anatofuz
parents:
diff changeset
218 %2:2 = call @foo(%0, %1) : (i32, i64) -> (i32, i64)
anatofuz
parents:
diff changeset
219 "use_i32"(%2#0) : (i32) -> ()
anatofuz
parents:
diff changeset
220 "use_i64"(%2#1) : (i64) -> ()
anatofuz
parents:
diff changeset
221 }
anatofuz
parents:
diff changeset
222
anatofuz
parents:
diff changeset
223 // is transformed into
anatofuz
parents:
diff changeset
224
anatofuz
parents:
diff changeset
225 func @foo(%arg0: !llvm.i32, %arg1: !llvm.i64) -> !llvm<"{i32, i64}"> {
anatofuz
parents:
diff changeset
226 // insert the vales into a structure
anatofuz
parents:
diff changeset
227 %0 = llvm.mlir.undef : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
228 %1 = llvm.insertvalue %arg0, %0[0] : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
229 %2 = llvm.insertvalue %arg1, %1[1] : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
230
anatofuz
parents:
diff changeset
231 // return the structure value
anatofuz
parents:
diff changeset
232 llvm.return %2 : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
233 }
anatofuz
parents:
diff changeset
234 func @bar() {
anatofuz
parents:
diff changeset
235 %0 = llvm.mlir.constant(42 : i32) : !llvm.i32
anatofuz
parents:
diff changeset
236 %1 = llvm.mlir.constant(17) : !llvm.i64
anatofuz
parents:
diff changeset
237
anatofuz
parents:
diff changeset
238 // call and extract the values from the structure
anatofuz
parents:
diff changeset
239 %2 = llvm.call @bar(%0, %1) : (%arg0: !llvm.i32, %arg1: !llvm.i32) -> !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
240 %3 = llvm.extractvalue %2[0] : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
241 %4 = llvm.extractvalue %2[1] : !llvm<"{i32, i64}">
anatofuz
parents:
diff changeset
242
anatofuz
parents:
diff changeset
243 // use as before
anatofuz
parents:
diff changeset
244 "use_i32"(%3) : (!llvm.i32) -> ()
anatofuz
parents:
diff changeset
245 "use_i64"(%4) : (!llvm.i64) -> ()
anatofuz
parents:
diff changeset
246 }
anatofuz
parents:
diff changeset
247 ```
anatofuz
parents:
diff changeset
248
anatofuz
parents:
diff changeset
249 ### Calling Convention for `memref`
anatofuz
parents:
diff changeset
250
anatofuz
parents:
diff changeset
251 Function _arguments_ of `memref` type, ranked or unranked, are _expanded_ into a
anatofuz
parents:
diff changeset
252 list of arguments of non-aggregate types that the memref descriptor defined
anatofuz
parents:
diff changeset
253 above comprises. That is, the outer struct type and the inner array types are
anatofuz
parents:
diff changeset
254 replaced with individual arguments.
anatofuz
parents:
diff changeset
255
anatofuz
parents:
diff changeset
256 This convention is implemented in the conversion of `std.func` and `std.call` to
anatofuz
parents:
diff changeset
257 the LLVM dialect, with the former unpacking the descriptor into a set of
anatofuz
parents:
diff changeset
258 individual values and the latter packing those values back into a descriptor so
anatofuz
parents:
diff changeset
259 as to make it transparently usable by other operations. Conversions from other
anatofuz
parents:
diff changeset
260 dialects should take this convention into account.
anatofuz
parents:
diff changeset
261
anatofuz
parents:
diff changeset
262 This specific convention is motivated by the necessity to specify alignment and
anatofuz
parents:
diff changeset
263 aliasing attributes on the raw pointers underpinning the memref.
anatofuz
parents:
diff changeset
264
anatofuz
parents:
diff changeset
265 Examples:
anatofuz
parents:
diff changeset
266
anatofuz
parents:
diff changeset
267 ```mlir
anatofuz
parents:
diff changeset
268 func @foo(%arg0: memref<?xf32>) -> () {
anatofuz
parents:
diff changeset
269 "use"(%arg0) : (memref<?xf32>) -> ()
anatofuz
parents:
diff changeset
270 return
anatofuz
parents:
diff changeset
271 }
anatofuz
parents:
diff changeset
272
anatofuz
parents:
diff changeset
273 // Gets converted to the following.
anatofuz
parents:
diff changeset
274
anatofuz
parents:
diff changeset
275 llvm.func @foo(%arg0: !llvm<"float*">, // Allocated pointer.
anatofuz
parents:
diff changeset
276 %arg1: !llvm<"float*">, // Aligned pointer.
anatofuz
parents:
diff changeset
277 %arg2: !llvm.i64, // Offset.
anatofuz
parents:
diff changeset
278 %arg3: !llvm.i64, // Size in dim 0.
anatofuz
parents:
diff changeset
279 %arg4: !llvm.i64) { // Stride in dim 0.
anatofuz
parents:
diff changeset
280 // Populate memref descriptor structure.
anatofuz
parents:
diff changeset
281 %0 = llvm.mlir.undef : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
282 %1 = llvm.insertvalue %arg0, %0[0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
283 %2 = llvm.insertvalue %arg1, %1[1] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
284 %3 = llvm.insertvalue %arg2, %2[2] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
285 %4 = llvm.insertvalue %arg3, %3[3, 0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
286 %5 = llvm.insertvalue %arg4, %4[4, 0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
287
anatofuz
parents:
diff changeset
288 // Descriptor is now usable as a single value.
anatofuz
parents:
diff changeset
289 "use"(%5) : (!llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">) -> ()
anatofuz
parents:
diff changeset
290 llvm.return
anatofuz
parents:
diff changeset
291 }
anatofuz
parents:
diff changeset
292 ```
anatofuz
parents:
diff changeset
293
anatofuz
parents:
diff changeset
294 ```mlir
anatofuz
parents:
diff changeset
295 func @bar() {
anatofuz
parents:
diff changeset
296 %0 = "get"() : () -> (memref<?xf32>)
anatofuz
parents:
diff changeset
297 call @foo(%0) : (memref<?xf32>) -> ()
anatofuz
parents:
diff changeset
298 return
anatofuz
parents:
diff changeset
299 }
anatofuz
parents:
diff changeset
300
anatofuz
parents:
diff changeset
301 // Gets converted to the following.
anatofuz
parents:
diff changeset
302
anatofuz
parents:
diff changeset
303 llvm.func @bar() {
anatofuz
parents:
diff changeset
304 %0 = "get"() : () -> !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
305
anatofuz
parents:
diff changeset
306 // Unpack the memref descriptor.
anatofuz
parents:
diff changeset
307 %1 = llvm.extractvalue %0[0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
308 %2 = llvm.extractvalue %0[1] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
309 %3 = llvm.extractvalue %0[2] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
310 %4 = llvm.extractvalue %0[3, 0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
311 %5 = llvm.extractvalue %0[4, 0] : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }">
anatofuz
parents:
diff changeset
312
anatofuz
parents:
diff changeset
313 // Pass individual values to the callee.
anatofuz
parents:
diff changeset
314 llvm.call @foo(%1, %2, %3, %4, %5) : (!llvm<"float*">, !llvm<"float*">, !llvm.i64, !llvm.i64, !llvm.i64) -> ()
anatofuz
parents:
diff changeset
315 llvm.return
anatofuz
parents:
diff changeset
316 }
anatofuz
parents:
diff changeset
317
anatofuz
parents:
diff changeset
318 ```
anatofuz
parents:
diff changeset
319
anatofuz
parents:
diff changeset
320 For **unranked** memrefs, the list of function arguments always contains two
anatofuz
parents:
diff changeset
321 elements, same as the unranked memref descriptor: an integer rank, and a
anatofuz
parents:
diff changeset
322 type-erased (`!llvm<"i8*">`) pointer to the ranked memref descriptor. Note that
anatofuz
parents:
diff changeset
323 while the _calling convention_ does not require stack allocation, _casting_ to
anatofuz
parents:
diff changeset
324 unranked memref does since one cannot take an address of an SSA value containing
anatofuz
parents:
diff changeset
325 the ranked memref. The caller is in charge of ensuring the thread safety and
anatofuz
parents:
diff changeset
326 eventually removing unnecessary stack allocations in cast operations.
anatofuz
parents:
diff changeset
327
anatofuz
parents:
diff changeset
328 Example
anatofuz
parents:
diff changeset
329
anatofuz
parents:
diff changeset
330 ```mlir
anatofuz
parents:
diff changeset
331 llvm.func @foo(%arg0: memref<*xf32>) -> () {
anatofuz
parents:
diff changeset
332 "use"(%arg0) : (memref<*xf32>) -> ()
anatofuz
parents:
diff changeset
333 return
anatofuz
parents:
diff changeset
334 }
anatofuz
parents:
diff changeset
335
anatofuz
parents:
diff changeset
336 // Gets converted to the following.
anatofuz
parents:
diff changeset
337
anatofuz
parents:
diff changeset
338 llvm.func @foo(%arg0: !llvm.i64 // Rank.
anatofuz
parents:
diff changeset
339 %arg1: !llvm<"i8*">) { // Type-erased pointer to descriptor.
anatofuz
parents:
diff changeset
340 // Pack the unranked memref descriptor.
anatofuz
parents:
diff changeset
341 %0 = llvm.mlir.undef : !llvm<"{ i64, i8* }">
anatofuz
parents:
diff changeset
342 %1 = llvm.insertvalue %arg0, %0[0] : !llvm<"{ i64, i8* }">
anatofuz
parents:
diff changeset
343 %2 = llvm.insertvalue %arg1, %1[1] : !llvm<"{ i64, i8* }">
anatofuz
parents:
diff changeset
344
anatofuz
parents:
diff changeset
345 "use"(%2) : (!llvm<"{ i64, i8* }">) -> ()
anatofuz
parents:
diff changeset
346 llvm.return
anatofuz
parents:
diff changeset
347 }
anatofuz
parents:
diff changeset
348 ```
anatofuz
parents:
diff changeset
349
anatofuz
parents:
diff changeset
350 ```mlir
anatofuz
parents:
diff changeset
351 llvm.func @bar() {
anatofuz
parents:
diff changeset
352 %0 = "get"() : () -> (memref<*xf32>)
anatofuz
parents:
diff changeset
353 call @foo(%0): (memref<*xf32>) -> ()
anatofuz
parents:
diff changeset
354 return
anatofuz
parents:
diff changeset
355 }
anatofuz
parents:
diff changeset
356
anatofuz
parents:
diff changeset
357 // Gets converted to the following.
anatofuz
parents:
diff changeset
358
anatofuz
parents:
diff changeset
359 llvm.func @bar() {
anatofuz
parents:
diff changeset
360 %0 = "get"() : () -> (!llvm<"{ i64, i8* }">)
anatofuz
parents:
diff changeset
361
anatofuz
parents:
diff changeset
362 // Unpack the memref descriptor.
anatofuz
parents:
diff changeset
363 %1 = llvm.extractvalue %0[0] : !llvm<"{ i64, i8* }">
anatofuz
parents:
diff changeset
364 %2 = llvm.extractvalue %0[1] : !llvm<"{ i64, i8* }">
anatofuz
parents:
diff changeset
365
anatofuz
parents:
diff changeset
366 // Pass individual values to the callee.
anatofuz
parents:
diff changeset
367 llvm.call @foo(%1, %2) : (!llvm.i64, !llvm<"i8*">)
anatofuz
parents:
diff changeset
368 llvm.return
anatofuz
parents:
diff changeset
369 }
anatofuz
parents:
diff changeset
370 ```
anatofuz
parents:
diff changeset
371
anatofuz
parents:
diff changeset
372 *This convention may or may not apply if the conversion of MemRef types is
anatofuz
parents:
diff changeset
373 overridden by the user.*
anatofuz
parents:
diff changeset
374
anatofuz
parents:
diff changeset
375 ### C-compatible wrapper emission
anatofuz
parents:
diff changeset
376
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
377 In practical cases, it may be desirable to have externally-facing functions with
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
378 a single attribute corresponding to a MemRef argument. When interfacing with
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
379 LLVM IR produced from C, the code needs to respect the corresponding calling
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
380 convention. The conversion to the LLVM dialect provides an option to generate
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
381 wrapper functions that take memref descriptors as pointers-to-struct compatible
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
382 with data types produced by Clang when compiling C sources. The generation of
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
383 such wrapper functions can additionally be controlled at a function granularity
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
384 by setting the `llvm.emit_c_interface` unit attribute.
150
anatofuz
parents:
diff changeset
385
anatofuz
parents:
diff changeset
386 More specifically, a memref argument is converted into a pointer-to-struct
anatofuz
parents:
diff changeset
387 argument of type `{T*, T*, i64, i64[N], i64[N]}*` in the wrapper function, where
anatofuz
parents:
diff changeset
388 `T` is the converted element type and `N` is the memref rank. This type is
anatofuz
parents:
diff changeset
389 compatible with that produced by Clang for the following C++ structure template
anatofuz
parents:
diff changeset
390 instantiations or their equivalents in C.
anatofuz
parents:
diff changeset
391
anatofuz
parents:
diff changeset
392 ```cpp
anatofuz
parents:
diff changeset
393 template<typename T, size_t N>
anatofuz
parents:
diff changeset
394 struct MemRefDescriptor {
anatofuz
parents:
diff changeset
395 T *allocated;
anatofuz
parents:
diff changeset
396 T *aligned;
anatofuz
parents:
diff changeset
397 intptr_t offset;
anatofuz
parents:
diff changeset
398 intptr_t sizes[N];
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
399 intptr_t strides[N];
150
anatofuz
parents:
diff changeset
400 };
anatofuz
parents:
diff changeset
401 ```
anatofuz
parents:
diff changeset
402
anatofuz
parents:
diff changeset
403 If enabled, the option will do the following. For _external_ functions declared
anatofuz
parents:
diff changeset
404 in the MLIR module.
anatofuz
parents:
diff changeset
405
anatofuz
parents:
diff changeset
406 1. Declare a new function `_mlir_ciface_<original name>` where memref arguments
anatofuz
parents:
diff changeset
407 are converted to pointer-to-struct and the remaining arguments are converted
anatofuz
parents:
diff changeset
408 as usual.
anatofuz
parents:
diff changeset
409 1. Add a body to the original function (making it non-external) that
anatofuz
parents:
diff changeset
410 1. allocates a memref descriptor,
anatofuz
parents:
diff changeset
411 1. populates it, and
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
412 1. passes the pointer to it into the newly declared interface function, then
150
anatofuz
parents:
diff changeset
413 1. collects the result of the call and returns it to the caller.
anatofuz
parents:
diff changeset
414
anatofuz
parents:
diff changeset
415 For (non-external) functions defined in the MLIR module.
anatofuz
parents:
diff changeset
416
anatofuz
parents:
diff changeset
417 1. Define a new function `_mlir_ciface_<original name>` where memref arguments
anatofuz
parents:
diff changeset
418 are converted to pointer-to-struct and the remaining arguments are converted
anatofuz
parents:
diff changeset
419 as usual.
anatofuz
parents:
diff changeset
420 1. Populate the body of the newly defined function with IR that
anatofuz
parents:
diff changeset
421 1. loads descriptors from pointers;
anatofuz
parents:
diff changeset
422 1. unpacks descriptor into individual non-aggregate values;
anatofuz
parents:
diff changeset
423 1. passes these values into the original function;
anatofuz
parents:
diff changeset
424 1. collects the result of the call and returns it to the caller.
anatofuz
parents:
diff changeset
425
anatofuz
parents:
diff changeset
426 Examples:
anatofuz
parents:
diff changeset
427
anatofuz
parents:
diff changeset
428 ```mlir
anatofuz
parents:
diff changeset
429
anatofuz
parents:
diff changeset
430 func @qux(%arg0: memref<?x?xf32>)
anatofuz
parents:
diff changeset
431
anatofuz
parents:
diff changeset
432 // Gets converted into the following.
anatofuz
parents:
diff changeset
433
anatofuz
parents:
diff changeset
434 // Function with unpacked arguments.
anatofuz
parents:
diff changeset
435 llvm.func @qux(%arg0: !llvm<"float*">, %arg1: !llvm<"float*">, %arg2: !llvm.i64,
anatofuz
parents:
diff changeset
436 %arg3: !llvm.i64, %arg4: !llvm.i64, %arg5: !llvm.i64,
anatofuz
parents:
diff changeset
437 %arg6: !llvm.i64) {
anatofuz
parents:
diff changeset
438 // Populate memref descriptor (as per calling convention).
anatofuz
parents:
diff changeset
439 %0 = llvm.mlir.undef : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
440 %1 = llvm.insertvalue %arg0, %0[0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
441 %2 = llvm.insertvalue %arg1, %1[1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
442 %3 = llvm.insertvalue %arg2, %2[2] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
443 %4 = llvm.insertvalue %arg3, %3[3, 0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
444 %5 = llvm.insertvalue %arg5, %4[4, 0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
445 %6 = llvm.insertvalue %arg4, %5[3, 1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
446 %7 = llvm.insertvalue %arg6, %6[4, 1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
447
anatofuz
parents:
diff changeset
448 // Store the descriptor in a stack-allocated space.
anatofuz
parents:
diff changeset
449 %8 = llvm.mlir.constant(1 : index) : !llvm.i64
anatofuz
parents:
diff changeset
450 %9 = llvm.alloca %8 x !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
451 : (!llvm.i64) -> !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">
anatofuz
parents:
diff changeset
452 llvm.store %7, %9 : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">
anatofuz
parents:
diff changeset
453
anatofuz
parents:
diff changeset
454 // Call the interface function.
anatofuz
parents:
diff changeset
455 llvm.call @_mlir_ciface_qux(%9) : (!llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">) -> ()
anatofuz
parents:
diff changeset
456
anatofuz
parents:
diff changeset
457 // The stored descriptor will be freed on return.
anatofuz
parents:
diff changeset
458 llvm.return
anatofuz
parents:
diff changeset
459 }
anatofuz
parents:
diff changeset
460
anatofuz
parents:
diff changeset
461 // Interface function.
anatofuz
parents:
diff changeset
462 llvm.func @_mlir_ciface_qux(!llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">)
anatofuz
parents:
diff changeset
463 ```
anatofuz
parents:
diff changeset
464
anatofuz
parents:
diff changeset
465 ```mlir
anatofuz
parents:
diff changeset
466 func @foo(%arg0: memref<?x?xf32>) {
anatofuz
parents:
diff changeset
467 return
anatofuz
parents:
diff changeset
468 }
anatofuz
parents:
diff changeset
469
anatofuz
parents:
diff changeset
470 // Gets converted into the following.
anatofuz
parents:
diff changeset
471
anatofuz
parents:
diff changeset
472 // Function with unpacked arguments.
anatofuz
parents:
diff changeset
473 llvm.func @foo(%arg0: !llvm<"float*">, %arg1: !llvm<"float*">, %arg2: !llvm.i64,
anatofuz
parents:
diff changeset
474 %arg3: !llvm.i64, %arg4: !llvm.i64, %arg5: !llvm.i64,
anatofuz
parents:
diff changeset
475 %arg6: !llvm.i64) {
anatofuz
parents:
diff changeset
476 llvm.return
anatofuz
parents:
diff changeset
477 }
anatofuz
parents:
diff changeset
478
anatofuz
parents:
diff changeset
479 // Interface function callable from C.
anatofuz
parents:
diff changeset
480 llvm.func @_mlir_ciface_foo(%arg0: !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">) {
anatofuz
parents:
diff changeset
481 // Load the descriptor.
anatofuz
parents:
diff changeset
482 %0 = llvm.load %arg0 : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }*">
anatofuz
parents:
diff changeset
483
anatofuz
parents:
diff changeset
484 // Unpack the descriptor as per calling convention.
anatofuz
parents:
diff changeset
485 %1 = llvm.extractvalue %0[0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
486 %2 = llvm.extractvalue %0[1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
487 %3 = llvm.extractvalue %0[2] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
488 %4 = llvm.extractvalue %0[3, 0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
489 %5 = llvm.extractvalue %0[3, 1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
490 %6 = llvm.extractvalue %0[4, 0] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
491 %7 = llvm.extractvalue %0[4, 1] : !llvm<"{ float*, float*, i64, [2 x i64], [2 x i64] }">
anatofuz
parents:
diff changeset
492 llvm.call @foo(%1, %2, %3, %4, %5, %6, %7)
anatofuz
parents:
diff changeset
493 : (!llvm<"float*">, !llvm<"float*">, !llvm.i64, !llvm.i64, !llvm.i64,
anatofuz
parents:
diff changeset
494 !llvm.i64, !llvm.i64) -> ()
anatofuz
parents:
diff changeset
495 llvm.return
anatofuz
parents:
diff changeset
496 }
anatofuz
parents:
diff changeset
497 ```
anatofuz
parents:
diff changeset
498
anatofuz
parents:
diff changeset
499 Rationale: Introducing auxiliary functions for C-compatible interfaces is
anatofuz
parents:
diff changeset
500 preferred to modifying the calling convention since it will minimize the effect
anatofuz
parents:
diff changeset
501 of C compatibility on intra-module calls or calls between MLIR-generated
anatofuz
parents:
diff changeset
502 functions. In particular, when calling external functions from an MLIR module in
anatofuz
parents:
diff changeset
503 a (parallel) loop, the fact of storing a memref descriptor on stack can lead to
anatofuz
parents:
diff changeset
504 stack exhaustion and/or concurrent access to the same address. Auxiliary
anatofuz
parents:
diff changeset
505 interface function serves as an allocation scope in this case. Furthermore, when
anatofuz
parents:
diff changeset
506 targeting accelerators with separate memory spaces such as GPUs, stack-allocated
anatofuz
parents:
diff changeset
507 descriptors passed by pointer would have to be transferred to the device memory,
anatofuz
parents:
diff changeset
508 which introduces significant overhead. In such situations, auxiliary interface
anatofuz
parents:
diff changeset
509 functions are executed on host and only pass the values through device function
anatofuz
parents:
diff changeset
510 invocation mechanism.
anatofuz
parents:
diff changeset
511
anatofuz
parents:
diff changeset
512 ## Repeated Successor Removal
anatofuz
parents:
diff changeset
513
anatofuz
parents:
diff changeset
514 Since the goal of the LLVM IR dialect is to reflect LLVM IR in MLIR, the dialect
anatofuz
parents:
diff changeset
515 and the conversion procedure must account for the differences between block
anatofuz
parents:
diff changeset
516 arguments and LLVM IR PHI nodes. In particular, LLVM IR disallows PHI nodes with
anatofuz
parents:
diff changeset
517 different values coming from the same source. Therefore, the LLVM IR dialect
anatofuz
parents:
diff changeset
518 disallows operations that have identical successors accepting arguments, which
anatofuz
parents:
diff changeset
519 would lead to invalid PHI nodes. The conversion process resolves the potential
anatofuz
parents:
diff changeset
520 PHI source ambiguity by injecting dummy blocks if the same block is used more
anatofuz
parents:
diff changeset
521 than once as a successor in an instruction. These dummy blocks branch
anatofuz
parents:
diff changeset
522 unconditionally to the original successors, pass them the original operands
anatofuz
parents:
diff changeset
523 (available in the dummy block because it is dominated by the original block) and
anatofuz
parents:
diff changeset
524 are used instead of them in the original terminator operation.
anatofuz
parents:
diff changeset
525
anatofuz
parents:
diff changeset
526 Example:
anatofuz
parents:
diff changeset
527
anatofuz
parents:
diff changeset
528 ```mlir
anatofuz
parents:
diff changeset
529 cond_br %0, ^bb1(%1 : i32), ^bb1(%2 : i32)
anatofuz
parents:
diff changeset
530 ^bb1(%3 : i32)
anatofuz
parents:
diff changeset
531 "use"(%3) : (i32) -> ()
anatofuz
parents:
diff changeset
532 ```
anatofuz
parents:
diff changeset
533
anatofuz
parents:
diff changeset
534 leads to a new basic block being inserted,
anatofuz
parents:
diff changeset
535
anatofuz
parents:
diff changeset
536 ```mlir
anatofuz
parents:
diff changeset
537 cond_br %0, ^bb1(%1 : i32), ^dummy
anatofuz
parents:
diff changeset
538 ^bb1(%3 : i32):
anatofuz
parents:
diff changeset
539 "use"(%3) : (i32) -> ()
anatofuz
parents:
diff changeset
540 ^dummy:
anatofuz
parents:
diff changeset
541 br ^bb1(%4 : i32)
anatofuz
parents:
diff changeset
542 ```
anatofuz
parents:
diff changeset
543
anatofuz
parents:
diff changeset
544 before the conversion to the LLVM IR dialect:
anatofuz
parents:
diff changeset
545
anatofuz
parents:
diff changeset
546 ```mlir
anatofuz
parents:
diff changeset
547 llvm.cond_br %0, ^bb1(%1 : !llvm.i32), ^dummy
anatofuz
parents:
diff changeset
548 ^bb1(%3 : !llvm<"i32">):
anatofuz
parents:
diff changeset
549 "use"(%3) : (!llvm.i32) -> ()
anatofuz
parents:
diff changeset
550 ^dummy:
anatofuz
parents:
diff changeset
551 llvm.br ^bb1(%2 : !llvm.i32)
anatofuz
parents:
diff changeset
552 ```
anatofuz
parents:
diff changeset
553
anatofuz
parents:
diff changeset
554 ## Default Memref Model
anatofuz
parents:
diff changeset
555
anatofuz
parents:
diff changeset
556 ### Memref Descriptor
anatofuz
parents:
diff changeset
557
anatofuz
parents:
diff changeset
558 Within a converted function, a `memref`-typed value is represented by a memref
anatofuz
parents:
diff changeset
559 _descriptor_, the type of which is the structure type obtained by converting
anatofuz
parents:
diff changeset
560 from the memref type. This descriptor holds all the necessary information to
anatofuz
parents:
diff changeset
561 produce an address of a specific element. In particular, it holds dynamic values
anatofuz
parents:
diff changeset
562 for static sizes, and they are expected to match at all times.
anatofuz
parents:
diff changeset
563
anatofuz
parents:
diff changeset
564 It is created by the allocation operation and is updated by the conversion
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
565 operations that may change static dimensions into dynamic dimensions and vice versa.
150
anatofuz
parents:
diff changeset
566
anatofuz
parents:
diff changeset
567 **Note**: LLVM IR conversion does not support `memref`s with layouts that are
anatofuz
parents:
diff changeset
568 not amenable to the strided form.
anatofuz
parents:
diff changeset
569
anatofuz
parents:
diff changeset
570 ### Index Linearization
anatofuz
parents:
diff changeset
571
anatofuz
parents:
diff changeset
572 Accesses to a memref element are transformed into an access to an element of the
anatofuz
parents:
diff changeset
573 buffer pointed to by the descriptor. The position of the element in the buffer
anatofuz
parents:
diff changeset
574 is calculated by linearizing memref indices in row-major order (lexically first
anatofuz
parents:
diff changeset
575 index is the slowest varying, similar to C, but accounting for strides). The
anatofuz
parents:
diff changeset
576 computation of the linear address is emitted as arithmetic operation in the LLVM
anatofuz
parents:
diff changeset
577 IR dialect. Strides are extracted from the memref descriptor.
anatofuz
parents:
diff changeset
578
anatofuz
parents:
diff changeset
579 Accesses to zero-dimensional memref (that are interpreted as pointers to the
anatofuz
parents:
diff changeset
580 elemental type) are directly converted into `llvm.load` or `llvm.store` without
anatofuz
parents:
diff changeset
581 any pointer manipulations.
anatofuz
parents:
diff changeset
582
anatofuz
parents:
diff changeset
583 Examples:
anatofuz
parents:
diff changeset
584
anatofuz
parents:
diff changeset
585 An access to a zero-dimensional memref is converted into a plain load:
anatofuz
parents:
diff changeset
586
anatofuz
parents:
diff changeset
587 ```mlir
anatofuz
parents:
diff changeset
588 // before
anatofuz
parents:
diff changeset
589 %0 = load %m[] : memref<f32>
anatofuz
parents:
diff changeset
590
anatofuz
parents:
diff changeset
591 // after
anatofuz
parents:
diff changeset
592 %0 = llvm.load %m : !llvm<"float*">
anatofuz
parents:
diff changeset
593 ```
anatofuz
parents:
diff changeset
594
anatofuz
parents:
diff changeset
595 An access to a memref with indices:
anatofuz
parents:
diff changeset
596
anatofuz
parents:
diff changeset
597 ```mlir
anatofuz
parents:
diff changeset
598 %0 = load %m[1,2,3,4] : memref<10x?x13x?xf32>
anatofuz
parents:
diff changeset
599 ```
anatofuz
parents:
diff changeset
600
anatofuz
parents:
diff changeset
601 is transformed into the equivalent of the following code:
anatofuz
parents:
diff changeset
602
anatofuz
parents:
diff changeset
603 ```mlir
anatofuz
parents:
diff changeset
604 // Compute the linearized index from strides. Each block below extracts one
anatofuz
parents:
diff changeset
605 // stride from the descriptor, multiplies it with the index and accumulates
anatofuz
parents:
diff changeset
606 // the total offset.
anatofuz
parents:
diff changeset
607 %stride1 = llvm.extractvalue[4, 0] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
608 %idx1 = llvm.mlir.constant(1 : index) !llvm.i64
anatofuz
parents:
diff changeset
609 %addr1 = muli %stride1, %idx1 : !llvm.i64
anatofuz
parents:
diff changeset
610
anatofuz
parents:
diff changeset
611 %stride2 = llvm.extractvalue[4, 1] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
612 %idx2 = llvm.mlir.constant(2 : index) !llvm.i64
anatofuz
parents:
diff changeset
613 %addr2 = muli %stride2, %idx2 : !llvm.i64
anatofuz
parents:
diff changeset
614 %addr3 = addi %addr1, %addr2 : !llvm.i64
anatofuz
parents:
diff changeset
615
anatofuz
parents:
diff changeset
616 %stride3 = llvm.extractvalue[4, 2] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
617 %idx3 = llvm.mlir.constant(3 : index) !llvm.i64
anatofuz
parents:
diff changeset
618 %addr4 = muli %stride3, %idx3 : !llvm.i64
anatofuz
parents:
diff changeset
619 %addr5 = addi %addr3, %addr4 : !llvm.i64
anatofuz
parents:
diff changeset
620
anatofuz
parents:
diff changeset
621 %stride4 = llvm.extractvalue[4, 3] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
622 %idx4 = llvm.mlir.constant(4 : index) !llvm.i64
anatofuz
parents:
diff changeset
623 %addr6 = muli %stride4, %idx4 : !llvm.i64
anatofuz
parents:
diff changeset
624 %addr7 = addi %addr5, %addr6 : !llvm.i64
anatofuz
parents:
diff changeset
625
anatofuz
parents:
diff changeset
626 // Add the linear offset to the address.
anatofuz
parents:
diff changeset
627 %offset = llvm.extractvalue[2] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
628 %addr8 = addi %addr7, %offset : !llvm.i64
anatofuz
parents:
diff changeset
629
anatofuz
parents:
diff changeset
630 // Obtain the aligned pointer.
anatofuz
parents:
diff changeset
631 %aligned = llvm.extractvalue[1] : !llvm<"{float*, float*, i64, i64[4], i64[4]}">
anatofuz
parents:
diff changeset
632
anatofuz
parents:
diff changeset
633 // Get the address of the data pointer.
anatofuz
parents:
diff changeset
634 %ptr = llvm.getelementptr %aligned[%addr8]
anatofuz
parents:
diff changeset
635 : !llvm<"{float*, float*, i64, i64[4], i64[4]}"> -> !llvm<"float*">
anatofuz
parents:
diff changeset
636
anatofuz
parents:
diff changeset
637 // Perform the actual load.
anatofuz
parents:
diff changeset
638 %0 = llvm.load %ptr : !llvm<"float*">
anatofuz
parents:
diff changeset
639 ```
anatofuz
parents:
diff changeset
640
anatofuz
parents:
diff changeset
641 For stores, the address computation code is identical and only the actual store
anatofuz
parents:
diff changeset
642 operation is different.
anatofuz
parents:
diff changeset
643
anatofuz
parents:
diff changeset
644 Note: the conversion does not perform any sort of common subexpression
anatofuz
parents:
diff changeset
645 elimination when emitting memref accesses.