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