MLIR

Multi-Level IR Compiler Framework

Builtin Dialect

The builtin dialect contains a core set of Attributes, Operations, and Types that have wide applicability across a very large number of domains and abstractions. Many of the components of this dialect are also instrumental in the implementation of the core IR. As such, this dialect is implicitly loaded in every MLIRContext, and available directly to all users of MLIR.

Given the far-reaching nature of this dialect and the fact that MLIR is extensible by design, any potential additions are heavily scrutinized.

Attributes 

AffineMapAttr 

An Attribute containing an AffineMap object

Syntax:

affine-map-attribute ::= `affine_map` `<` affine-map `>`

Examples:

affine_map<(d0) -> (d0)>
affine_map<(d0, d1, d2) -> (d0, d1)>

Parameters: 

ParameterC++ typeDescription
valueAffineMap

ArrayAttr 

A collection of other Attribute values

Syntax:

array-attribute ::= `[` (attribute-value (`,` attribute-value)*)? `]`

An array attribute is an attribute that represents a collection of attribute values.

Examples:

[]
[10, i32]
[affine_map<(d0, d1, d2) -> (d0, d1)>, i32, "string attribute"]

Parameters: 

ParameterC++ typeDescription
value::llvm::ArrayRef<Attribute>

DenseIntOrFPElementsAttr 

An Attribute containing a dense multi-dimensional array of integer or floating-point values

Syntax:

dense-intorfloat-elements-attribute ::= `dense` `<` attribute-value `>` `:`
                                        ( tensor-type | vector-type )

A dense int-or-float elements attribute is an elements attribute containing a densely packed vector or tensor of integer or floating-point values. The element type of this attribute is required to be either an IntegerType or a FloatType.

Examples:

// A splat tensor of integer values.
dense<10> : tensor<2xi32>
// A tensor of 2 float32 elements.
dense<[10.0, 11.0]> : tensor<2xf32>

Parameters: 

ParameterC++ typeDescription
typeShapedType
rawDataArrayRef<char>

DenseStringElementsAttr 

An Attribute containing a dense multi-dimensional array of strings

Syntax:

dense-string-elements-attribute ::= `dense` `<` attribute-value `>` `:`
                                    ( tensor-type | vector-type )

A dense string elements attribute is an elements attribute containing a densely packed vector or tensor of string values. There are no restrictions placed on the element type of this attribute, enabling the use of dialect specific string types.

Examples:

// A splat tensor of strings.
dense<"example"> : tensor<2x!foo.string>
// A tensor of 2 string elements.
dense<["example1", "example2"]> : tensor<2x!foo.string>

Parameters: 

ParameterC++ typeDescription
typeShapedType
valueArrayRef<StringRef>

DictionaryAttr 

An dictionary of named Attribute values

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.

Examples:

{}
{attr_name = "string attribute"}
{int_attr = 10, "string attr name" = "string attribute"}

Parameters: 

ParameterC++ typeDescription
value::llvm::ArrayRef<NamedAttribute>

FloatAttr 

An Attribute containing a floating-point value

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 . 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

Parameters: 

ParameterC++ typeDescription
type::mlir::Type
value::llvm::APFloat

IntegerAttr 

An Attribute containing a integer value

Syntax:

integer-attribute ::= (integer-literal ( `:` (index-type | integer-type) )?)
                      | `true` | `false`

An integer attribute is a literal attribute that represents an integral value of the specified integer or index type. i1 integer attributes are treated as boolean attributes, and use a unique assembly format of either true or false depending on the value. The default type for non-boolean integer attributes, if a type is not specified, is signless 64-bit integer.

Examples:

10 : i32
10    // : i64 is implied here.
true  // A bool, i.e. i1, value.
false // A bool, i.e. i1, value.

Parameters: 

ParameterC++ typeDescription
type::mlir::Type
valueAPInt

IntegerSetAttr 

An Attribute containing an IntegerSet object

Syntax:

integer-set-attribute ::= `affine_set` `<` integer-set `>`

Examples:

affine_set<(d0) : (d0 - 2 >= 0)>

Parameters: 

ParameterC++ typeDescription
valueIntegerSet

