llvm/mlir/include/mlir/IR/BuiltinTypes.td

//===- BuiltinTypes.td - Builtin type definitions ----------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the set of builtin MLIR types, or the set of types necessary for the
// validity of and defining the IR.
//
//===----------------------------------------------------------------------===//

#ifndef BUILTIN_TYPES
#define BUILTIN_TYPES

include "mlir/IR/AttrTypeBase.td"
include "mlir/IR/BuiltinDialect.td"
include "mlir/IR/BuiltinTypeInterfaces.td"
include "mlir/IR/CommonTypeConstraints.td"

// TODO: Currently the types defined in this file are prefixed with `Builtin_`.
// This is to differentiate the types here with the ones in OpBase.td. We should
// remove the definitions in OpBase.td, and repoint users to this file instead.

// Base class for Builtin dialect types.
class Builtin_Type<string name, string typeMnemonic, list<Trait> traits = [],
                   string baseCppClass = "::mlir::Type">
    : TypeDef<Builtin_Dialect, name, traits, baseCppClass> {
  let mnemonic = ?;
  let typeName = "builtin." # typeMnemonic;
}

//===----------------------------------------------------------------------===//
// Traits
//===----------------------------------------------------------------------===//

/// Type trait indicating that the type has value semantics.
def ValueSemantics : NativeTypeTrait<"ValueSemantics"> {
  let cppNamespace = "::mlir";
}

//===----------------------------------------------------------------------===//
// ComplexType
//===----------------------------------------------------------------------===//

def Builtin_Complex : Builtin_Type<"Complex", "complex"> {
  let summary = "Complex number with a parameterized element type";
  let description = [{
    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.

    #### Example:

    ```mlir
    complex<f32>
    complex<i32>
    ```
  }];
  let parameters = (ins "Type":$elementType);
  let builders = [
    TypeBuilderWithInferredContext<(ins "Type":$elementType), [{
      return $_get(elementType.getContext(), elementType);
    }]>
  ];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// FloatType
//===----------------------------------------------------------------------===//

// Base class for Builtin dialect float types.
class Builtin_FloatType<string name, string mnemonic>
    : Builtin_Type<name, mnemonic, /*traits=*/[], "::mlir::FloatType"> {
  let extraClassDeclaration = [{
    static }] # name # [{Type get(MLIRContext *context);
  }];
}

//===----------------------------------------------------------------------===//
// Float8E5M2Type

def Builtin_Float8E5M2 : Builtin_FloatType<"Float8E5M2", "f8E5M2"> {
  let summary = "8-bit floating point with 2 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 5 bits exponent and 2 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E5M2
      * exponent bias: 15
      * infinities: supported with exponent set to all 1s and mantissa 0s
      * NaNs: supported with exponent bits set to all 1s and mantissa of
        (01, 10, or 11)
      * denormals when exponent is 0

    Described in: https://arxiv.org/abs/2209.05433
  }];
}

//===----------------------------------------------------------------------===//
// Float8E4M3Type

def Builtin_Float8E4M3 : Builtin_FloatType<"Float8E4M3", "f8E4M3"> {
  let summary = "8-bit floating point with 3 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E4M3
      * exponent bias: 7
      * infinities: supported with exponent set to all 1s and mantissa 0s
      * NaNs: supported with exponent bits set to all 1s and mantissa of
        (001, 010, 011, 100, 101, 110, 111)
      * denormals when exponent is 0
  }];
}

//===----------------------------------------------------------------------===//
// Float8E4M3FNType

def Builtin_Float8E4M3FN : Builtin_FloatType<"Float8E4M3FN", "f8E4M3FN"> {
  let summary = "8-bit floating point with 3 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it follows
    similar conventions, with the exception that there are no infinity values
    and only two NaN representations. This type has the following
    characteristics:

      * bit encoding: S1E4M3
      * exponent bias: 7
      * infinities: Not supported
      * NaNs: supported with exponent bits and mantissa bits set to all 1s
      * denormals when exponent is 0

    Described in: https://arxiv.org/abs/2209.05433
  }];
}

//===----------------------------------------------------------------------===//
// Float8E5M2FNUZType

def Builtin_Float8E5M2FNUZ : Builtin_FloatType<"Float8E5M2FNUZ", "f8E5M2FNUZ"> {
  let summary = "8-bit floating point with 2 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 5 bits exponent and 2 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it follows
    similar conventions, with the exception that there are no infinity values,
    no negative zero, and only one NaN representation. This type has the
    following characteristics:

      * bit encoding: S1E5M2
      * exponent bias: 16
      * infinities: Not supported
      * NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
      * denormals when exponent is 0

    Described in: https://arxiv.org/abs/2206.02915
  }];
}

