diff options
Diffstat (limited to 'mlir/docs/LangRef.md')
-rw-r--r-- | mlir/docs/LangRef.md | 1497 |
1 files changed, 1497 insertions, 0 deletions
diff --git a/mlir/docs/LangRef.md b/mlir/docs/LangRef.md new file mode 100644 index 00000000000..da60b8b892e --- /dev/null +++ b/mlir/docs/LangRef.md @@ -0,0 +1,1497 @@ +# MLIR Specification + +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.md), +[glossary](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 an +[SSA-based](https://en.wikipedia.org/wiki/Static_single_assignment_form) IR, +which means that values are defined before use and have scope defined by their +dominance relations. Operations may produce zero or more results, and each is a +distinct SSA value with its own type defined by the [type system](#type-system). + +The unit of code in MLIR is an [Operation](#operations). Operations allow for +representing many different concepts: allocating buffers, producing views to +transform them, target-independent arithmetic, target-specific operations, and +even arbitrary user-defined high-level operations including the +[Module](#module) and [Function](#functions) operations. Operations may contain +[Regions](#regions) that represent a Control Flow Graph (CFG) of +[Blocks](#blocks), that contain operations and end with a +[terminator operation](#terminator-operations) (like branches). + +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)* +ssa-id ::= `%` suffix-id +suffix-id ::= (digit+ | ((letter|id-punct) (letter|id-punct|digit)*)) + +symbol-ref-id ::= `@` (suffix-id | string-literal) +ssa-id-list ::= ssa-id (`,` ssa-id)* + +// Uses of an SSA value, e.g. in an operand list to an operation. +ssa-use ::= ssa-id +ssa-use-list ::= ssa-use (`,` ssa-use)* +``` + +Identifiers name entities such as SSA 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 SSA 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. + +The scope of SSA values is defined based on the standard definition of +[dominance](https://en.wikipedia.org/wiki/Dominator_\(graph_theory\)). Argument +identifiers in mapping functions are in scope for the mapping body. Function +identifiers and mapping identifiers are visible across the entire module. + +## 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 '(' ssa-use-list? ')' attribute-dict? + `:` function-type +custom-operation ::= bare-id custom-operation-format +op-result-list ::= op-result (`,` op-result)* `=` +op-result ::= ssa-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 SSA +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 an opaque top-level container operation. It contains a +single region containing a single block that is comprised of any operations. +Operations within this region must not implicitly capture values defined above +it. Modules have an optional symbol name that can be used to refer to them in +operations. + +### Functions + +An MLIR Function is an operation with a name containing one [region](#regions). +The region of a function is not allowed to implicitly capture values defined +outside of the function, and 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 ::= ssa-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 +ssa-id-and-type ::= ssa-id `:` type + +// Non-empty list of names and types. +ssa-id-and-type-list ::= ssa-id-and-type (`,` ssa-id-and-type)* + +block-arg-list ::= `(` ssa-id-and-type-list? `)` +``` + +A [block](https://en.wikipedia.org/wiki/Basic_block) is a sequential list of +operations without control flow (calls are not considered control flow for this +purpose) that are executed from top to bottom. The last operation in a block is +a [terminator operation](#terminator-operations), which ends the block. + +Blocks in MLIR take a list of block arguments, which represent SSA PHI nodes in +a functional notation. The arguments are defined by the block, and values are +provided for these block arguments by branches that go to the block. + +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 twice. +^bb3(%c: i64): + br ^bb4(%c, %c : i64, i64) + +^bb4(%d : i64, %e : i64): + %0 = addi %d, %e : i64 + return %0 : i64 +} +``` + +**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.md#block-arguments-vs-phi-nodes)]. + +## Regions + +### Definition + +A region is a CFG of MLIR [Blocks](#blocks). Regions serve to group semantically +connected blocks, where the semantics is not imposed by the IR. Instead, the +containing operation defines the semantics of the regions it contains. Regions +do not have a name or an address, only the blocks contained in a region do. +Regions are meaningless outside of the containing entity and have no type or +attributes. + +The first block in the region cannot be a successor of any other block. The +syntax for the region is as follows: + +``` +region ::= `{` block* `}` +``` + +The 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 +(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). + +### Control and Value Scoping + +Regions provide nested control isolation: it is impossible to branch to a block +within a region from outside it or to branch from within a region to a block +outside it. Similarly, it provides a natural scoping for value visibility: SSA +values defined in a region don't escape to the enclosing region, if any. By +default, a region can reference values defined outside of the region whenever it +would have been legal to use them as operands to the enclosing operation. + +Example: + +```mlir +func @accelerator_compute(i64, i1) -> i64 { +^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"() { + ^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: + ... +} +``` + +This can be further restricted using the custom verifier associated with the +enclosing operation, for example, disallowing references to values defined +outside the region completely. + +### Control Flow + +Regions are Single-Entry-Multiple-Exit (SEME). This means that control can only +flow into the first block of the region, but can flow out of the region at the +end of any of the contained blocks (This behavior is similar to that of a +function body in most programming languages). When exiting a Region, control is +returned to the enclosing operation. + +The enclosing operation determines the way in which control is transmitted into +the entry block of a Region. The successor to a region’s exit points may not +necessarily exist: for example a call to a function that does not return. +Concurrent or asynchronous execution of regions is unspecified. Operations may +define specific rules of execution, e.g. sequential loops or switch cases. + +A Region may also enter another region within the enclosing operation. If an +operation has multiple regions, the semantics of the operation defines into +which regions the control flows and in which order, if any. An operation may +transmit control into regions that were specified in other operations, in +particular those that defined the values the given operation uses. Thus such +operations can be treated opaquely in the enclosing control flow graph, +providing a level of control flow isolation similar to that of the call +operation. + +#### 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. + +### 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 SSA 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 [standard types](#standard-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 | standard-type + +type-list-no-parens ::= type (`,` type)* +type-list-parens ::= `(` `)` + | `(` type-list-no-parens `)` + +// This is a common way to refer to an SSA 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](DefiningAttributesAndTypes.md) to learn how to define dialect types. + +### Standard Types + +Standard 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. + +``` +standard-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` +``` + +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#constant-operation) produces the +address of a function as an SSA value. This SSA 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.md#signless-types)) and is +used by the affine constructs in MLIR. Unlike fixed-size integers, it cannot be +used as an element of vector, tensor or memref type +([rationale](Rationale.md#index-type-disallowed-in-vectortensormemref-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. +integer-type ::= `i` [1-9][0-9]* +``` + +MLIR supports arbitrary precision integer types. Integer types are signless, but +have a designated width. + +**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.md#quantized-integer-operations)). + +#### Memref Type + +Syntax: + +``` +memref-type ::= ranked-memref-type | unranked-memref-type + +ranked-memref-type ::= `memref` `<` dimension-list-ranked tensor-memref-element-type + (`,` layout-specification)? | + (`,` memory-space)? `>` + +unranked-memref-type ::= `memref` `<*x` tensor-memref-element-type + (`,` memory-space)? `>` + +stride-list ::= `[` (dimension (`,` dimension)*)? `]` +strided-layout ::= `offset:` dimension `,` `strides: ` stride-list +layout-specification ::= semi-affine-map | 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 = (d0, d1) -> (d0, d1) + +// Column major layout. +#col_major = (d0, d1, d2) -> (d2, d1, d0) + +// A 2-d tiled layout with tiles of size 128 x 256. +#tiled_2d_128x256 = (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 = (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 = (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, memspace0> + +// 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, memspace0> + +// 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 * 68 float elements of data. +%P = alloc() : memref<16x64xf32, #padded> + +// Affine map with symbol 's0' used as offset for the first dimension. +#imapS = (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, memspace0> + +// Loads data from memref '%A' using a 2D index: (%i, %j) +%v = load %A[%i, %j] : memref<16x32xf32, #imapA, memspace0> +``` + +##### 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 tensor-memref-element-type `>` +tensor-memref-element-type ::= vector-element-type | vector-type | complex-type + +// memref requires a known rank, but tensor does not. +dimension-list ::= dimension-list-ranked | (`*` `x`) +dimension-list-ranked ::= (dimension `x`)* +dimension ::= `?` | decimal-literal +``` + +SSA values of 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.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|[_$])* +``` + +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#dim-operation), 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 | standard-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 = (d0) -> (d0 + 10) + +// Using the original attribute. +%b = affine.apply (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 attributes values are distinguished with a leading '#', +while dialect types are distinguished with a leading '!'. + +``` +dialect-attribute ::= '#' opaque-dialect-item +dialect-attribute ::= '#' pretty-dialect-item +``` + +Dialect attributes can be specified in a verbose form, e.g. like this: + +```mlir +// Complex attribute +#foo<"something<abcd>"> + +// Even more complex attribute +#foo<"something<a%%123^^^>>>"> +``` + +Dialect attributes 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 attributes 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](DefiningAttributesAndTypes.md) to learn how to define dialect +attribute values. + +### Standard Attribute Values + +Standard 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. + +``` +standard-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 +``` + +An affine-map attribute is an attribute that represents a 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 packed to the element bitwidth. The +element type of the vector or tensor constant must be of integer, index, or +floating point 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-map +``` + +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. If a reference to a symbol is necessary from outside +of the symbol table that the symbol is defined in, a +[string attribute](string-attribute) can be used to refer to the symbol name. + +**Rationale:** Given that MLIR models global accesses with symbol references, to +enable efficient multi-threading, it becomes difficult to effectively reason +about their uses. By restricting the places that can legally hold a symbol +reference, we can always opaquely reason about a symbols usage characteristics. + +#### 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} +``` |