OpaqueAttr 

An opaque representation of another Attribute

Syntax:

opaque-attribute ::= dialect-namespace `<` attr-data `>`

Opaque attributes represent attributes of non-registered dialects. These are attribute represented in their raw string form, and can only usefully be tested for attribute equality.

Examples:

#dialect<"opaque attribute data">

Parameters: 

ParameterC++ typeDescription
dialectNamespaceStringAttr
attrData::llvm::StringRef
type::mlir::Type

OpaqueElementsAttr 

An opaque representation of a multi-dimensional array

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.

Examples:

opaque<"foo_dialect", "0xDEADBEEF"> : tensor<10xi32>

Parameters: 

ParameterC++ typeDescription
dialectStringAttr
value::llvm::StringRef
typeShapedType

SparseElementsAttr 

An opaque representation of a multi-dimensional array

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:

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]]

Parameters: 

ParameterC++ typeDescription
typeShapedType
indicesDenseIntElementsAttr
valuesDenseElementsAttr

StringAttr 

An Attribute containing a string

Syntax:

string-attribute ::= string-literal (`:` type)?

A string attribute is an attribute that represents a string literal value.

Examples:

"An important string"
"string with a type" : !dialect.string

Parameters: 

ParameterC++ typeDescription
value::llvm::StringRef
type::mlir::Type

SymbolRefAttr 

An Attribute containing a symbolic reference to an Operation

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 , dictionary attributes (including the top-level operation attribute dictionary) as well as attributes exposing it via the SubElementAttrInterface interface. Symbol reference attributes nested in types are currently not supported.

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 for more information.

Examples:

@flat_reference
@parent_reference::@nested_reference

Parameters: 

ParameterC++ typeDescription
rootReferenceStringAttr
nestedReferences::llvm::ArrayRef<FlatSymbolRefAttr>

TypeAttr 

An Attribute containing a Type

Syntax:

type-attribute ::= type

A type attribute is an attribute that represents a type object .

Examples:

i32
!dialect.type

Parameters: 

ParameterC++ typeDescription
valueType

UnitAttr 

An Attribute value of unit type