//===----------------------------------------------------------------------===//
// Float8E4M3FNUZType

def Builtin_Float8E4M3FNUZ : Builtin_FloatType<"Float8E4M3FNUZ", "f8E4M3FNUZ"> {
  let summary = "8-bit floating point with 3 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it follows
    similar conventions, with the exception that there are no infinity values,
    no negative zero, and only one NaN representation. This type has the
    following characteristics:

      * bit encoding: S1E4M3
      * exponent bias: 8
      * infinities: Not supported
      * NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
      * denormals when exponent is 0

    Described in: https://arxiv.org/abs/2209.05433
  }];
}

//===----------------------------------------------------------------------===//
// Float8E4M3B11FNUZType

def Builtin_Float8E4M3B11FNUZ : Builtin_FloatType<"Float8E4M3B11FNUZ", "f8E4M3B11FNUZ"> {
  let summary = "8-bit floating point with 3 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it follows
    similar conventions, with the exception that there are no infinity values,
    no negative zero, and only one NaN representation. This type has the
    following characteristics:

      * bit encoding: S1E4M3
      * exponent bias: 11
      * infinities: Not supported
      * NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
      * denormals when exponent is 0

    Related to: https://dl.acm.org/doi/10.5555/3454287.3454728
  }];
}

//===----------------------------------------------------------------------===//
// Float8E3M4Type

def Builtin_Float8E3M4 : Builtin_FloatType<"Float8E3M4", "f8E3M4"> {
  let summary = "8-bit floating point with 3 bits exponent and 4 bit mantissa";
  let description = [{
    An 8-bit floating point type with 1 sign bit, 3 bits exponent and 4 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E3M4
      * exponent bias: 3
      * infinities: supported with exponent set to all 1s and mantissa 0s
      * NaNs: supported with exponent bits set to all 1s and mantissa values of
        {0,1}⁴ except S.111.0000
      * denormals when exponent is 0
  }];
}

//===----------------------------------------------------------------------===//
// Float4E2M1FNType

def Builtin_Float4E2M1FN : Builtin_FloatType<"Float4E2M1FN", "f4E2M1FN"> {
  let summary = "4-bit floating point with 2-bit exponent and 1-bit mantissa";
  let description = [{
    An 4-bit floating point type with 1 sign bit, 2 bits exponent and 1 bit
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E2M1
      * exponent bias: 1
      * infinities: Not supported
      * NaNs: Not supported
      * denormals when exponent is 0

    Open Compute Project (OCP) microscaling formats (MX) specification:
    https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
  }];
}

//===----------------------------------------------------------------------===//
// Float6E2M3FNType

def Builtin_Float6E2M3FN : Builtin_FloatType<"Float6E2M3FN", "f6E2M3FN"> {
  let summary = "6-bit floating point with 2-bit exponent and 3-bit mantissa";
  let description = [{
    An 6-bit floating point type with 1 sign bit, 2 bits exponent and 3 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E2M3
      * exponent bias: 1
      * infinities: Not supported
      * NaNs: Not supported
      * denormals when exponent is 0

    Open Compute Project (OCP) microscaling formats (MX) specification:
    https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
  }];
}

//===----------------------------------------------------------------------===//
// Float6E3M2FNType

def Builtin_Float6E3M2FN : Builtin_FloatType<"Float6E3M2FN", "f6E3M2FN"> {
  let summary = "6-bit floating point with 3-bit exponent and 2-bit mantissa";
  let description = [{
    An 6-bit floating point type with 1 sign bit, 3 bits exponent and 2 bits
    mantissa. This is not a standard type as defined by IEEE-754, but it
    follows similar conventions with the following characteristics:

      * bit encoding: S1E3M2
      * exponent bias: 3
      * infinities: Not supported
      * NaNs: Not supported
      * denormals when exponent is 0

    Open Compute Project (OCP) microscaling formats (MX) specification:
    https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
  }];
}

//===----------------------------------------------------------------------===//
// Float8E8M0FNUType

def Builtin_Float8E8M0FNU : Builtin_FloatType<"Float8E8M0FNU", "f8E8M0FNU"> {
  let summary = "8-bit floating point with 8-bit exponent, no mantissa or sign";
  let description = [{
    An 8-bit floating point type with no sign bit, 8 bits exponent and no
    mantissa. This is not a standard type as defined by IEEE-754; it is intended
    to be used for representing scaling factors, so it cannot represent zeros
    and negative numbers. The values it can represent are powers of two in the
    range [-127,127] and NaN.

      * bit encoding: S0E8M0
      * exponent bias: 127
      * infinities: Not supported
      * NaNs: Supported with all bits set to 1
      * denormals: Not supported

    Open Compute Project (OCP) microscaling formats (MX) specification:
    https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
  }];
}

