| # MLIR Language Reference |
| |
| MLIR (Multi-Level IR) is a compiler intermediate representation with |
| similarities to traditional three-address SSA representations (like |
| [LLVM IR](http://llvm.org/docs/LangRef.html) or |
| [SIL](https://github.com/apple/swift/blob/master/docs/SIL.rst)), but which |
| introduces notions from polyhedral loop optimization as first-class concepts. |
| This hybrid design is optimized to represent, analyze, and transform high level |
| dataflow graphs as well as target-specific code generated for high performance |
| data parallel systems. Beyond its representational capabilities, its single |
| continuous design provides a framework to lower from dataflow graphs to |
| high-performance target-specific code. |
| |
| This document defines and describes the key concepts in MLIR, and is intended |
| to be a dry reference document - the [rationale |
| documentation](Rationale/Rationale.md), |
| [glossary](../getting_started/Glossary.md), and other content are hosted |
| elsewhere. |
| |
| MLIR is designed to be used in three different forms: a human-readable textual |
| form suitable for debugging, an in-memory form suitable for programmatic |
| transformations and analysis, and a compact serialized form suitable for |
| storage and transport. The different forms all describe the same semantic |
| content. This document describes the human-readable textual form. |
| |
| [TOC] |
| |
| ## High-Level Structure |
| |
| MLIR is fundamentally based on a graph-like data structure of nodes, called |
| *Operations*, and edges, called *Values*. Each Value is the result of exactly |
| one Operation or Block Argument, and has a *Value Type* defined by the [type |
| system](#type-system). [Operations](#operations) are contained in |
| [Blocks](#blocks) and Blocks are contained in [Regions](#regions). Operations |
| are also ordered within their containing block and Blocks are ordered in their |
| containing region, although this order may or may not be semantically |
| meaningful in a given [kind of region](Interfaces.md#regionkindinterfaces)). |
| Operations may also contain regions, enabling hierarchical structures to be |
| represented. |
| |
| Operations can represent many different concepts, from higher-level concepts |
| like function definitions, function calls, buffer allocations, view or slices |
| of buffers, and process creation, to lower-level concepts like |
| target-independent arithmetic, target-specific instructions, configuration |
| registers, and logic gates. These different concepts are represented by |
| different operations in MLIR and the set of operations usable in MLIR can be |
| arbitrarily extended. |
| |
| MLIR also provides an extensible framework for transformations on operations, |
| using familiar concepts of compiler [Passes](Passes.md). Enabling an arbitrary |
| set of passes on an arbitrary set of operations results in a significant |
| scaling challenge, since each transformation must potentially take into |
| account the semantics of any operation. MLIR addresses this complexity by |
| allowing operation semantics to be described abstractly using |
| [Traits](Traits.md) and [Interfaces](Interfaces.md), enabling transformations |
| to operate on operations more generically. Traits often describe verification |
| constraints on valid IR, enabling complex invariants to be captured and |
| checked. (see [Op vs |
| Operation](docs/Tutorials/Toy/Ch-2/#op-vs-operation-using-mlir-operations)) |
| |
| One obvious application of MLIR is to represent an |
| [SSA-based](https://en.wikipedia.org/wiki/Static_single_assignment_form) IR, |
| like the LLVM core IR, with appropriate choice of Operation Types to define |
| [Modules](#module), [Functions](#functions), Branches, Allocations, and |
| verification constraints to ensure the SSA Dominance property. MLIR includes a |
| 'standard' dialect which defines just such structures. However, MLIR is |
| intended to be general enough to represent other compiler-like data |
| structures, such as Abstract Syntax Trees in a language frontend, generated |
| instructions in a target-specific backend, or circuits in a High-Level |
| Synthesis tool. |
| |
| Here's an example of an MLIR module: |
| |
| ```mlir |
| // Compute A*B using an implementation of multiply kernel and print the |
| // result using a TensorFlow op. The dimensions of A and B are partially |
| // known. The shapes are assumed to match. |
| func @mul(%A: tensor<100x?xf32>, %B: tensor<?x50xf32>) -> (tensor<100x50xf32>) { |
| // Compute the inner dimension of %A using the dim operation. |
| %n = dim %A, 1 : tensor<100x?xf32> |
| |
| // Allocate addressable "buffers" and copy tensors %A and %B into them. |
| %A_m = alloc(%n) : memref<100x?xf32> |
| tensor_store %A to %A_m : memref<100x?xf32> |
| |
| %B_m = alloc(%n) : memref<?x50xf32> |
| tensor_store %B to %B_m : memref<?x50xf32> |
| |
| // Call function @multiply passing memrefs as arguments, |
| // and getting returned the result of the multiplication. |
| %C_m = call @multiply(%A_m, %B_m) |
| : (memref<100x?xf32>, memref<?x50xf32>) -> (memref<100x50xf32>) |
| |
| dealloc %A_m : memref<100x?xf32> |
| dealloc %B_m : memref<?x50xf32> |
| |
| // Load the buffer data into a higher level "tensor" value. |
| %C = tensor_load %C_m : memref<100x50xf32> |
| dealloc %C_m : memref<100x50xf32> |
| |
| // Call TensorFlow built-in function to print the result tensor. |
| "tf.Print"(%C){message: "mul result"} |
| : (tensor<100x50xf32) -> (tensor<100x50xf32>) |
| |
| return %C : tensor<100x50xf32> |
| } |
| |
| // A function that multiplies two memrefs and returns the result. |
| func @multiply(%A: memref<100x?xf32>, %B: memref<?x50xf32>) |
| -> (memref<100x50xf32>) { |
| // Compute the inner dimension of %A. |
| %n = dim %A, 1 : memref<100x?xf32> |
| |
| // Allocate memory for the multiplication result. |
| %C = alloc() : memref<100x50xf32> |
| |
| // Multiplication loop nest. |
| affine.for %i = 0 to 100 { |
| affine.for %j = 0 to 50 { |
| store 0 to %C[%i, %j] : memref<100x50xf32> |
| affine.for %k = 0 to %n { |
| %a_v = load %A[%i, %k] : memref<100x?xf32> |
| %b_v = load %B[%k, %j] : memref<?x50xf32> |
| %prod = mulf %a_v, %b_v : f32 |
| %c_v = load %C[%i, %j] : memref<100x50xf32> |
| %sum = addf %c_v, %prod : f32 |
| store %sum, %C[%i, %j] : memref<100x50xf32> |
| } |
| } |
| } |
| return %C : memref<100x50xf32> |
| } |
| ``` |
| |
| ## Notation |
| |
| MLIR has a simple and unambiguous grammar, allowing it to reliably round-trip |
| through a textual form. This is important for development of the compiler - |
| e.g. for understanding the state of code as it is being transformed and |
| writing test cases. |
| |
| This document describes the grammar using |
| [Extended Backus-Naur Form (EBNF)](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form). |
| |
| This is the EBNF grammar used in this document, presented in yellow boxes. |
| |
| ``` |
| alternation ::= expr0 | expr1 | expr2 // Either expr0 or expr1 or expr2. |
| sequence ::= expr0 expr1 expr2 // Sequence of expr0 expr1 expr2. |
| repetition0 ::= expr* // 0 or more occurrences. |
| repetition1 ::= expr+ // 1 or more occurrences. |
| optionality ::= expr? // 0 or 1 occurrence. |
| grouping ::= (expr) // Everything inside parens is grouped together. |
| literal ::= `abcd` // Matches the literal `abcd`. |
| ``` |
| |
| Code examples are presented in blue boxes. |
| |
| ```mlir |
| // This is an example use of the grammar above: |
| // This matches things like: ba, bana, boma, banana, banoma, bomana... |
| example ::= `b` (`an` | `om`)* `a` |
| ``` |
| |
| ### Common syntax |
| |
| The following core grammar productions are used in this document: |
| |
| ``` |
| // TODO: Clarify the split between lexing (tokens) and parsing (grammar). |
| digit ::= [0-9] |
| hex_digit ::= [0-9a-fA-F] |
| letter ::= [a-zA-Z] |
| id-punct ::= [$._-] |
| |
| integer-literal ::= decimal-literal | hexadecimal-literal |
| decimal-literal ::= digit+ |
| hexadecimal-literal ::= `0x` hex_digit+ |
| float-literal ::= [-+]?[0-9]+[.][0-9]*([eE][-+]?[0-9]+)? |
| string-literal ::= `"` [^"\n\f\v\r]* `"` TODO: define escaping rules |
| ``` |
| |
| Not listed here, but MLIR does support comments. They use standard BCPL syntax, |
| starting with a `//` and going until the end of the line. |
| |
| ### Identifiers and keywords |
| |
| Syntax: |
| |
| ``` |
| // Identifiers |
| bare-id ::= (letter|[_]) (letter|digit|[_$.])* |
| bare-id-list ::= bare-id (`,` bare-id)* |
| value-id ::= `%` suffix-id |
| suffix-id ::= (digit+ | ((letter|id-punct) (letter|id-punct|digit)*)) |
| |
| symbol-ref-id ::= `@` (suffix-id | string-literal) |
| value-id-list ::= value-id (`,` value-id)* |
| |
| // Uses of value, e.g. in an operand list to an operation. |
| value-use ::= value-id |
| value-use-list ::= value-use (`,` value-use)* |
| ``` |
| |
| Identifiers name entities such as values, types and functions, and are |
| chosen by the writer of MLIR code. Identifiers may be descriptive (e.g. |
| `%batch_size`, `@matmul`), or may be non-descriptive when they are |
| auto-generated (e.g. `%23`, `@func42`). Identifier names for values may be |
| used in an MLIR text file but are not persisted as part of the IR - the printer |
| will give them anonymous names like `%42`. |
| |
| MLIR guarantees identifiers never collide with keywords by prefixing identifiers |
| with a sigil (e.g. `%`, `#`, `@`, `^`, `!`). In certain unambiguous contexts |
| (e.g. affine expressions), identifiers are not prefixed, for brevity. New |
| keywords may be added to future versions of MLIR without danger of collision |
| with existing identifiers. |
| |
| Value identifiers are only [in scope](#value-scoping) for the (nested) |
| region in which they are defined and cannot be accessed or referenced |
| outside of that region. Argument identifiers in mapping functions are |
| in scope for the mapping body. Particular operations may further limit |
| which identifiers are in scope in their regions. For instance, the |
| scope of values in a region with [SSA control flow |
| semantics](#control-flow-and-ssacfg-regions) is constrained according |
| to the standard definition of [SSA |
| dominance](https://en.wikipedia.org/wiki/Dominator_\(graph_theory\)). Another |
| example is the [IsolatedFromAbove trait](Traits.md#isolatedfromabove), |
| which restricts directly accessing values defined in containing |
| regions. |
| |
| Function identifiers and mapping identifiers are associated with |
| [Symbols](SymbolsAndSymbolTables) and have scoping rules dependent on |
| symbol attributes. |
| |
| ## Dialects |
| |
| Dialects are the mechanism by which to engage with and extend the MLIR |
| ecosystem. They allow for defining new [operations](#operations), as well as |
| [attributes](#attributes) and [types](#type-system). Each dialect is given a |
| unique `namespace` that is prefixed to each defined attribute/operation/type. |
| For example, the [Affine dialect](Dialects/Affine.md) defines the namespace: |
| `affine`. |
| |
| MLIR allows for multiple dialects, even those outside of the main tree, to |
| co-exist together within one module. Dialects are produced and consumed by |
| certain passes. MLIR provides a [framework](DialectConversion.md) to convert |
| between, and within, different dialects. |
| |
| A few of the dialects supported by MLIR: |
| |
| * [Affine dialect](Dialects/Affine.md) |
| * [GPU dialect](Dialects/GPU.md) |
| * [LLVM dialect](Dialects/LLVM.md) |
| * [SPIR-V dialect](Dialects/SPIR-V.md) |
| * [Standard dialect](Dialects/Standard.md) |
| * [Vector dialect](Dialects/Vector.md) |
| |
| ### Target specific operations |
| |
| Dialects provide a modular way in which targets can expose target-specific |
| operations directly through to MLIR. As an example, some targets go through |
| LLVM. LLVM has a rich set of intrinsics for certain target-independent |
| operations (e.g. addition with overflow check) as well as providing access to |
| target-specific operations for the targets it supports (e.g. vector |
| permutation operations). LLVM intrinsics in MLIR are represented via |
| operations that start with an "llvm." name. |
| |
| Example: |
| |
| ```mlir |
| // LLVM: %x = call {i16, i1} @llvm.sadd.with.overflow.i16(i16 %a, i16 %b) |
| %x:2 = "llvm.sadd.with.overflow.i16"(%a, %b) : (i16, i16) -> (i16, i1) |
| ``` |
| |
| These operations only work when targeting LLVM as a backend (e.g. for CPUs and |
| GPUs), and are required to align with the LLVM definition of these intrinsics. |
| |
| ## Operations |
| |
| Syntax: |
| |
| ``` |
| operation ::= op-result-list? (generic-operation | custom-operation) |
| trailing-location? |
| generic-operation ::= string-literal `(` value-use-list? `)` successor-list? |
| (`(` region-list `)`)? attribute-dict? `:` function-type |
| custom-operation ::= bare-id custom-operation-format |
| op-result-list ::= op-result (`,` op-result)* `=` |
| op-result ::= value-id (`:` integer-literal) |
| successor-list ::= successor (`,` successor)* |
| successor ::= caret-id (`:` bb-arg-list)? |
| region-list ::= region (`,` region)* |
| trailing-location ::= (`loc` `(` location `)`)? |
| ``` |
| |
| MLIR introduces a uniform concept called _operations_ to enable describing |
| many different levels of abstractions and computations. Operations in MLIR are |
| fully extensible (there is no fixed list of operations) and have |
| application-specific semantics. For example, MLIR supports [target-independent |
| operations](Dialects/Standard.md#memory-operations), [affine |
| operations](Dialects/Affine.md), and [target-specific machine |
| operations](#target-specific-operations). |
| |
| The internal representation of an operation is simple: an operation is |
| identified by a unique string (e.g. `dim`, `tf.Conv2d`, `x86.repmovsb`, |
| `ppc.eieio`, etc), can return zero or more results, take zero or more |
| operands, may have zero or more attributes, may have zero or more successors, |
| and zero or more enclosed [regions](#regions). The generic printing form |
| includes all these elements literally, with a function type to indicate the |
| types of the results and operands. |
| |
| Example: |
| |
| ```mlir |
| // An operation that produces two results. |
| // The results of %result can be accessed via the <name> `#` <opNo> syntax. |
| %result:2 = "foo_div"() : () -> (f32, i32) |
| |
| // Pretty form that defines a unique name for each result. |
| %foo, %bar = "foo_div"() : () -> (f32, i32) |
| |
| // Invoke a TensorFlow function called tf.scramble with two inputs |
| // and an attribute "fruit". |
| %2 = "tf.scramble"(%result#0, %bar) {fruit: "banana"} : (f32, i32) -> f32 |
| ``` |
| |
| In addition to the basic syntax above, dialects may register known operations. |
| This allows those dialects to support _custom assembly form_ for parsing and |
| printing operations. In the operation sets listed below, we show both forms. |
| |
| ### Terminator Operations |
| |
| These are a special category of operations that *must* terminate a block, e.g. |
| [branches](Dialects/Standard.md#terminator-operations). These operations may |
| also have a list of successors ([blocks](#blocks) and their arguments). |
| |
| Example: |
| |
| ```mlir |
| // Branch to ^bb1 or ^bb2 depending on the condition %cond. |
| // Pass value %v to ^bb2, but not to ^bb1. |
| "cond_br"(%cond)[^bb1, ^bb2(%v : index)] : (i1) -> () |
| ``` |
| |
| ### Module |
| |
| ``` |
| module ::= `module` symbol-ref-id? (`attributes` attribute-dict)? region |
| ``` |
| |
| An MLIR Module represents a top-level container operation. It contains a single |
| [SSACFG region](#control-flow-and-ssacfg-regions) containing a single block |
| which can contain any operations. Operations within this region cannot |
| implicitly capture values defined outside the module, i.e. Modules are |
| [IsolatedFromAbove](Traits.md#isolatedfromabove). Modules have an optional |
| [symbol name](SymbolsAndSymbolTables.md) which can be used to refer to them in |
| operations. |
| |
| ### Functions |
| |
| An MLIR Function is an operation with a name containing a single [SSACFG |
| region](#control-flow-and-ssacfg-regions). Operations within this region |
| cannot implicitly capture values defined outside of the function, |
| i.e. Functions are [IsolatedFromAbove](Traits.md#isolatedfromabove). All |
| external references must use function arguments or attributes that establish a |
| symbolic connection (e.g. symbols referenced by name via a string attribute |
| like [SymbolRefAttr](#symbol-reference-attribute)): |
| |
| ``` |
| function ::= `func` function-signature function-attributes? function-body? |
| |
| function-signature ::= symbol-ref-id `(` argument-list `)` |
| (`->` function-result-list)? |
| |
| argument-list ::= (named-argument (`,` named-argument)*) | /*empty*/ |
| argument-list ::= (type attribute-dict? (`,` type attribute-dict?)*) | /*empty*/ |
| named-argument ::= value-id `:` type attribute-dict? |
| |
| function-result-list ::= function-result-list-parens |
| | non-function-type |
| function-result-list-parens ::= `(` `)` |
| | `(` function-result-list-no-parens `)` |
| function-result-list-no-parens ::= function-result (`,` function-result)* |
| function-result ::= type attribute-dict? |
| |
| function-attributes ::= `attributes` attribute-dict |
| function-body ::= region |
| ``` |
| |
| An external function declaration (used when referring to a function declared |
| in some other module) has no body. While the MLIR textual form provides a nice |
| inline syntax for function arguments, they are internally represented as |
| "block arguments" to the first block in the region. |
| |
| Only dialect attribute names may be specified in the attribute dictionaries |
| for function arguments, results, or the function itself. |
| |
| Examples: |
| |
| ```mlir |
| // External function definitions. |
| func @abort() |
| func @scribble(i32, i64, memref<? x 128 x f32, #layout_map0>) -> f64 |
| |
| // A function that returns its argument twice: |
| func @count(%x: i64) -> (i64, i64) |
| attributes {fruit: "banana"} { |
| return %x, %x: i64, i64 |
| } |
| |
| // A function with an argument attribute |
| func @example_fn_arg(%x: i32 {swift.self = unit}) |
| |
| // A function with a result attribute |
| func @example_fn_result() -> (f64 {dialectName.attrName = 0 : i64}) |
| |
| // A function with an attribute |
| func @example_fn_attr() attributes {dialectName.attrName = false} |
| ``` |
| |
| ## Blocks |
| |
| Syntax: |
| |
| ``` |
| block ::= block-label operation+ |
| block-label ::= block-id block-arg-list? `:` |
| block-id ::= caret-id |
| caret-id ::= `^` suffix-id |
| value-id-and-type ::= value-id `:` type |
| |
| // Non-empty list of names and types. |
| value-id-and-type-list ::= value-id-and-type (`,` value-id-and-type)* |
| |
| block-arg-list ::= `(` value-id-and-type-list? `)` |
| ``` |
| |
| A *Block* is an ordered list of operations, concluding with a single |
| [terminator operation](#terminator-operations). In [SSACFG |
| regions](#control-flow-and-ssacfg-regions), each block represents a compiler |
| [basic block](https://en.wikipedia.org/wiki/Basic_block) where instructions |
| inside the block are executed in order and terminator operations implement |
| control flow branches between basic blocks. |
| |
| Blocks in MLIR take a list of block arguments, notated in a function-like |
| way. Block arguments are bound to values specified by the semantics of |
| individual operations. Block arguments of the entry block of a region are also |
| arguments to the region and the values bound to these arguments are determined |
| by the semantics of the containing operation. Block arguments of other blocks |
| are determined by the semantics of terminator operations, e.g. Branches, which |
| have the block as a successor. In regions with [control |
| flow](#control-flow-and-ssacfg-regions), MLIR leverages this structure to |
| implicitly represent the passage of control-flow dependent values without the |
| complex nuances of PHI nodes in traditional SSA representations. Note that |
| values which are not control-flow dependent can be referenced directly and do |
| not need to be passed through block arguments. |
| |
| Here is a simple example function showing branches, returns, and block |
| arguments: |
| |
| ```mlir |
| func @simple(i64, i1) -> i64 { |
| ^bb0(%a: i64, %cond: i1): // Code dominated by ^bb0 may refer to %a |
| cond_br %cond, ^bb1, ^bb2 |
| |
| ^bb1: |
| br ^bb3(%a: i64) // Branch passes %a as the argument |
| |
| ^bb2: |
| %b = addi %a, %a : i64 |
| br ^bb3(%b: i64) // Branch passes %b as the argument |
| |
| // ^bb3 receives an argument, named %c, from predecessors |
| // and passes it on to bb4 along with %a. %a is referenced |
| // directly from its defining operation and is not passed through |
| // an argument of ^bb3. |
| ^bb3(%c: i64): |
| br ^bb4(%c, %a : i64, i64) |
| |
| ^bb4(%d : i64, %e : i64): |
| %0 = addi %d, %e : i64 |
| return %0 : i64 // Return is also a terminator. |
| } |
| ``` |
| |
| **Context:** The "block argument" representation eliminates a number |
| of special cases from the IR compared to traditional "PHI nodes are |
| operations" SSA IRs (like LLVM). For example, the [parallel copy |
| semantics](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.524.5461&rep=rep1&type=pdf) |
| of SSA is immediately apparent, and function arguments are no longer a |
| special case: they become arguments to the entry block [[more |
| rationale](Rationale/Rationale.md#block-arguments-vs-phi-nodes)]. Blocks |
| are also a fundamental concept that cannot be represented by |
| operations because values defined in an operation cannot be accessed |
| outside the operation. |
| |
| ## Regions |
| |
| ### Definition |
| |
| A region is an ordered list of MLIR [Blocks](#blocks). The semantics within a |
| region is not imposed by the IR. Instead, the containing operation defines the |
| semantics of the regions it contains. MLIR currently defines two kinds of |
| regions: [SSACFG regions](#control-flow-and-ssacfg-regions), which describe |
| control flow between blocks, and [Graph regions](#graph-regions), which do not |
| require control flow between block. The kinds of regions within an operation |
| are described using the |
| [RegionKindInterface](Interfaces.md#regionkindinterfaces). |
| |
| Regions do not have a name or an address, only the blocks contained in a |
| region do. Regions must be contained within operations and have no type or |
| attributes. The first block in the region is a special block called the 'entry |
| block'. The arguments to the entry block are also the arguments of the region |
| itself. The entry block cannot be listed as a successor of any other |
| block. The syntax for a region is as follows: |
| |
| ``` |
| region ::= `{` block* `}` |
| ``` |
| |
| A function body is an example of a region: it consists of a CFG of blocks and |
| has additional semantic restrictions that other types of regions may not have. |
| For example, in a function body, block terminators must either branch to a |
| different block, or return from a function where the types of the `return` |
| arguments must match the result types of the function signature. Similarly, |
| the function arguments must match the types and count of the region arguments. |
| In general, operations with regions can define these correspondances |
| arbitrarily. |
| |
| ### Value Scoping |
| |
| Regions provide hierarchical encapsulation of programs: it is impossible to |
| reference, i.e. branch to, a block which is not in the same region as the |
| source of the reference, i.e. a terminator operation. Similarly, regions |
| provides a natural scoping for value visibility: values defined in a region |
| don't escape to the enclosing region, if any. By default, operations inside a |
| region can reference values defined outside of the region whenever it would |
| have been legal for operands of the enclosing operation to reference those |
| values, but this can be restricted using traits, such as |
| [OpTrait::IsolatedFromAbove](Traits.md#isolatedfromabove), or a custom |
| verifier. |
| |
| Example: |
| |
| ```mlir |
| "any_op"(%a) ({ // if %a is in-scope in the containing region... |
| // then %a is in-scope here too. |
| %new_value = "another_op"(%a) : (i64) -> (i64) |
| }) : (i64) -> (i64) |
| ``` |
| |
| MLIR defines a generalized 'hierarchical dominance' concept that operates |
| across hierarchy and defines whether a value is 'in scope' and can be used by |
| a particular operation. Whether a value can be used by another operation in |
| the same region is defined by the kind of region. A value defined in a region |
| can be used by an operation which has a parent in the same region, if and only |
| if the parent could use the value. A value defined by an argument to a region |
| can always be used by any operation deeply contained in the region. A value |
| defined in a region can never be used outside of the region. |
| |
| ### Control Flow and SSACFG Regions |
| |
| In MLIR, control flow semantics of a region is indicated by |
| [RegionKind::SSACFG](Interfaces.md#regionkindinterfaces). Informally, these |
| regions support semantics where operations in a region 'execute |
| sequentially'. Before an operation executes, its operands have well-defined |
| values. After an operation executes, the operands have the same values and |
| results also have well-defined values. After an operation executes, the next |
| operation in the block executes until the operation is the terminator operation |
| at the end of a block, in which case some other operation will execute. The |
| determination of the next instruction to execute is the 'passing of control |
| flow'. |
| |
| In general, when control flow is passed to an operation, MLIR does not |
| restrict when control flow enters or exits the regions contained in that |
| operation. However, when control flow enters a region, it always begins in the |
| first block of the region, called the *entry* block. Terminator operations |
| ending each block represent control flow by explicitly specifying the |
| successor blocks of the block. Control flow can only pass to one of the |
| specified successor blocks as in a `branch` operation, or back to the |
| containing operation as in a `return` operation. Terminator operations without |
| successors can only pass control back to the containing operation. Within |
| these restrictions, the particular semantics of terminator operations is |
| determined by the specific dialect operations involved. Blocks (other than the |
| entry block) that are not listed as a successor of a terminator operation are |
| defined to be unreachable and can be removed without affecting the semantics |
| of the containing operation. |
| |
| Although control flow always enters a region through the entry block, control |
| flow may exit a region through any block with an appropriate terminator. The |
| standard dialect leverages this capability to define operations with |
| Single-Entry-Multiple-Exit (SEME) regions, possibly flowing through different |
| blocks in the region and exiting through any block with a `return` |
| operation. This behavior is similar to that of a function body in most |
| programming languages. In addition, control flow may also not reach the end of |
| a block or region, for example if a function call does not return. |
| |
| Example: |
| |
| ```mlir |
| func @accelerator_compute(i64, i1) -> i64 { // An SSACFG region |
| ^bb0(%a: i64, %cond: i1): // Code dominated by ^bb0 may refer to %a |
| cond_br %cond, ^bb1, ^bb2 |
| |
| ^bb1: |
| // This def for %value does not dominate ^bb2 |
| %value = "op.convert"(%a) : (i64) -> i64 |
| br ^bb3(%a: i64) // Branch passes %a as the argument |
| |
| ^bb2: |
| accelerator.launch() { // An SSACFG region |
| ^bb0: |
| // Region of code nested under "accelerator.launch", it can reference %a but |
| // not %value. |
| %new_value = "accelerator.do_something"(%a) : (i64) -> () |
| } |
| // %new_value cannot be referenced outside of the region |
| |
| ^bb3: |
| ... |
| } |
| ``` |
| |
| #### Operations with Multiple Regions |
| |
| An operation containing multiple regions also completely determines the |
| semantics of those regions. In particular, when control flow is passed to an |
| operation, it may transfer control flow to any contained region. When control |
| flow exits a region and is returned to the containing operation, the |
| containing operation may pass control flow to any region in the same |
| operation. An operation may also pass control flow to multiple contained |
| regions concurrently. An operation may also pass control flow into regions |
| that were specified in other operations, in particular those that defined the |
| values or symbols the given operation uses as in a call operation. This |
| passage of control is generally independent of passage of control flow through |
| the basic blocks of the containing region. |
| |
| #### Closure |
| |
| Regions allow defining an operation that creates a closure, for example by |
| “boxing” the body of the region into a value they produce. It remains up to the |
| operation to define its semantics. Note that if an operation triggers |
| asynchronous execution of the region, it is under the responsibility of the |
| operation caller to wait for the region to be executed guaranteeing that any |
| directly used values remain live. |
| |
| ### Graph Regions |
| |
| In MLIR, graph-like semantics in a region is indicated by |
| [RegionKind::Graph](Interfaces.md#regionkindinterfaces). Graph regions are |
| appropriate for concurrent semantics without control flow, or for modeling |
| generic directed graph data structures. Graph regions are appropriate for |
| representing cyclic relationships between coupled values where there is no |
| fundamental order to the relationships. For instance, operations in a graph |
| region may represent independent threads of control with values representing |
| streams of data. As usual in MLIR, the particular semantics of a region is |
| completely determined by its containing operation. Graph regions may only |
| contain a single basic block (the entry block). |
| |
| **Rationale:** Currently graph regions are arbitrarily limited to a single |
| basic block, although there is no particular semantic reason for this |
| limitation. This limitation has been added to make it easier to stabilize the |
| pass infrastructure and commonly used passes for processing graph regions to |
| properly handle feedback loops. Multi-block regions may be allowed in the |
| future if use cases that require it arise. |
| |
| In graph regions, MLIR operations naturally represent nodes, while each MLIR |
| value represents a multi-edge connecting a single source node and multiple |
| destination nodes. All values defined in the region as results of operations |
| are in scope within the region and can be accessed by any other operation in |
| the region. In graph regions, the order of operations within a block and the |
| order of blocks in a region is not semantically meaningful and non-terminator |
| operations may be freely reordered, for instance, by canonicalization. Other |
| kinds of graphs, such as graphs with multiple source nodes and multiple |
| destination nodes, can also be represented by representing graph edges as MLIR |
| operations. |
| |
| Note that cycles can occur within a single block in a graph region, or between |
| basic blocks. |
| |
| ```mlir |
| "test.graph_region"() ({ // A Graph region |
| %1 = "op1"(%1, %3) : (i32, i32) -> (i32) // OK: %1, %3 allowed here |
| %2 = "test.ssacfg_region"() ({ |
| %5 = "op2"(%1, %2, %3, %4) : (i32, i32, i32, i32) -> (i32) // OK: %1, %2, %3, %4 all defined in the containing region |
| }) : () -> (i32) |
| %3 = "op2"(%1, %4) : (i32, i32) -> (i32) // OK: %4 allowed here |
| %4 = "op3"(%1) : (i32) -> (i32) |
| }) : () -> () |
| ``` |
| |
| ### Arguments and Results |
| |
| The arguments of the first block of a region are treated as arguments of the |
| region. The source of these arguments is defined by the semantics of the parent |
| operation. They may correspond to some of the values the operation itself uses. |
| |
| Regions produce a (possibly empty) list of values. The operation semantics |
| defines the relation between the region results and the operation results. |
| |
| ## Type System |
| |
| Each value in MLIR has a type defined by the type system below. There are a |
| number of primitive types (like integers) and also aggregate types for tensors |
| and memory buffers. MLIR [builtin types](#builtin-types) do not include |
| structures, arrays, or dictionaries. |
| |
| MLIR has an open type system (i.e. there is no fixed list of types), and types |
| may have application-specific semantics. For example, MLIR supports a set of |
| [dialect types](#dialect-types). |
| |
| ``` |
| type ::= type-alias | dialect-type | builtin-type |
| |
| type-list-no-parens ::= type (`,` type)* |
| type-list-parens ::= `(` `)` |
| | `(` type-list-no-parens `)` |
| |
| // This is a common way to refer to a value with a specified type. |
| ssa-use-and-type ::= ssa-use `:` type |
| |
| // Non-empty list of names and types. |
| ssa-use-and-type-list ::= ssa-use-and-type (`,` ssa-use-and-type)* |
| ``` |
| |
| ### Type Aliases |
| |
| ``` |
| type-alias-def ::= '!' alias-name '=' 'type' type |
| type-alias ::= '!' alias-name |
| ``` |
| |
| MLIR supports defining named aliases for types. A type alias is an identifier |
| that can be used in the place of the type that it defines. These aliases *must* |
| be defined before their uses. Alias names may not contain a '.', since those |
| names are reserved for [dialect types](#dialect-types). |
| |
| Example: |
| |
| ```mlir |
| !avx_m128 = type vector<4 x f32> |
| |
| // Using the original type. |
| "foo"(%x) : vector<4 x f32> -> () |
| |
| // Using the type alias. |
| "foo"(%x) : !avx_m128 -> () |
| ``` |
| |
| ### Dialect Types |
| |
| Similarly to operations, dialects may define custom extensions to the type |
| system. |
| |
| ``` |
| dialect-namespace ::= bare-id |
| |
| opaque-dialect-item ::= dialect-namespace '<' string-literal '>' |
| |
| pretty-dialect-item ::= dialect-namespace '.' pretty-dialect-item-lead-ident |
| pretty-dialect-item-body? |
| |
| pretty-dialect-item-lead-ident ::= '[A-Za-z][A-Za-z0-9._]*' |
| pretty-dialect-item-body ::= '<' pretty-dialect-item-contents+ '>' |
| pretty-dialect-item-contents ::= pretty-dialect-item-body |
| | '(' pretty-dialect-item-contents+ ')' |
| | '[' pretty-dialect-item-contents+ ']' |
| | '{' pretty-dialect-item-contents+ '}' |
| | '[^[<({>\])}\0]+' |
| |
| dialect-type ::= '!' opaque-dialect-item |
| dialect-type ::= '!' pretty-dialect-item |
| ``` |
| |
| Dialect types can be specified in a verbose form, e.g. like this: |
| |
| ```mlir |
| // LLVM type that wraps around llvm IR types. |
| !llvm<"i32*"> |
| |
| // Tensor flow string type. |
| !tf.string |
| |
| // Complex type |
| !foo<"something<abcd>"> |
| |
| // Even more complex type |
| !foo<"something<a%%123^^^>>>"> |
| ``` |
| |
| Dialect types that are simple enough can use the pretty format, which is a |
| lighter weight syntax that is equivalent to the above forms: |
| |
| ```mlir |
| // Tensor flow string type. |
| !tf.string |
| |
| // Complex type |
| !foo.something<abcd> |
| ``` |
| |
| Sufficiently complex dialect types are required to use the verbose form for |
| generality. For example, the more complex type shown above wouldn't be valid in |
| the lighter syntax: `!foo.something<a%%123^^^>>>` because it contains characters |
| that are not allowed in the lighter syntax, as well as unbalanced `<>` |
| characters. |
| |
| See [here](Tutorials/DefiningAttributesAndTypes.md) to learn how to define dialect types. |
| |
| ### Builtin Types |
| |
| Builtin types are a core set of [dialect types](#dialect-types) that are defined |
| in a builtin dialect and thus available to all users of MLIR. |
| |
| ``` |
| builtin-type ::= complex-type |
| | float-type |
| | function-type |
| | index-type |
| | integer-type |
| | memref-type |
| | none-type |
| | tensor-type |
| | tuple-type |
| | vector-type |
| ``` |
| |
| #### Complex Type |
| |
| Syntax: |
| |
| ``` |
| complex-type ::= `complex` `<` type `>` |
| ``` |
| |
| The value of `complex` type represents a complex number with a parameterized |
| element type, which is composed of a real and imaginary value of that element |
| type. The element must be a floating point or integer scalar type. |
| |
| Examples: |
| |
| ```mlir |
| complex<f32> |
| complex<i32> |
| ``` |
| |
| #### Floating Point Types |
| |
| Syntax: |
| |
| ``` |
| // Floating point. |
| float-type ::= `f16` | `bf16` | `f32` | `f64` | `f80` | `f128` |
| ``` |
| |
| MLIR supports float types of certain widths that are widely used as indicated |
| above. |
| |
| #### Function Type |
| |
| Syntax: |
| |
| ``` |
| // MLIR functions can return multiple values. |
| function-result-type ::= type-list-parens |
| | non-function-type |
| |
| function-type ::= type-list-parens `->` function-result-type |
| ``` |
| |
| MLIR supports first-class functions: for example, the |
| [`constant` operation](Dialects/Standard.md#stdconstant-constantop) produces the |
| address of a function as a value. This value may be passed to and |
| returned from functions, merged across control flow boundaries with |
| [block arguments](#blocks), and called with the |
| [`call_indirect` operation](Dialects/Standard.md#call-indirect-operation). |
| |
| Function types are also used to indicate the arguments and results of |
| [operations](#operations). |
| |
| #### Index Type |
| |
| Syntax: |
| |
| ``` |
| // Target word-sized integer. |
| index-type ::= `index` |
| ``` |
| |
| The `index` type is a signless integer whose size is equal to the natural |
| machine word of the target |
| ([rationale](Rationale/Rationale.md#integer-signedness-semantics)) and is used |
| by the affine constructs in MLIR. Unlike fixed-size integers, it cannot be used |
| as an element of vector |
| ([rationale](Rationale/Rationale.md#index-type-disallowed-in-vector-types)). |
| |
| **Rationale:** integers of platform-specific bit widths are practical to express |
| sizes, dimensionalities and subscripts. |
| |
| #### Integer Type |
| |
| Syntax: |
| |
| ``` |
| // Sized integers like i1, i4, i8, i16, i32. |
| signed-integer-type ::= `si` [1-9][0-9]* |
| unsigned-integer-type ::= `ui` [1-9][0-9]* |
| signless-integer-type ::= `i` [1-9][0-9]* |
| integer-type ::= signed-integer-type | |
| unsigned-integer-type | |
| signless-integer-type |
| ``` |
| |
| MLIR supports arbitrary precision integer types. Integer types have a designated |
| width and may have signedness semantics. |
| |
| **Rationale:** low precision integers (like `i2`, `i4` etc) are useful for |
| low-precision inference chips, and arbitrary precision integers are useful for |
| hardware synthesis (where a 13 bit multiplier is a lot cheaper/smaller than a 16 |
| bit one). |
| |
| TODO: Need to decide on a representation for quantized integers |
| ([initial thoughts](Rationale/Rationale.md#quantized-integer-operations)). |
| |
| #### Memref Type |
| |
| Syntax: |
| |
| ``` |
| memref-type ::= ranked-memref-type | unranked-memref-type |
| |
| ranked-memref-type ::= `memref` `<` dimension-list-ranked type |
| (`,` layout-specification)? (`,` memory-space)? `>` |
| |
| unranked-memref-type ::= `memref` `<*x` type (`,` memory-space)? `>` |
| |
| stride-list ::= `[` (dimension (`,` dimension)*)? `]` |
| strided-layout ::= `offset:` dimension `,` `strides: ` stride-list |
| semi-affine-map-composition ::= (semi-affine-map `,` )* semi-affine-map |
| layout-specification ::= semi-affine-map-composition | strided-layout |
| memory-space ::= integer-literal /* | TODO: address-space-id */ |
| ``` |
| |
| A `memref` type is a reference to a region of memory (similar to a buffer |
| pointer, but more powerful). The buffer pointed to by a memref can be allocated, |
| aliased and deallocated. A memref can be used to read and write data from/to the |
| memory region which it references. Memref types use the same shape specifier as |
| tensor types. Note that `memref<f32>`, `memref<0 x f32>`, `memref<1 x 0 x f32>`, |
| and `memref<0 x 1 x f32>` are all different types. |
| |
| A `memref` is allowed to have an unknown rank (e.g. `memref<*xf32>`). The |
| purpose of unranked memrefs is to allow external library functions to receive |
| memref arguments of any rank without versioning the functions based on the rank. |
| Other uses of this type are disallowed or will have undefined behavior. |
| |
| ##### Codegen of Unranked Memref |
| |
| Using unranked memref in codegen besides the case mentioned above is highly |
| discouraged. Codegen is concerned with generating loop nests and specialized |
| instructions for high-performance, unranked memref is concerned with hiding the |
| rank and thus, the number of enclosing loops required to iterate over the data. |
| However, if there is a need to code-gen unranked memref, one possible path is to |
| cast into a static ranked type based on the dynamic rank. Another possible path |
| is to emit a single while loop conditioned on a linear index and perform |
| delinearization of the linear index to a dynamic array containing the (unranked) |
| indices. While this is possible, it is expected to not be a good idea to perform |
| this during codegen as the cost of the translations is expected to be |
| prohibitive and optimizations at this level are not expected to be worthwhile. |
| If expressiveness is the main concern, irrespective of performance, passing |
| unranked memrefs to an external C++ library and implementing rank-agnostic logic |
| there is expected to be significantly simpler. |
| |
| Unranked memrefs may provide expressiveness gains in the future and help bridge |
| the gap with unranked tensors. Unranked memrefs will not be expected to be |
| exposed to codegen but one may query the rank of an unranked memref (a special |
| op will be needed for this purpose) and perform a switch and cast to a ranked |
| memref as a prerequisite to codegen. |
| |
| Example: |
| |
| ```mlir |
| // With static ranks, we need a function for each possible argument type |
| %A = alloc() : memref<16x32xf32> |
| %B = alloc() : memref<16x32x64xf32> |
| call @helper_2D(%A) : (memref<16x32xf32>)->() |
| call @helper_3D(%B) : (memref<16x32x64xf32>)->() |
| |
| // With unknown rank, the functions can be unified under one unranked type |
| %A = alloc() : memref<16x32xf32> |
| %B = alloc() : memref<16x32x64xf32> |
| // Remove rank info |
| %A_u = memref_cast %A : memref<16x32xf32> -> memref<*xf32> |
| %B_u = memref_cast %B : memref<16x32x64xf32> -> memref<*xf32> |
| // call same function with dynamic ranks |
| call @helper(%A_u) : (memref<*xf32>)->() |
| call @helper(%B_u) : (memref<*xf32>)->() |
| ``` |
| |
| The core syntax and representation of a layout specification is a |
| [semi-affine map](Dialects/Affine.md#semi-affine-maps). Additionally, syntactic |
| sugar is supported to make certain layout specifications more intuitive to read. |
| For the moment, a `memref` supports parsing a strided form which is converted to |
| a semi-affine map automatically. |
| |
| The memory space of a memref is specified by a target-specific integer index. If |
| no memory space is specified, then the default memory space (0) is used. The |
| default space is target specific but always at index 0. |
| |
| TODO: MLIR will eventually have target-dialects which allow symbolic use of |
| memory hierarchy names (e.g. L3, L2, L1, ...) but we have not spec'd the details |
| of that mechanism yet. Until then, this document pretends that it is valid to |
| refer to these memories by `bare-id`. |
| |
| The notionally dynamic value of a memref value includes the address of the |
| buffer allocated, as well as the symbols referred to by the shape, layout map, |
| and index maps. |
| |
| Examples of memref static type |
| |
| ```mlir |
| // Identity index/layout map |
| #identity = affine_map<(d0, d1) -> (d0, d1)> |
| |
| // Column major layout. |
| #col_major = affine_map<(d0, d1, d2) -> (d2, d1, d0)> |
| |
| // A 2-d tiled layout with tiles of size 128 x 256. |
| #tiled_2d_128x256 = affine_map<(d0, d1) -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)> |
| |
| // A tiled data layout with non-constant tile sizes. |
| #tiled_dynamic = affine_map<(d0, d1)[s0, s1] -> (d0 floordiv s0, d1 floordiv s1, |
| d0 mod s0, d1 mod s1)> |
| |
| // A layout that yields a padding on two at either end of the minor dimension. |
| #padded = affine_map<(d0, d1) -> (d0, (d1 + 2) floordiv 2, (d1 + 2) mod 2)> |
| |
| |
| // The dimension list "16x32" defines the following 2D index space: |
| // |
| // { (i, j) : 0 <= i < 16, 0 <= j < 32 } |
| // |
| memref<16x32xf32, #identity> |
| |
| // The dimension list "16x4x?" defines the following 3D index space: |
| // |
| // { (i, j, k) : 0 <= i < 16, 0 <= j < 4, 0 <= k < N } |
| // |
| // where N is a symbol which represents the runtime value of the size of |
| // the third dimension. |
| // |
| // %N here binds to the size of the third dimension. |
| %A = alloc(%N) : memref<16x4x?xf32, #col_major> |
| |
| // A 2-d dynamic shaped memref that also has a dynamically sized tiled layout. |
| // The memref index space is of size %M x %N, while %B1 and %B2 bind to the |
| // symbols s0, s1 respectively of the layout map #tiled_dynamic. Data tiles of |
| // size %B1 x %B2 in the logical space will be stored contiguously in memory. |
| // The allocation size will be (%M ceildiv %B1) * %B1 * (%N ceildiv %B2) * %B2 |
| // f32 elements. |
| %T = alloc(%M, %N) [%B1, %B2] : memref<?x?xf32, #tiled_dynamic> |
| |
| // A memref that has a two-element padding at either end. The allocation size |
| // will fit 16 * 64 float elements of data. |
| %P = alloc() : memref<16x64xf32, #padded> |
| |
| // Affine map with symbol 's0' used as offset for the first dimension. |
| #imapS = affine_map<(d0, d1) [s0] -> (d0 + s0, d1)> |
| // Allocate memref and bind the following symbols: |
| // '%n' is bound to the dynamic second dimension of the memref type. |
| // '%o' is bound to the symbol 's0' in the affine map of the memref type. |
| %n = ... |
| %o = ... |
| %A = alloc (%n)[%o] : <16x?xf32, #imapS> |
| ``` |
| |
| ##### Index Space |
| |
| A memref dimension list defines an index space within which the memref can be |
| indexed to access data. |
| |
| ##### Index |
| |
| Data is accessed through a memref type using a multidimensional index into the |
| multidimensional index space defined by the memref's dimension list. |
| |
| Examples |
| |
| ```mlir |
| // Allocates a memref with 2D index space: |
| // { (i, j) : 0 <= i < 16, 0 <= j < 32 } |
| %A = alloc() : memref<16x32xf32, #imapA> |
| |
| // Loads data from memref '%A' using a 2D index: (%i, %j) |
| %v = load %A[%i, %j] : memref<16x32xf32, #imapA> |
| ``` |
| |
| ##### Index Map |
| |
| An index map is a one-to-one |
| [semi-affine map](Dialects/Affine.md#semi-affine-maps) that transforms a |
| multidimensional index from one index space to another. For example, the |
| following figure shows an index map which maps a 2-dimensional index from a 2x2 |
| index space to a 3x3 index space, using symbols `S0` and `S1` as offsets. |
| |
|  |
| |
| The number of domain dimensions and range dimensions of an index map can be |
| different, but must match the number of dimensions of the input and output index |
| spaces on which the map operates. The index space is always non-negative and |
| integral. In addition, an index map must specify the size of each of its range |
| dimensions onto which it maps. Index map symbols must be listed in order with |
| symbols for dynamic dimension sizes first, followed by other required symbols. |
| |
| ##### Layout Map |
| |
| A layout map is a [semi-affine map](Dialects/Affine.md#semi-affine-maps) which |
| encodes logical to physical index space mapping, by mapping input dimensions to |
| their ordering from most-major (slowest varying) to most-minor (fastest |
| varying). Therefore, an identity layout map corresponds to a row-major layout. |
| Identity layout maps do not contribute to the MemRef type identification and are |
| discarded on construction. That is, a type with an explicit identity map is |
| `memref<?x?xf32, (i,j)->(i,j)>` is strictly the same as the one without layout |
| maps, `memref<?x?xf32>`. |
| |
| Layout map examples: |
| |
| ```mlir |
| // MxN matrix stored in row major layout in memory: |
| #layout_map_row_major = (i, j) -> (i, j) |
| |
| // MxN matrix stored in column major layout in memory: |
| #layout_map_col_major = (i, j) -> (j, i) |
| |
| // MxN matrix stored in a 2-d blocked/tiled layout with 64x64 tiles. |
| #layout_tiled = (i, j) -> (i floordiv 64, j floordiv 64, i mod 64, j mod 64) |
| ``` |
| |
| ##### Affine Map Composition |
| |
| A memref specifies a semi-affine map composition as part of its type. A |
| semi-affine map composition is a composition of semi-affine maps beginning with |
| zero or more index maps, and ending with a layout map. The composition must be |
| conformant: the number of dimensions of the range of one map, must match the |
| number of dimensions of the domain of the next map in the composition. |
| |
| The semi-affine map composition specified in the memref type, maps from accesses |
| used to index the memref in load/store operations to other index spaces (i.e. |
| logical to physical index mapping). Each of the |
| [semi-affine maps](Dialects/Affine.md) and thus its composition is required to |
| be one-to-one. |
| |
| The semi-affine map composition can be used in dependence analysis, memory |
| access pattern analysis, and for performance optimizations like vectorization, |
| copy elision and in-place updates. If an affine map composition is not specified |
| for the memref, the identity affine map is assumed. |
| |
| ##### Strided MemRef |
| |
| A memref may specify strides as part of its type. A stride specification is a |
| list of integer values that are either static or `?` (dynamic case). Strides |
| encode the distance, in number of elements, in (linear) memory between |
| successive entries along a particular dimension. A stride specification is |
| syntactic sugar for an equivalent strided memref representation using |
| semi-affine maps. For example, `memref<42x16xf32, offset: 33, strides: [1, 64]>` |
| specifies a non-contiguous memory region of `42` by `16` `f32` elements such |
| that: |
| |
| 1. the minimal size of the enclosing memory region must be `33 + 42 * 1 + 16 * |
| 64 = 1066` elements; |
| 2. the address calculation for accessing element `(i, j)` computes `33 + i + |
| 64 * j` |
| 3. the distance between two consecutive elements along the outer dimension is |
| `1` element and the distance between two consecutive elements along the |
| outer dimension is `64` elements. |
| |
| This corresponds to a column major view of the memory region and is internally |
| represented as the type `memref<42x16xf32, (i, j) -> (33 + i + 64 * j)>`. |
| |
| The specification of strides must not alias: given an n-D strided memref, |
| indices `(i1, ..., in)` and `(j1, ..., jn)` may not refer to the same memory |
| address unless `i1 == j1, ..., in == jn`. |
| |
| Strided memrefs represent a view abstraction over preallocated data. They are |
| constructed with special ops, yet to be introduced. Strided memrefs are a |
| special subclass of memrefs with generic semi-affine map and correspond to a |
| normalized memref descriptor when lowering to LLVM. |
| |
| #### None Type |
| |
| Syntax: |
| |
| ``` |
| none-type ::= `none` |
| ``` |
| |
| The `none` type is a unit type, i.e. a type with exactly one possible value, |
| where its value does not have a defined dynamic representation. |
| |
| #### Tensor Type |
| |
| Syntax: |
| |
| ``` |
| tensor-type ::= `tensor` `<` dimension-list type `>` |
| |
| dimension-list ::= dimension-list-ranked | (`*` `x`) |
| dimension-list-ranked ::= (dimension `x`)* |
| dimension ::= `?` | decimal-literal |
| ``` |
| |
| Values with tensor type represents aggregate N-dimensional data values, and |
| have a known element type. It may have an unknown rank (indicated by `*`) or may |
| have a fixed rank with a list of dimensions. Each dimension may be a static |
| non-negative decimal constant or be dynamically determined (indicated by `?`). |
| |
| The runtime representation of the MLIR tensor type is intentionally abstracted - |
| you cannot control layout or get a pointer to the data. For low level buffer |
| access, MLIR has a [`memref` type](#memref-type). This abstracted runtime |
| representation holds both the tensor data values as well as information about |
| the (potentially dynamic) shape of the tensor. The |
| [`dim` operation](Dialects/Standard.md#dim-operation) returns the size of a |
| dimension from a value of tensor type. |
| |
| Note: hexadecimal integer literals are not allowed in tensor type declarations |
| to avoid confusion between `0xf32` and `0 x f32`. Zero sizes are allowed in |
| tensors and treated as other sizes, e.g., `tensor<0 x 1 x i32>` and `tensor<1 x |
| 0 x i32>` are different types. Since zero sizes are not allowed in some other |
| types, such tensors should be optimized away before lowering tensors to vectors. |
| |
| Examples: |
| |
| ```mlir |
| // Tensor with unknown rank. |
| tensor<* x f32> |
| |
| // Known rank but unknown dimensions. |
| tensor<? x ? x ? x ? x f32> |
| |
| // Partially known dimensions. |
| tensor<? x ? x 13 x ? x f32> |
| |
| // Full static shape. |
| tensor<17 x 4 x 13 x 4 x f32> |
| |
| // Tensor with rank zero. Represents a scalar. |
| tensor<f32> |
| |
| // Zero-element dimensions are allowed. |
| tensor<0 x 42 x f32> |
| |
| // Zero-element tensor of f32 type (hexadecimal literals not allowed here). |
| tensor<0xf32> |
| ``` |
| |
| #### Tuple Type |
| |
| Syntax: |
| |
| ``` |
| tuple-type ::= `tuple` `<` (type ( `,` type)*)? `>` |
| ``` |
| |
| The value of `tuple` type represents a fixed-size collection of elements, where |
| each element may be of a different type. |
| |
| **Rationale:** Though this type is first class in the type system, MLIR provides |
| no standard operations for operating on `tuple` types |
| ([rationale](Rationale/Rationale.md#tuple-types)). |
| |
| Examples: |
| |
| ```mlir |
| // Empty tuple. |
| tuple<> |
| |
| // Single element |
| tuple<f32> |
| |
| // Many elements. |
| tuple<i32, f32, tensor<i1>, i5> |
| ``` |
| |
| #### Vector Type |
| |
| Syntax: |
| |
| ``` |
| vector-type ::= `vector` `<` static-dimension-list vector-element-type `>` |
| vector-element-type ::= float-type | integer-type |
| |
| static-dimension-list ::= (decimal-literal `x`)+ |
| ``` |
| |
| The vector type represents a SIMD style vector, used by target-specific |
| operation sets like AVX. While the most common use is for 1D vectors (e.g. |
| vector<16 x f32>) we also support multidimensional registers on targets that |
| support them (like TPUs). |
| |
| Vector shapes must be positive decimal integers. |
| |
| Note: hexadecimal integer literals are not allowed in vector type declarations, |
| `vector<0x42xi32>` is invalid because it is interpreted as a 2D vector with |
| shape `(0, 42)` and zero shapes are not allowed. |
| |
| ## Attributes |
| |
| Syntax: |
| |
| ``` |
| attribute-dict ::= `{` `}` |
| | `{` attribute-entry (`,` attribute-entry)* `}` |
| attribute-entry ::= dialect-attribute-entry | dependent-attribute-entry |
| dialect-attribute-entry ::= dialect-namespace `.` bare-id `=` attribute-value |
| dependent-attribute-entry ::= dependent-attribute-name `=` attribute-value |
| dependent-attribute-name ::= ((letter|[_]) (letter|digit|[_$])*) |
| | string-literal |
| ``` |
| |
| Attributes are the mechanism for specifying constant data on operations in |
| places where a variable is never allowed - e.g. the index of a |
| [`dim` operation](Dialects/Standard.md#stddim-dimop), or the stride of a |
| convolution. They consist of a name and a concrete attribute value. The set of |
| expected attributes, their structure, and their interpretation are all |
| contextually dependent on what they are attached to. |
| |
| There are two main classes of attributes: dependent and dialect. Dependent |
| attributes derive their structure and meaning from what they are attached to; |
| e.g., the meaning of the `index` attribute on a `dim` operation is defined by |
| the `dim` operation. Dialect attributes, on the other hand, derive their context |
| and meaning from a specific dialect. An example of a dialect attribute may be a |
| `swift.self` function argument attribute that indicates an argument is the |
| self/context parameter. The context of this attribute is defined by the `swift` |
| dialect and not the function argument. |
| |
| Attribute values are represented by the following forms: |
| |
| ``` |
| attribute-value ::= attribute-alias | dialect-attribute | builtin-attribute |
| ``` |
| |
| ### Attribute Value Aliases |
| |
| ``` |
| attribute-alias ::= '#' alias-name '=' attribute-value |
| attribute-alias ::= '#' alias-name |
| ``` |
| |
| MLIR supports defining named aliases for attribute values. An attribute alias is |
| an identifier that can be used in the place of the attribute that it defines. |
| These aliases *must* be defined before their uses. Alias names may not contain a |
| '.', since those names are reserved for |
| [dialect attributes](#dialect-attribute-values). |
| |
| Example: |
| |
| ```mlir |
| #map = affine_map<(d0) -> (d0 + 10)> |
| |
| // Using the original attribute. |
| %b = affine.apply affine_map<(d0) -> (d0 + 10)> (%a) |
| |
| // Using the attribute alias. |
| %b = affine.apply #map(%a) |
| ``` |
| |
| ### Dialect Attribute Values |
| |
| Similarly to operations, dialects may define custom attribute values. The |
| syntactic structure of these values is identical to custom dialect type values, |
| except that dialect attribute values are distinguished with a leading '#', while |
| dialect types are distinguished with a leading '!'. |
| |
| ``` |
| dialect-attribute-value ::= '#' opaque-dialect-item |
| dialect-attribute-value ::= '#' pretty-dialect-item |
| ``` |
| |
| Dialect attribute values can be specified in a verbose form, e.g. like this: |
| |
| ```mlir |
| // Complex attribute value. |
| #foo<"something<abcd>"> |
| |
| // Even more complex attribute value. |
| #foo<"something<a%%123^^^>>>"> |
| ``` |
| |
| Dialect attribute values that are simple enough can use the pretty format, which |
| is a lighter weight syntax that is equivalent to the above forms: |
| |
| ```mlir |
| // Complex attribute |
| #foo.something<abcd> |
| ``` |
| |
| Sufficiently complex dialect attribute values are required to use the verbose |
| form for generality. For example, the more complex type shown above would not be |
| valid in the lighter syntax: `#foo.something<a%%123^^^>>>` because it contains |
| characters that are not allowed in the lighter syntax, as well as unbalanced |
| `<>` characters. |
| |
| See [here](Tutorials/DefiningAttributesAndTypes.md) on how to define dialect |
| attribute values. |
| |
| ### Builtin Attribute Values |
| |
| Builtin attributes are a core set of |
| [dialect attributes](#dialect-attribute-values) that are defined in a builtin |
| dialect and thus available to all users of MLIR. |
| |
| ``` |
| builtin-attribute ::= affine-map-attribute |
| | array-attribute |
| | bool-attribute |
| | dictionary-attribute |
| | elements-attribute |
| | float-attribute |
| | integer-attribute |
| | integer-set-attribute |
| | string-attribute |
| | symbol-ref-attribute |
| | type-attribute |
| | unit-attribute |
| ``` |
| |
| #### AffineMap Attribute |
| |
| Syntax: |
| |
| ``` |
| affine-map-attribute ::= `affine_map` `<` affine-map `>` |
| ``` |
| |
| An affine-map attribute is an attribute that represents an affine-map object. |
| |
| #### Array Attribute |
| |
| Syntax: |
| |
| ``` |
| array-attribute ::= `[` (attribute-value (`,` attribute-value)*)? `]` |
| ``` |
| |
| An array attribute is an attribute that represents a collection of attribute |
| values. |
| |
| #### Boolean Attribute |
| |
| Syntax: |
| |
| ``` |
| bool-attribute ::= bool-literal |
| ``` |
| |
| A boolean attribute is a literal attribute that represents a one-bit boolean |
| value, true or false. |
| |
| #### Dictionary Attribute |
| |
| Syntax: |
| |
| ``` |
| dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}` |
| ``` |
| |
| A dictionary attribute is an attribute that represents a sorted collection of |
| named attribute values. The elements are sorted by name, and each name must be |
| unique within the collection. |
| |
| #### Elements Attributes |
| |
| Syntax: |
| |
| ``` |
| elements-attribute ::= dense-elements-attribute |
| | opaque-elements-attribute |
| | sparse-elements-attribute |
| ``` |
| |
| An elements attribute is a literal attribute that represents a constant |
| [vector](#vector-type) or [tensor](#tensor-type) value. |
| |
| ##### Dense Elements Attribute |
| |
| Syntax: |
| |
| ``` |
| dense-elements-attribute ::= `dense` `<` attribute-value `>` `:` |
| ( tensor-type | vector-type ) |
| ``` |
| |
| A dense elements attribute is an elements attribute where the storage for the |
| constant vector or tensor value has been densely packed. The attribute supports |
| storing integer or floating point elements, with integer/index/floating element |
| types. It also support storing string elements with a custom dialect string |
| element type. |
| |
| ##### Opaque Elements Attribute |
| |
| Syntax: |
| |
| ``` |
| opaque-elements-attribute ::= `opaque` `<` dialect-namespace `,` |
| hex-string-literal `>` `:` |
| ( tensor-type | vector-type ) |
| ``` |
| |
| An opaque elements attribute is an elements attribute where the content of the |
| value is opaque. The representation of the constant stored by this elements |
| attribute is only understood, and thus decodable, by the dialect that created |
| it. |
| |
| Note: The parsed string literal must be in hexadecimal form. |
| |
| ##### Sparse Elements Attribute |
| |
| Syntax: |
| |
| ``` |
| sparse-elements-attribute ::= `sparse` `<` attribute-value `,` attribute-value |
| `>` `:` ( tensor-type | vector-type ) |
| ``` |
| |
| A sparse elements attribute is an elements attribute that represents a sparse |
| vector or tensor object. This is where very few of the elements are non-zero. |
| |
| The attribute uses COO (coordinate list) encoding to represent the sparse |
| elements of the elements attribute. The indices are stored via a 2-D tensor of |
| 64-bit integer elements with shape [N, ndims], which specifies the indices of |
| the elements in the sparse tensor that contains non-zero values. The element |
| values are stored via a 1-D tensor with shape [N], that supplies the |
| corresponding values for the indices. |
| |
| Example: |
| |
| ```mlir |
| sparse<[[0, 0], [1, 2]], [1, 5]> : tensor<3x4xi32> |
| |
| // This represents the following tensor: |
| /// [[1, 0, 0, 0], |
| /// [0, 0, 5, 0], |
| /// [0, 0, 0, 0]] |
| ``` |
| |
| #### Float Attribute |
| |
| Syntax: |
| |
| ``` |
| float-attribute ::= (float-literal (`:` float-type)?) |
| | (hexadecimal-literal `:` float-type) |
| ``` |
| |
| A float attribute is a literal attribute that represents a floating point value |
| of the specified [float type](#floating-point-types). It can be represented in |
| the hexadecimal form where the hexadecimal value is interpreted as bits of the |
| underlying binary representation. This form is useful for representing infinity |
| and NaN floating point values. To avoid confusion with integer attributes, |
| hexadecimal literals _must_ be followed by a float type to define a float |
| attribute. |
| |
| Examples: |
| |
| ``` |
| 42.0 // float attribute defaults to f64 type |
| 42.0 : f32 // float attribute of f32 type |
| 0x7C00 : f16 // positive infinity |
| 0x7CFF : f16 // NaN (one of possible values) |
| 42 : f32 // Error: expected integer type |
| ``` |
| |
| #### Integer Attribute |
| |
| Syntax: |
| |
| ``` |
| integer-attribute ::= integer-literal ( `:` (index-type | integer-type) )? |
| ``` |
| |
| An integer attribute is a literal attribute that represents an integral value of |
| the specified integer or index type. The default type for this attribute, if one |
| is not specified, is a 64-bit integer. |
| |
| ##### Integer Set Attribute |
| |
| Syntax: |
| |
| ``` |
| integer-set-attribute ::= `affine_set` `<` integer-set `>` |
| ``` |
| |
| An integer-set attribute is an attribute that represents an integer-set object. |
| |
| #### String Attribute |
| |
| Syntax: |
| |
| ``` |
| string-attribute ::= string-literal (`:` type)? |
| ``` |
| |
| A string attribute is an attribute that represents a string literal value. |
| |
| #### Symbol Reference Attribute |
| |
| Syntax: |
| |
| ``` |
| symbol-ref-attribute ::= symbol-ref-id (`::` symbol-ref-id)* |
| ``` |
| |
| A symbol reference attribute is a literal attribute that represents a named |
| reference to an operation that is nested within an operation with the |
| `OpTrait::SymbolTable` trait. As such, this reference is given meaning by the |
| nearest parent operation containing the `OpTrait::SymbolTable` trait. It may |
| optionally contain a set of nested references that further resolve to a symbol |
| nested within a different symbol table. |
| |
| This attribute can only be held internally by |
| [array attributes](#array-attribute) and |
| [dictionary attributes](#dictionary-attribute)(including the top-level operation |
| attribute dictionary), i.e. no other attribute kinds such as Locations or |
| extended attribute kinds. |
| |
| **Rationale:** Identifying accesses to global data is critical to |
| enabling efficient multi-threaded compilation. Restricting global |
| data access to occur through symbols and limiting the places that can |
| legally hold a symbol reference simplifies reasoning about these data |
| accesses. |
| |
| See [`Symbols And SymbolTables`](SymbolsAndSymbolTables.md) for more |
| information. |
| |
| #### Type Attribute |
| |
| Syntax: |
| |
| ``` |
| type-attribute ::= type |
| ``` |
| |
| A type attribute is an attribute that represents a [type object](#type-system). |
| |
| #### Unit Attribute |
| |
| ``` |
| unit-attribute ::= `unit` |
| ``` |
| |
| A unit attribute is an attribute that represents a value of `unit` type. The |
| `unit` type allows only one value forming a singleton set. This attribute value |
| is used to represent attributes that only have meaning from their existence. |
| |
| One example of such an attribute could be the `swift.self` attribute. This |
| attribute indicates that a function parameter is the self/context parameter. It |
| could be represented as a [boolean attribute](#boolean-attribute)(true or |
| false), but a value of false doesn't really bring any value. The parameter |
| either is the self/context or it isn't. |
| |
| ```mlir |
| // A unit attribute defined with the `unit` value specifier. |
| func @verbose_form(i1) attributes {dialectName.unitAttr = unit} |
| |
| // A unit attribute can also be defined without the value specifier. |
| func @simple_form(i1) attributes {dialectName.unitAttr} |
| ``` |