Syntax:

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 (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.

Examples:

// A unit attribute defined with the `unit` value specifier.
func @verbose_form() attributes {dialectName.unitAttr = unit}

// A unit attribute in an attribute dictionary can also be defined without
// the value specifier.
func @simple_form() attributes {dialectName.unitAttr}

Location Attributes 

A subset of the builtin attribute values correspond to source locations , that may be attached to Operations.

CallSiteLoc 

A callsite source location

Syntax:

callsite-location ::= `callsite` `(` location `at` location `)`

An instance of this location allows for representing a directed stack of location usages. This connects a location of a callee with the location of a caller.

Example:

loc(callsite("foo" at "mysource.cc":10:8))

Parameters: 

ParameterC++ typeDescription
calleeLocation
callerLocation

FileLineColLoc 

A file:line:column source location

Syntax:

filelinecol-location ::= string-literal `:` integer-literal `:`
                         integer-literal

An instance of this location represents a tuple of file, line number, and column number. This is similar to the type of location that you get from most source languages.

Example:

loc("mysource.cc":10:8)

Parameters: 

ParameterC++ typeDescription
filenameStringAttr
lineunsigned
columnunsigned

FusedLoc 

A tuple of other source locations

Syntax:

fused-location ::= `fused` fusion-metadata? `[` location (location `,`)* `]`
fusion-metadata ::= `<` attribute-value `>`

An instance of a fused location represents a grouping of several other source locations, with optional metadata that describes the context of the fusion. There are many places within a compiler in which several constructs may be fused together, e.g. pattern rewriting, that normally result partial or even total loss of location information. With fused locations, this is a non-issue.

Example:

loc(fused["mysource.cc":10:8, "mysource.cc":22:8)
loc(fused<"CSE">["mysource.cc":10:8, "mysource.cc":22:8])

Parameters: 

ParameterC++ typeDescription
locations::llvm::ArrayRef<Location>
metadataAttribute

NameLoc 

A named source location

Syntax:

name-location ::= string-literal (`(` location `)`)?

An instance of this location allows for attaching a name to a child location. This can be useful for representing the locations of variable, or node, definitions.

Example:

loc("CSE"("mysource.cc":10:8))

Parameters: 

ParameterC++ typeDescription
nameStringAttr
childLocLocation

OpaqueLoc 

An opaque source location

An instance of this location essentially contains a pointer to some data structure that is external to MLIR and an optional location that can be used if the first one is not suitable. Since it contains an external structure, only the optional location is used during serialization.

Parameters: 

ParameterC++ typeDescription
underlyingLocationuintptr_t
underlyingTypeIDTypeID
fallbackLocationLocation

UnknownLoc 

An unspecified source location

Syntax:

unknown-location ::= `?`

Source location information is an extremely integral part of the MLIR infrastructure. As such, location information is always present in the IR, and must explicitly be set to unknown. Thus, an instance of the unknown location represents an unspecified source location.

Example:

loc(?)

Operations 

builtin.func (::mlir::FuncOp) 

An operation with a name containing a single SSACFG region

Operations within the function cannot implicitly capture values defined outside of the function, i.e. Functions are 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). 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.

Example:

// 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}

Traits: AffineScope, AutomaticAllocationScope, FunctionLike, IsolatedFromAbove

Interfaces: CallableOpInterface, Symbol

Attributes: 

AttributeMLIR TypeDescription
sym_name::mlir::StringAttrstring attribute
type::mlir::TypeAttrany type attribute
sym_visibility::mlir::StringAttrstring attribute

builtin.module (::mlir::ModuleOp) 

A top level container operation

Syntax:

operation ::= `builtin.module` ($sym_name^)? attr-dict-with-keyword $body

A module represents a top-level container operation. It contains a single graph region containing a single block which can contain any operations and does not have a terminator. Operations within this region cannot implicitly capture values defined outside the module, i.e. Modules are IsolatedFromAbove . Modules have an optional symbol name which can be used to refer to them in operations.

Example:

module {
  func @foo()
}

Traits: AffineScope, HasOnlyGraphRegion, IsolatedFromAbove, NoRegionArguments, NoTerminator, SingleBlock, SymbolTable

Interfaces: OpAsmOpInterface, RegionKindInterface, Symbol

Attributes: 

AttributeMLIR TypeDescription
sym_name::mlir::StringAttrstring attribute
sym_visibility::mlir::StringAttrstring attribute

builtin.unrealized_conversion_cast (::mlir::UnrealizedConversionCastOp) 

An unrealized conversion from one set of types to another

Syntax:

operation ::= `builtin.unrealized_conversion_cast` ($inputs^ `:` type($inputs))? `to` type($outputs) attr-dict

An unrealized_conversion_cast operation represents an unrealized conversion from one set of types to another, that is used to enable the inter-mixing of different type systems. This operation should not be attributed any special representational or execution semantics, and is generally only intended to be used to satisfy the temporary intermixing of type systems during the conversion of one type system to another.

This operation may produce results of arity 1-N, and accept as input operands of arity 0-N.

Example:

// An unrealized 0-1 conversion. These types of conversions are useful in
// cases where a type is removed from the type system, but not all uses have
// been converted. For example, imagine we have a tuple type that is
// expanded to its element types. If only some uses of an empty tuple type
// instance are converted we still need an instance of the tuple type, but
// have no inputs to the unrealized conversion.
%result = unrealized_conversion_cast to !bar.tuple_type<>

// An unrealized 1-1 conversion.
%result1 = unrealized_conversion_cast %operand : !foo.type to !bar.lowered_type

// An unrealized 1-N conversion.
%results2:2 = unrealized_conversion_cast %tuple_operand : !foo.tuple_type<!foo.type, !foo.type> to !foo.type, !foo.type

// An unrealized N-1 conversion.
%result3 = unrealized_conversion_cast %operand, %operand : !foo.type, !foo.type to !bar.tuple_type<!foo.type, !foo.type>

Interfaces: CastOpInterface, NoSideEffect (MemoryEffectOpInterface)

Effects: MemoryEffects::Effect{}

Operands: 

OperandDescription
inputsany type

Results: 

ResultDescription
outputsany type

Types 

BFloat16Type 

bfloat16 floating-point type

ComplexType 

Complex number with a parameterized element 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:

complex<f32>
complex<i32>

Parameters: 

ParameterC++ typeDescription
elementTypeType

Float128Type 

128-bit floating-point type

Float16Type 

16-bit floating-point type

Float32Type 

32-bit floating-point type

Float64Type 

64-bit floating-point type

Float80Type 

80-bit floating-point type

FunctionType 

Map from a list of inputs to a list of results

Syntax:

// Function types may have multiple results.
function-result-type ::= type-list-parens | non-function-type
function-type ::= type-list-parens `->` function-result-type

The function type can be thought of as a function signature. It consists of a list of formal parameter types and a list of formal result types.

Parameters: 

ParameterC++ typeDescription
inputsArrayRef<Type>
resultsArrayRef<Type>

IndexType 

Integer-like type with unknown platform-dependent bit width

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 ) and is used by the affine constructs in MLIR.