//===----------------------------------------------------------------------===//
// BFloat16Type

def Builtin_BFloat16 : Builtin_FloatType<"BFloat16", "bf16"> {
  let summary = "bfloat16 floating-point type";
}

//===----------------------------------------------------------------------===//
// Float16Type

def Builtin_Float16 : Builtin_FloatType<"Float16", "f16"> {
  let summary = "16-bit floating-point type";
}

//===----------------------------------------------------------------------===//
// FloatTF32Type

def Builtin_FloatTF32 : Builtin_FloatType<"FloatTF32", "tf32"> {
  let summary = "TF32 floating-point type";
}

//===----------------------------------------------------------------------===//
// Float32Type

def Builtin_Float32 : Builtin_FloatType<"Float32", "f32"> {
  let summary = "32-bit floating-point type";
}

//===----------------------------------------------------------------------===//
// Float64Type

def Builtin_Float64 : Builtin_FloatType<"Float64", "f64"> {
  let summary = "64-bit floating-point type";
}

//===----------------------------------------------------------------------===//
// Float80Type

def Builtin_Float80 : Builtin_FloatType<"Float80", "f80"> {
  let summary = "80-bit floating-point type";
}

//===----------------------------------------------------------------------===//
// Float128Type

def Builtin_Float128 : Builtin_FloatType<"Float128", "f128"> {
  let summary = "128-bit floating-point type";
}

//===----------------------------------------------------------------------===//
// FunctionType
//===----------------------------------------------------------------------===//

def Builtin_Function : Builtin_Type<"Function", "function"> {
  let summary = "Map from a list of inputs to a list of results";
  let description = [{
    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.

    #### Example:

    ```mlir
    func.func @add_one(%arg0 : i64) -> i64 {
      %c1 = arith.constant 1 : i64
      %0 = arith.addi %arg0, %c1 : i64
      return %0 : i64
    }
    ```
  }];
  let parameters = (ins "ArrayRef<Type>":$inputs, "ArrayRef<Type>":$results);
  let builders = [
    TypeBuilder<(ins CArg<"TypeRange">:$inputs, CArg<"TypeRange">:$results), [{
      return $_get($_ctxt, inputs, results);
    }]>
  ];
  let skipDefaultBuilders = 1;
  let genStorageClass = 0;
  let extraClassDeclaration = [{
    /// Input types.
    unsigned getNumInputs() const;
    Type getInput(unsigned i) const { return getInputs()[i]; }

    /// Result types.
    unsigned getNumResults() const;
    Type getResult(unsigned i) const { return getResults()[i]; }

    /// Returns a clone of this function type with the given argument
    /// and result types.
    FunctionType clone(TypeRange inputs, TypeRange results) const;

    /// Returns a new function type with the specified arguments and results
    /// inserted.
    FunctionType getWithArgsAndResults(ArrayRef<unsigned> argIndices,
                                       TypeRange argTypes,
                                       ArrayRef<unsigned> resultIndices,
                                       TypeRange resultTypes);

    /// Returns a new function type without the specified arguments and results.
    FunctionType getWithoutArgsAndResults(const BitVector &argIndices,
                                          const BitVector &resultIndices);
  }];
}

//===----------------------------------------------------------------------===//
// IndexType
//===----------------------------------------------------------------------===//