Rationale: integers of platform-specific bit widths are practical to express sizes, dimensionalities and subscripts.

IntegerType 

Integer type with arbitrary precision up to a fixed limit

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

Integer types have a designated bit width and may optionally 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).

Parameters: 

ParameterC++ typeDescription
widthunsigned
signednessSignednessSemantics

MemRefType 

Shaped reference to a region of memory

Syntax:

memref-type ::= `memref` `<` dimension-list-ranked type
                (`,` layout-specification)? (`,` memory-space)? `>`

stride-list ::= `[` (dimension (`,` dimension)*)? `]`
strided-layout ::= `offset:` dimension `,` `strides: ` stride-list
layout-specification ::= semi-affine-map | strided-layout | attribute-value
memory-space ::= attribute-value

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.

Are accepted as elements:

  • built-in integer types;
  • built-in index type;
  • built-in floating point types;
  • built-in vector types with elements of the above types;
  • another memref type;
  • any other type implementing MemRefElementTypeInterface.
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:

// 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 . 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 attribute. It might be an integer value, string, dictionary or custom dialect attribute. The empty memory space (attribute is None) is target specific.

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

// 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

// 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 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.

Index Map Example

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 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:

// 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)
Strided MemRef 

A memref may specify a strided layout 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 with a single semi-affine map.

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 inner 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.

Parameters: 

ParameterC++ typeDescription
shape::llvm::ArrayRef<int64_t>
elementTypeType
layoutMemRefLayoutAttrInterface
memorySpaceAttribute

NoneType 

A unit type

NoneType is a unit type, i.e. a type with exactly one possible value, where its value does not have a defined dynamic representation.

OpaqueType 

Type of a non-registered dialect

Syntax:

opaque-type ::= `opaque` `<` type `>`

Opaque types represent types of non-registered dialects. These are types represented in their raw string form, and can only usefully be tested for type equality.

Examples:

opaque<"llvm", "struct<(i32, float)>">
opaque<"pdl", "value">

Parameters: 

ParameterC++ typeDescription
dialectNamespaceStringAttr
typeData::llvm::StringRef

RankedTensorType 

Multi-dimensional array with a fixed number of dimensions

Syntax:

tensor-type ::= `tensor` `<` dimension-list type (`,` encoding)? `>`
dimension-list ::= (dimension `x`)*
dimension ::= `?` | decimal-literal
encoding ::= attribute-value

Values with tensor type represents aggregate N-dimensional data values, and have a known element type and 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 . 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 returns the size of a dimension from a value of tensor type.

The encoding attribute provides additional information on the tensor. An empty attribute denotes a straightforward tensor without any specific structure. But particular properties, like sparsity or other specific characteristics of the data of the tensor can be encoded through this attribute. The semantics are defined by a type and attribute interface and must be respected by all passes that operate on tensor types. TODO: provide this interface, and document it further.

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:

// 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>

// Tensor with an encoding attribute (where #ENCODING is a named alias).
tensor<?x?xf64, #ENCODING>

Parameters: 

ParameterC++ typeDescription
shape::llvm::ArrayRef<int64_t>
elementTypeType
encodingAttribute

TupleType 

Fixed-sized collection of other types

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 ).

Examples:

// Empty tuple.
tuple<>

// Single element
tuple<f32>

// Many elements.
tuple<i32, f32, tensor<i1>, i5>

Parameters: 

ParameterC++ typeDescription
typesArrayRef<Type>

UnrankedMemRefType 

Shaped reference, with unknown rank, to a region of memory

Syntax:

unranked-memref-type ::= `memref` `<*x` type (`,` memory-space)? `>`
memory-space ::= attribute-value

A memref type with 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.

See MemRefType for more information on memref types.

Examples:

memref<*f32>

// An unranked memref with a memory space of 10.
memref<*f32, 10>

Parameters: 

ParameterC++ typeDescription
elementTypeType
memorySpaceAttribute

UnrankedTensorType 

Multi-dimensional array with unknown dimensions

Syntax:

tensor-type ::= `tensor` `<` `*` `x` type `>`

An unranked tensor is a type of tensor in which the set of dimensions have unknown rank. See RankedTensorType for more information on tensor types.

Examples:

tensor<*f32>

Parameters: 

ParameterC++ typeDescription
elementTypeType

VectorType 

Multi-dimensional SIMD vector type

Syntax:

vector-type ::= `vector` `<` static-dimension-list vector-element-type `>`
vector-element-type ::= float-type | integer-type | index-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. 0D vectors are allowed by omitting the dimension: vector<f32>.

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.

Examples:

vector<3x42xi32>

Parameters: 

ParameterC++ typeDescription
shape::llvm::ArrayRef<int64_t>
elementTypeType

Type Interfaces 

MemRefElementTypeInterface (MemRefElementTypeInterface

Indication that this type can be used as element in memref types.

Implementing this interface establishes a contract between this type and the memref type indicating that this type can be used as element of ranked or unranked memrefs. The type is expected to:

  • model an entity stored in memory;
  • have non-zero size.

For example, scalar values such as integers can implement this interface, but indicator types such as void or unit should not.

The interface currently has no methods and is used by types to opt into being memref elements. This may change in the future, in particular to require types to provide their size or alignment given a data layout.

Methods: 

SubElementTypeInterface (SubElementTypeInterface

An interface used to query and manipulate sub-elements, such as sub-types and sub-attributes of a composite type.

Methods: 

walkImmediateSubElements 

void walkImmediateSubElements(llvm::function_ref<void(mlir::Attribute)> walkAttrsFn, llvm::function_ref<void(mlir::Type)> walkTypesFn);

Walk all of the immediately nested sub-attributes and sub-types. This method does not recurse into sub elements.

NOTE: This method must be implemented by the user.

replaceImmediateSubAttribute 

::mlir::SubElementTypeInterface replaceImmediateSubAttribute(::llvm::ArrayRef<std::pair<size_t, ::mlir::Attribute>> replacements);

Replace the attributes identified by the indices with the corresponding value. The index is derived from the order of the attributes returned by the attribute callback of walkImmediateSubElements. An index of 0 would replace the very first attribute given by walkImmediateSubElements. The new instance with the values replaced is returned.

NOTE: This method must be implemented by the user.

SubElementTypeInterface (anonymous_282

An interface used to query and manipulate sub-elements, such as sub-types and sub-attributes of a composite type.

Methods: 

walkImmediateSubElements 

void walkImmediateSubElements(llvm::function_ref<void(mlir::Attribute)> walkAttrsFn, llvm::function_ref<void(mlir::Type)> walkTypesFn);

Walk all of the immediately nested sub-attributes and sub-types. This method does not recurse into sub elements.

NOTE: This method must be implemented by the user.

replaceImmediateSubAttribute 

::mlir::SubElementTypeInterface replaceImmediateSubAttribute(::llvm::ArrayRef<std::pair<size_t, ::mlir::Attribute>> replacements);

Replace the attributes identified by the indices with the corresponding value. The index is derived from the order of the attributes returned by the attribute callback of walkImmediateSubElements. An index of 0 would replace the very first attribute given by walkImmediateSubElements. The new instance with the values replaced is returned.

NOTE: This method must be implemented by the user.