def Builtin_Index : Builtin_Type<"Index", "index"> {
  let summary = "Integer-like type with unknown platform-dependent bit width";
  let description = [{
    Syntax:

    ```
    // Target word-sized integer.
    index-type ::= `index`
    ```

    The index type is a signless integer whose size is equal to the natural
    machine word of the target ( [rationale](../../Rationale/Rationale/#integer-signedness-semantics) )
    and is used by the affine constructs in MLIR.

    **Rationale:** integers of platform-specific bit widths are practical to
    express sizes, dimensionalities and subscripts.
  }];
  let extraClassDeclaration = [{
    static IndexType get(MLIRContext *context);

    /// Storage bit width used for IndexType by internal compiler data
    /// structures.
    static constexpr unsigned kInternalStorageBitWidth = 64;
  }];
}

//===----------------------------------------------------------------------===//
// IntegerType
//===----------------------------------------------------------------------===//

def Builtin_Integer : Builtin_Type<"Integer", "integer"> {
  let summary = "Integer type with arbitrary precision up to a fixed limit";
  let description = [{
    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).
  }];
  let parameters = (ins "unsigned":$width, "SignednessSemantics":$signedness);
  let builders = [
    TypeBuilder<(ins "unsigned":$width,
                     CArg<"SignednessSemantics", "Signless">:$signedness)>
  ];

  // IntegerType uses a special storage class that compacts parameters to save
  // memory.
  let genStorageClass = 0;
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
  let extraClassDeclaration = [{
    /// Signedness semantics.
    enum SignednessSemantics : uint32_t {
      Signless, /// No signedness semantics
      Signed,   /// Signed integer
      Unsigned, /// Unsigned integer
    };

    /// Return true if this is a signless integer type.
    bool isSignless() const { return getSignedness() == Signless; }
    /// Return true if this is a signed integer type.
    bool isSigned() const { return getSignedness() == Signed; }
    /// Return true if this is an unsigned integer type.
    bool isUnsigned() const { return getSignedness() == Unsigned; }

    /// Get or create a new IntegerType with the same signedness as `this` and a
    /// bitwidth scaled by `scale`.
    /// Return null if the scaled element type cannot be represented.
    IntegerType scaleElementBitwidth(unsigned scale);

    /// Integer representation maximal bitwidth.
    /// Note: This is aligned with the maximum width of llvm::IntegerType.
    static constexpr unsigned kMaxWidth = (1 << 24) - 1;
  }];
}

//===----------------------------------------------------------------------===//
// MemRefType
//===----------------------------------------------------------------------===//

def Builtin_MemRef : Builtin_Type<"MemRef", "memref", [
    ShapedTypeInterface
  ], "BaseMemRefType"> {
  let summary = "Shaped reference to a region of memory";
  let description = [{
    Syntax:

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

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

    ##### Layout

    A memref may optionally have a layout that indicates how indices are
    transformed from the multi-dimensional form into a linear address. The
    layout must avoid internal aliasing, i.e., two distinct tuples of
    _in-bounds_ indices must be pointing to different elements in memory. The
    layout is an attribute that implements `MemRefLayoutAttrInterface`. The
    bulitin dialect offers two kinds of layouts: strided and affine map, each
    of which is available as an attribute. Other attributes may be used to
    represent the layout as long as they can be converted to a
    [semi-affine map](Affine.md/#semi-affine-maps) and implement the required
    interface. Users of memref are expected to fallback to the affine
    representation when handling unknown memref layouts. Multi-dimensional
    affine forms are interpreted in _row-major_ fashion.

    In absence of an explicit layout, a memref is considered to have a
    multi-dimensional identity affine map 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 a
    layout, `memref<?x?xf32>`.

    ##### Affine Map Layout

    The layout may be represented directly as an affine map from the index space
    to the storage space. 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](/includes/img/index-map.svg)

    Semi-affine maps are sufficiently flexible to represent a wide variety of
    dense storage layouts, including row- and column-major and tiled:

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

    ##### Strided Layout

    Memref layout can be expressed using strides to encode the distance, in
    number of elements, in (linear) memory between successive entries along a
    particular dimension. For example, a row-major strided layout for
    `memref<2x3x4xf32>` is `strided<[12, 4, 1]>`, where the last dimension is
    contiguous as indicated by the unit stride and the remaining strides are
    products of the sizes of faster-variying dimensions. Strided layout can also
    express non-contiguity, e.g., `memref<2x3, strided<[6, 2]>>` only accesses
    even elements of the dense consecutive storage along the innermost
    dimension.

    The strided layout supports an optional _offset_ that indicates the
    distance, in the number of elements, between the beginning of the memref
    and the first accessed element. When omitted, the offset is considered to
    be zero. That is, `memref<2, strided<[2], offset: 0>>` and
    `memref<2, strided<[2]>>` are strictly the same type.

    Both offsets and strides may be _dynamic_, that is, unknown at compile time.
    This is represented by using a question mark (`?`) instead of the value in
    the textual form of the IR.

    The strided layout converts into the following canonical one-dimensional
    affine form through explicit linearization:

    ```mlir
    affine_map<(d0, ... dN)[offset, stride0, ... strideN] ->
                (offset + d0 * stride0 + ... dN * strideN)>
    ```

    Therefore, it is never subject to the implicit row-major layout
    interpretation.

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

    ```mlir
    // Identity index/layout map
    #identity = affine_map<(d0, d1) -> (d0, d1)>

    // Column major layout.
    #col_major = affine_map<(d0, d1, d2) -> (d2, d1, d0)>

    // A 2-d tiled layout with tiles of size 128 x 256.
    #tiled_2d_128x256 = affine_map<(d0, d1) -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)>

    // A tiled data layout with non-constant tile sizes.
    #tiled_dynamic = affine_map<(d0, d1)[s0, s1] -> (d0 floordiv s0, d1 floordiv s1,
                                 d0 mod s0, d1 mod s1)>

    // A layout that yields a padding on two at either end of the minor dimension.
    #padded = affine_map<(d0, d1) -> (d0, (d1 + 2) floordiv 2, (d1 + 2) mod 2)>


    // The dimension list "16x32" defines the following 2D index space:
    //
    //   { (i, j) : 0 <= i < 16, 0 <= j < 32 }
    //
    memref<16x32xf32, #identity>

    // The dimension list "16x4x?" defines the following 3D index space:
    //
    //   { (i, j, k) : 0 <= i < 16, 0 <= j < 4, 0 <= k < N }
    //
    // where N is a symbol which represents the runtime value of the size of
    // the third dimension.
    //
    // %N here binds to the size of the third dimension.
    %A = alloc(%N) : memref<16x4x?xf32, #col_major>

    // A 2-d dynamic shaped memref that also has a dynamically sized tiled
    // layout. The memref index space is of size %M x %N, while %B1 and %B2
    // bind to the symbols s0, s1 respectively of the layout map #tiled_dynamic.
    // Data tiles of size %B1 x %B2 in the logical space will be stored
    // contiguously in memory. The allocation size will be
    // (%M ceildiv %B1) * %B1 * (%N ceildiv %B2) * %B2 f32 elements.
    %T = alloc(%M, %N) [%B1, %B2] : memref<?x?xf32, #tiled_dynamic>

    // A memref that has a two-element padding at either end. The allocation
    // size will fit 16 * 64 float elements of data.
    %P = alloc() : memref<16x64xf32, #padded>

    // Affine map with symbol 's0' used as offset for the first dimension.
    #imapS = affine_map<(d0, d1) [s0] -> (d0 + s0, d1)>
    // Allocate memref and bind the following symbols:
    // '%n' is bound to the dynamic second dimension of the memref type.
    // '%o' is bound to the symbol 's0' in the affine map of the memref type.
    %n = ...
    %o = ...
    %A = alloc (%n)[%o] : <16x?xf32, #imapS>
    ```
  }];
  let parameters = (ins
    ArrayRefParameter<"int64_t">:$shape,
    "Type":$elementType,
    "MemRefLayoutAttrInterface":$layout,
    "Attribute":$memorySpace
  );
  let builders = [
    TypeBuilderWithInferredContext<(ins
      "ArrayRef<int64_t>":$shape, "Type":$elementType,
      CArg<"MemRefLayoutAttrInterface", "{}">:$layout,
      CArg<"Attribute", "{}">:$memorySpace)>,
    TypeBuilderWithInferredContext<(ins
      "ArrayRef<int64_t>":$shape, "Type":$elementType,
      CArg<"AffineMap">:$map,
      CArg<"Attribute", "{}">:$memorySpace)>,
    /// [deprecated] `Attribute`-based form should be used instead.
    TypeBuilderWithInferredContext<(ins
      "ArrayRef<int64_t>":$shape, "Type":$elementType,
      "AffineMap":$map,
      "unsigned":$memorySpaceInd)>
  ];
  let extraClassDeclaration = [{
    using BaseMemRefType::clone;
    using ShapedType::Trait<MemRefType>::getElementTypeBitWidth;
    using ShapedType::Trait<MemRefType>::getRank;
    using ShapedType::Trait<MemRefType>::getNumElements;
    using ShapedType::Trait<MemRefType>::isDynamicDim;
    using ShapedType::Trait<MemRefType>::hasStaticShape;
    using ShapedType::Trait<MemRefType>::getNumDynamicDims;
    using ShapedType::Trait<MemRefType>::getDimSize;
    using ShapedType::Trait<MemRefType>::getDynamicDimIndex;

    /// This is a builder type that keeps local references to arguments.
    /// Arguments that are passed into the builder must outlive the builder.
    class Builder;

    /// [deprecated] Returns the memory space in old raw integer representation.
    /// New `Attribute getMemorySpace()` method should be used instead.
    unsigned getMemorySpaceAsInt() const;

  }];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// NoneType
//===----------------------------------------------------------------------===//

def Builtin_None : Builtin_Type<"None", "none"> {
  let summary = "A unit type";
  let description = [{
    Syntax:

    ```
    none-type ::= `none`
    ```

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

    #### Example:

    ```mlir
    func.func @none_type() {
      %none_val = "foo.unknown_op"() : () -> none
      return
    }
    ```
  }];
  let extraClassDeclaration = [{
    static NoneType get(MLIRContext *context);
  }];
}

//===----------------------------------------------------------------------===//
// OpaqueType
//===----------------------------------------------------------------------===//

def Builtin_Opaque : Builtin_Type<"Opaque", "opaque"> {
  let summary = "Type of a non-registered dialect";
  let description = [{
    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.

    #### Example:

    ```mlir
    opaque<"llvm", "struct<(i32, float)>">
    opaque<"pdl", "value">
    ```
  }];
  let parameters = (ins
    "StringAttr":$dialectNamespace,
    StringRefParameter<"">:$typeData
  );

  let builders = [
    TypeBuilderWithInferredContext<(ins
      "StringAttr":$dialectNamespace, CArg<"StringRef", "{}">:$typeData
    ), [{
      return $_get(dialectNamespace.getContext(), dialectNamespace, typeData);
    }]>
  ];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// RankedTensorType
//===----------------------------------------------------------------------===//

def Builtin_RankedTensor : Builtin_Type<"RankedTensor", "tensor", [
    ShapedTypeInterface, ValueSemantics
  ], "TensorType"> {
  let summary = "Multi-dimensional array with a fixed number of dimensions";
  let description = [{
    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](#memreftype). 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](MemRef.md/#memrefdim-mlirmemrefdimop) 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.

    #### Example:

    ```mlir
    // 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>
    ```
  }];
  let parameters = (ins
    ArrayRefParameter<"int64_t">:$shape,
    "Type":$elementType,
    "Attribute":$encoding
  );

  let builders = [
    TypeBuilderWithInferredContext<(ins
      "ArrayRef<int64_t>":$shape,
      "Type":$elementType,
      CArg<"Attribute", "{}">:$encoding
    ), [{
      return $_get(elementType.getContext(), shape, elementType, encoding);
    }]>
  ];
  let extraClassDeclaration = [{
    using TensorType::clone;
    using ShapedType::Trait<RankedTensorType>::getElementTypeBitWidth;
    using ShapedType::Trait<RankedTensorType>::getRank;
    using ShapedType::Trait<RankedTensorType>::getNumElements;
    using ShapedType::Trait<RankedTensorType>::isDynamicDim;
    using ShapedType::Trait<RankedTensorType>::hasStaticShape;
    using ShapedType::Trait<RankedTensorType>::getNumDynamicDims;
    using ShapedType::Trait<RankedTensorType>::getDimSize;
    using ShapedType::Trait<RankedTensorType>::getDynamicDimIndex;

    /// This is a builder type that keeps local references to arguments.
    /// Arguments that are passed into the builder must outlive the builder.
    class Builder;

    /// Return a clone of this type with the given new element type and the same
    /// shape as this type.
    RankedTensorType clone(::mlir::Type elementType) {
      return ::llvm::cast<RankedTensorType>(cloneWith(getShape(), elementType));
    }
  }];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// TupleType
//===----------------------------------------------------------------------===//

def Builtin_Tuple : Builtin_Type<"Tuple", "tuple"> {
  let summary = "Fixed-sized collection of other types";
  let description = [{
    Syntax:

    ```
    tuple-type ::= `tuple` `<` (type ( `,` type)*)? `>`
    ```

    The value of `tuple` type represents a fixed-size collection of elements,
    where each element may be of a different type.

    **Rationale:** Though this type is first class in the type system, MLIR
    provides no standard operations for operating on `tuple` types
    ([rationale](../../Rationale/Rationale/#tuple-types)).

    #### Example:

    ```mlir
    // Empty tuple.
    tuple<>

    // Single element
    tuple<f32>

    // Many elements.
    tuple<i32, f32, tensor<i1>, i5>
    ```
  }];
  let parameters = (ins "ArrayRef<Type>":$types);
  let builders = [
    TypeBuilder<(ins "TypeRange":$elementTypes), [{
      return $_get($_ctxt, elementTypes);
    }]>,
    TypeBuilder<(ins), [{
      return $_get($_ctxt, TypeRange());
    }]>
  ];
  let skipDefaultBuilders = 1;
  let genStorageClass = 0;
  let extraClassDeclaration = [{
    /// Accumulate the types contained in this tuple and tuples nested within
    /// it. Note that this only flattens nested tuples, not any other container
    /// type, e.g. a tuple<i32, tensor<i32>, tuple<f32, tuple<i64>>> is
    /// flattened to (i32, tensor<i32>, f32, i64)
    void getFlattenedTypes(SmallVectorImpl<Type> &types);

    /// Return the number of held types.
    size_t size() const;

    /// Iterate over the held elements.
    using iterator = ArrayRef<Type>::iterator;
    iterator begin() const { return getTypes().begin(); }
    iterator end() const { return getTypes().end(); }

    /// Return the element type at index 'index'.
    Type getType(size_t index) const {
      assert(index < size() && "invalid index for tuple type");
      return getTypes()[index];
    }
  }];
}

//===----------------------------------------------------------------------===//
// UnrankedMemRefType
//===----------------------------------------------------------------------===//

def Builtin_UnrankedMemRef : Builtin_Type<"UnrankedMemRef", "unranked_memref", [
    ShapedTypeInterface
  ], "BaseMemRefType"> {
  let summary = "Shaped reference, with unknown rank, to a region of memory";
  let description = [{
    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](#memreftype) for more information on
    memref types.

    #### Examples:

    ```mlir
    memref<*f32>

    // An unranked memref with a memory space of 10.
    memref<*f32, 10>
    ```
  }];
  let parameters = (ins "Type":$elementType, "Attribute":$memorySpace);

  let builders = [
    TypeBuilderWithInferredContext<(ins "Type":$elementType,
                                        "Attribute":$memorySpace), [{
      // Drop default memory space value and replace it with empty attribute.
      Attribute nonDefaultMemorySpace = skipDefaultMemorySpace(memorySpace);
      return $_get(elementType.getContext(), elementType, nonDefaultMemorySpace);
    }]>,
    /// [deprecated] `Attribute`-based form should be used instead.
    TypeBuilderWithInferredContext<(ins "Type":$elementType,
                                        "unsigned":$memorySpace), [{
      // Convert deprecated integer-like memory space to Attribute.
      Attribute memorySpaceAttr =
          wrapIntegerMemorySpace(memorySpace, elementType.getContext());
      return UnrankedMemRefType::get(elementType, memorySpaceAttr);
    }]>
  ];
  let extraClassDeclaration = [{
    using BaseMemRefType::clone;
    using ShapedType::Trait<UnrankedMemRefType>::getElementTypeBitWidth;
    using ShapedType::Trait<UnrankedMemRefType>::getRank;
    using ShapedType::Trait<UnrankedMemRefType>::getNumElements;
    using ShapedType::Trait<UnrankedMemRefType>::isDynamicDim;
    using ShapedType::Trait<UnrankedMemRefType>::hasStaticShape;
    using ShapedType::Trait<UnrankedMemRefType>::getNumDynamicDims;
    using ShapedType::Trait<UnrankedMemRefType>::getDimSize;
    using ShapedType::Trait<UnrankedMemRefType>::getDynamicDimIndex;

    ArrayRef<int64_t> getShape() const { return std::nullopt; }

    /// [deprecated] Returns the memory space in old raw integer representation.
    /// New `Attribute getMemorySpace()` method should be used instead.
    unsigned getMemorySpaceAsInt() const;

    /// Return a clone of this type with the given new element type and the same
    /// shape as this type.
    MemRefType clone(::mlir::Type elementType) {
      return ::llvm::cast<MemRefType>(cloneWith(getShape(), elementType));
    }
  }];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// UnrankedTensorType
//===----------------------------------------------------------------------===//

def Builtin_UnrankedTensor : Builtin_Type<"UnrankedTensor", "unranked_tensor", [
    ShapedTypeInterface, ValueSemantics
  ], "TensorType"> {
  let summary = "Multi-dimensional array with unknown dimensions";
  let description = [{
    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](#rankedtensortype)
    for more information on tensor types.

    #### Examples:

    ```mlir
    tensor<*xf32>
    ```
  }];
  let parameters = (ins "Type":$elementType);

  let builders = [
    TypeBuilderWithInferredContext<(ins "Type":$elementType), [{
      return $_get(elementType.getContext(), elementType);
    }]>
  ];
  let extraClassDeclaration = [{
    using TensorType::clone;
    using ShapedType::Trait<UnrankedTensorType>::getElementTypeBitWidth;
    using ShapedType::Trait<UnrankedTensorType>::getRank;
    using ShapedType::Trait<UnrankedTensorType>::getNumElements;
    using ShapedType::Trait<UnrankedTensorType>::isDynamicDim;
    using ShapedType::Trait<UnrankedTensorType>::hasStaticShape;
    using ShapedType::Trait<UnrankedTensorType>::getNumDynamicDims;
    using ShapedType::Trait<UnrankedTensorType>::getDimSize;
    using ShapedType::Trait<UnrankedTensorType>::getDynamicDimIndex;

    ArrayRef<int64_t> getShape() const { return std::nullopt; }
  }];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

//===----------------------------------------------------------------------===//
// VectorType
//===----------------------------------------------------------------------===//

def Builtin_VectorTypeElementType : AnyTypeOf<[AnyInteger, Index, AnyFloat]> {
  let cppFunctionName = "isValidVectorTypeElementType";
}

def Builtin_Vector : Builtin_Type<"Vector", "vector",
    [ShapedTypeInterface, ValueSemantics], "Type"> {
  let summary = "Multi-dimensional SIMD vector type";
  let description = [{
    Syntax:

    ```
    vector-type ::= `vector` `<` vector-dim-list vector-element-type `>`
    vector-element-type ::= float-type | integer-type | index-type
    vector-dim-list := (static-dim-list `x`)?
    static-dim-list ::= static-dim (`x` static-dim)*
    static-dim ::= (decimal-literal | `[` decimal-literal `]`)
    ```

    The vector type represents a SIMD style vector used by target-specific
    operation sets like AVX or SVE. 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). The dimensions of a vector type
    can be fixed-length, scalable, or a combination of the two. The scalable
    dimensions in a vector are indicated between square brackets ([ ]).

    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:

    ```mlir
    // A 2D fixed-length vector of 3x42 i32 elements.
    vector<3x42xi32>

    // A 1D scalable-length vector that contains a multiple of 4 f32 elements.
    vector<[4]xf32>

    // A 2D scalable-length vector that contains a multiple of 2x8 f32 elements.
    vector<[2]x[8]xf32>

    // A 2D mixed fixed/scalable vector that contains 4 scalable vectors of 4 f32 elements.
    vector<4x[4]xf32>

    // A 3D mixed fixed/scalable vector in which only the inner dimension is
    // scalable.
    vector<2x[4]x8xf32>
    ```
  }];
  let parameters = (ins
    ArrayRefParameter<"int64_t">:$shape,
    Builtin_VectorTypeElementType:$elementType,
    ArrayRefParameter<"bool">:$scalableDims
  );
  let builders = [
    TypeBuilderWithInferredContext<(ins
      "ArrayRef<int64_t>":$shape, "Type":$elementType,
      CArg<"ArrayRef<bool>", "{}">:$scalableDims
    ), [{
      // While `scalableDims` is optional, its default value should be
      // `false` for every dim in `shape`.
      SmallVector<bool> isScalableVec;
      if (scalableDims.empty()) {
        isScalableVec.resize(shape.size(), false);
        scalableDims = isScalableVec;
      }
      return $_get(elementType.getContext(), shape, elementType, scalableDims);
    }]>
  ];
  let extraClassDeclaration = [{
    /// This is a builder type that keeps local references to arguments.
    /// Arguments that are passed into the builder must outlive the builder.
    class Builder;

    /// Returns true if the given type can be used as an element of a vector
    /// type. See "Builtin_VectorTypeElementType" for allowed types.
    static bool isValidElementType(Type t);

    /// Returns true if the vector contains scalable dimensions.
    bool isScalable() const {
      return llvm::is_contained(getScalableDims(), true);
    }
    bool allDimsScalable() const {
      // Treat 0-d vectors as fixed size.
      if (getRank() == 0)
        return false;
      return !llvm::is_contained(getScalableDims(), false);
    }

    /// Get the number of scalable dimensions.
    size_t getNumScalableDims() const {
      return llvm::count(getScalableDims(), true);
    }

    /// Get or create a new VectorType with the same shape as `this` and an
    /// element type of bitwidth scaled by `scale`.
    /// Return null if the scaled element type cannot be represented.
    VectorType scaleElementBitwidth(unsigned scale);

    /// Returns if this type is ranked (always true).
    bool hasRank() const { return true; }

    /// Clone this vector type with the given shape and element type. If the
    /// provided shape is `std::nullopt`, the current shape of the type is used.
    VectorType cloneWith(std::optional<ArrayRef<int64_t>> shape,
                         Type elementType) const;
  }];
  let skipDefaultBuilders = 1;
  let genVerifyDecl = 1;
}

#endif // BUILTIN_TYPES