Skip to content

[mlir][IR] Generalize DenseElementsAttr to custom element types - #183891

Merged
matthias-springer merged 1 commit into
mainfrom
users/matthias-springer/dense_elements_attr_generalized2
Feb 28, 2026
Merged

matthias-springer merged 1 commit into
mainfrom
users/matthias-springer/dense_elements_attr_generalized2

Conversation

@matthias-springer

Copy link
Copy Markdown
Member

DenseElementsAttr supports only a hard-coded list of element types: int, index, float, complex. This commit generalizes the DenseElementsAttr infrastructure: it now supports arbitrary element types, as long as they implement the new DenseElementTypeInterface.

The DenseElementTypeInterface has the following helper functions:

  • getDenseElementBitSize: Query the size of an element in bits. (When storing an element in memory, each element is padded to a full byte. This is an existing limitation of the DenseElementsAttr; with an exception for i1.)
  • convertToAttribute: Attribute factory / deserializer. Converts bytes into an MLIR attribute. The attribute provides the assembly format / printer for a single element.
  • convertFromAttribute: Serializer. Converts an MLIR attribute into bytes.

Note: convertToAttribute / convertFromAttribute are mainly for writing test cases. For performance reasons, DenseElementsAttr users should work with raw bytes / elements and avoid any API that materializes MLIR attributes. However, MLIR attributes typically have human-readable parsers/printers, making them suitable for lit tests and debugging.

This PR introduces an additional assembly format for DenseElementsAttrs. There are now two formats. (The existing one is kept for compatibility reasons.)

  • Literal-first (existing): dense<[1, 2, 3]> : tensor<3xi32>
  • Type-first (new): dense<tensor<3xi32> : [1 : i32, 2 : i32, 3 : i32]>

The new syntax is needed to disambiguate between "literal" (e.g., 1) and attribute (e.g., 1 : i32) when parsing the first token. In the literal-first syntax, we only parse literals. In the type-first syntax, we only parse attributes.

The existing int, index, float, complex types also implement the DenseElementTypeInterface. This allows us to implement DenseElementsAttr::get and AttributeElementIterator::operator* in a generic way.

RFC:
https://discourse.llvm.org/t/rfc-allow-custom-element-types-in-denseelementattr/89656

This is a re-upload of #179122.

…79122)

`DenseElementsAttr` supports only a hard-coded list of element types:
`int`, `index`, `float`, `complex`. This commit generalizes the
`DenseElementsAttr` infrastructure: it now supports arbitrary element
types, as long as they implement the new `DenseElementTypeInterface`.

The `DenseElementTypeInterface` has the following helper functions:
- `getDenseElementBitSize`: Query the size of an element in bits. (When
storing an element in memory, each element is padded to a full byte.
This is an existing limitation of the `DenseElementsAttr`; with an
exception for `i1`.)
- `convertToAttribute`: Attribute factory / deserializer. Converts bytes
into an MLIR attribute. The attribute provides the assembly format /
printer for a single element.
- `convertFromAttribute`: Serializer. Converts an MLIR attribute into
bytes.

Note: `convertToAttribute` / `convertFromAttribute` are mainly for
writing test cases. For performance reasons, `DenseElementsAttr` users
should work with raw bytes / elements and avoid any API that
materializes MLIR attributes. However, MLIR attributes typically have
human-readable parsers/printers, making them suitable for lit tests and
debugging.

This PR introduces an additional assembly format for
`DenseElementsAttrs`. There are now two formats. (The existing one is
kept for compatibility reasons.)
- Literal-first (existing): `dense<[1, 2, 3]> : tensor<3xi32>`
- Type-first (new): `dense<tensor<3xi32> : [1 : i32, 2 : i32, 3 : i32]>`

The new syntax is needed to disambiguate between "literal" (e.g., `1`)
and attribute (e.g., `1 : i32`) when parsing the first token. In the
literal-first syntax, we only parse literals. In the type-first syntax,
we only parse attributes.

The existing `int`, `index`, `float`, `complex` types also implement the
`DenseElementTypeInterface`. This allows us to implement
`DenseElementsAttr::get` and `AttributeElementIterator::operator*` in a
generic way.

RFC:
https://discourse.llvm.org/t/rfc-allow-custom-element-types-in-denseelementattr/89656
@matthias-springer
matthias-springer marked this pull request as ready for review February 28, 2026 10:48
@llvmbot llvmbot added mlir:core MLIR Core Infrastructure mlir mlir:ods labels Feb 28, 2026
@llvmbot

llvmbot commented Feb 28, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-mlir-ods
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-core

Author: Matthias Springer (matthias-springer)

Changes

DenseElementsAttr supports only a hard-coded list of element types: int, index, float, complex. This commit generalizes the DenseElementsAttr infrastructure: it now supports arbitrary element types, as long as they implement the new DenseElementTypeInterface.

The DenseElementTypeInterface has the following helper functions:

  • getDenseElementBitSize: Query the size of an element in bits. (When storing an element in memory, each element is padded to a full byte. This is an existing limitation of the DenseElementsAttr; with an exception for i1.)
  • convertToAttribute: Attribute factory / deserializer. Converts bytes into an MLIR attribute. The attribute provides the assembly format / printer for a single element.
  • convertFromAttribute: Serializer. Converts an MLIR attribute into bytes.

Note: convertToAttribute / convertFromAttribute are mainly for writing test cases. For performance reasons, DenseElementsAttr users should work with raw bytes / elements and avoid any API that materializes MLIR attributes. However, MLIR attributes typically have human-readable parsers/printers, making them suitable for lit tests and debugging.

This PR introduces an additional assembly format for DenseElementsAttrs. There are now two formats. (The existing one is kept for compatibility reasons.)

  • Literal-first (existing): dense&lt;[1, 2, 3]&gt; : tensor&lt;3xi32&gt;
  • Type-first (new): dense&lt;tensor&lt;3xi32&gt; : [1 : i32, 2 : i32, 3 : i32]&gt;

The new syntax is needed to disambiguate between "literal" (e.g., 1) and attribute (e.g., 1 : i32) when parsing the first token. In the literal-first syntax, we only parse literals. In the type-first syntax, we only parse attributes.

The existing int, index, float, complex types also implement the DenseElementTypeInterface. This allows us to implement DenseElementsAttr::get and AttributeElementIterator::operator* in a generic way.

RFC:
https://discourse.llvm.org/t/rfc-allow-custom-element-types-in-denseelementattr/89656

This is a re-upload of #179122.


Patch is 40.75 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/183891.diff

14 Files Affected:

  • (modified) mlir/include/mlir/IR/BuiltinAttributes.td (+34-15)
  • (modified) mlir/include/mlir/IR/BuiltinTypeInterfaces.h (+23)
  • (modified) mlir/include/mlir/IR/BuiltinTypeInterfaces.td (+74-1)
  • (modified) mlir/include/mlir/IR/BuiltinTypes.td (+11-3)
  • (modified) mlir/lib/AsmParser/AttributeParser.cpp (+124-1)
  • (modified) mlir/lib/IR/AsmPrinter.cpp (+41-3)
  • (modified) mlir/lib/IR/AttributeDetail.h (+4-6)
  • (modified) mlir/lib/IR/BuiltinAttributes.cpp (+25-92)
  • (modified) mlir/lib/IR/BuiltinTypeInterfaces.cpp (+34)
  • (modified) mlir/lib/IR/BuiltinTypes.cpp (+87)
  • (added) mlir/test/IR/dense-elements-type-interface.mlir (+83)
  • (modified) mlir/test/lib/Dialect/Test/TestTypeDefs.td (+12)
  • (modified) mlir/test/lib/Dialect/Test/TestTypes.cpp (+28)
  • (modified) mlir/test/lib/Dialect/Test/TestTypes.h (+1)
diff --git a/mlir/include/mlir/IR/BuiltinAttributes.td b/mlir/include/mlir/IR/BuiltinAttributes.td
index 798d3c84f9618..dced379d1f979 100644
--- a/mlir/include/mlir/IR/BuiltinAttributes.td
+++ b/mlir/include/mlir/IR/BuiltinAttributes.td
@@ -239,29 +239,48 @@ def Builtin_DenseIntOrFPElementsAttr : Builtin_Attr<
     "DenseElementsAttr"
   > {
   let summary = "An Attribute containing a dense multi-dimensional array of "
-                "integer or floating-point values";
+                "values";
   let description = [{
-    Syntax:
-
-    ```
-    tensor-literal ::= integer-literal | float-literal | bool-literal | [] | [tensor-literal (, tensor-literal)* ]
-    dense-intorfloat-elements-attribute ::= `dense` `<` tensor-literal `>` `:`
-                                            ( 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`.
+    A dense elements attribute stores one or multiple elements of the same type.
+    The term "dense" refers to the fact that elements are not stored as
+    individual MLIR attributes, but in a raw buffer. The attribute provides a
+    covenience API to access elements in the form of MLIR attributes, but users
+    should avoid that API in performance-critical code and utilize APIs that
+    operate on raw bytes instead.
+
+    The number of elements is determined by the `type` shaped type. (Unranked
+    shaped types are not supported.) The element type of the shaped type must
+    implement the `DenseElementType` interface. This type interface defines the
+    bitwidth of an element and provides a serializer/deserializer to/from MLIR
+    attributes.
+
+    Storage format: Given an element bitwidth "w", element "i" starts at byte
+    offset "i * ceildiv(w, 8)". In other words, each element starts at a full
+    byte offset.
+
+    TODO: The name `DenseIntOrFPElements` is no longer accurate. The attribute
+    will be renamed in the future.
 
     Examples:
 
     ```
-    // A splat tensor of integer values.
+    // Literal-first syntax: A splat tensor of integer values.
     dense<10> : tensor<2xi32>
-    // A tensor of 2 float32 elements.
+
+    // Literal-first syntax: A tensor of 2 float32 elements.
     dense<[10.0, 11.0]> : tensor<2xf32>
+
+    // Type-first syntax: A splat tensor of integer values.
+    dense<tensor<2xi32> : 10 : i32>
+
+    // Type-first syntax: A tensor of 2 float32 elements.
+    dense<tensor<2xf32> : [10.0, 11.0]>
     ```
+
+    Note: The literal-first syntax is supported only for complex, float, index,
+    int element types. The parser/print have special casing for these types.
+    Dense element attributes with other element types must use the type-first
+    syntax.
   }];
   let parameters = (ins AttributeSelfTypeParameter<"", "ShapedType">:$type,
                         "ArrayRef<char>":$rawData);
diff --git a/mlir/include/mlir/IR/BuiltinTypeInterfaces.h b/mlir/include/mlir/IR/BuiltinTypeInterfaces.h
index 5f14517d8dd71..9425d554b427c 100644
--- a/mlir/include/mlir/IR/BuiltinTypeInterfaces.h
+++ b/mlir/include/mlir/IR/BuiltinTypeInterfaces.h
@@ -19,6 +19,29 @@ struct fltSemantics;
 namespace mlir {
 class FloatType;
 class MLIRContext;
+
+namespace detail {
+/// Float type implementation of
+/// DenseElementTypeInterface::getDenseElementBitSize.
+size_t getFloatTypeDenseElementBitSize(Type type);
+
+/// Float type implementation of DenseElementTypeInterface::convertToAttribute.
+Attribute convertFloatTypeToAttribute(Type type, llvm::ArrayRef<char> rawData);
+
+/// Float type implementation of
+/// DenseElementTypeInterface::convertFromAttribute.
+LogicalResult
+convertFloatTypeFromAttribute(Type type, Attribute attr,
+                              llvm::SmallVectorImpl<char> &result);
+
+/// Read `bitWidth` bits from byte-aligned position in `rawData` and return as
+/// an APInt. Handles endianness correctly.
+llvm::APInt readBits(const char *rawData, size_t bitPos, size_t bitWidth);
+
+/// Write `value` to byte-aligned position `bitPos` in `rawData`. Handles
+/// endianness correctly.
+void writeBits(char *rawData, size_t bitPos, llvm::APInt value);
+} // namespace detail
 } // namespace mlir
 
 #include "mlir/IR/BuiltinTypeInterfaces.h.inc"
diff --git a/mlir/include/mlir/IR/BuiltinTypeInterfaces.td b/mlir/include/mlir/IR/BuiltinTypeInterfaces.td
index 9ef08b7020b99..93c8c0694b467 100644
--- a/mlir/include/mlir/IR/BuiltinTypeInterfaces.td
+++ b/mlir/include/mlir/IR/BuiltinTypeInterfaces.td
@@ -41,12 +41,70 @@ def VectorElementTypeInterface : TypeInterface<"VectorElementTypeInterface"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// DenseElementTypeInterface
+//===----------------------------------------------------------------------===//
+
+def DenseElementTypeInterface : TypeInterface<"DenseElementType"> {
+  let cppNamespace = "::mlir";
+  let description = [{
+    This interface allows custom types to be used as element types in
+    DenseElementsAttr. Types implementing this interface define:
+
+    1. The bit size for element storage.
+    2. Helper methods for converting from/to Attribute. This assumes that there
+       is a corresponding attribute for each type that implements this
+       interface.
+
+    The helper methods for converting from/to Attribute are utilized when
+    parsing/printing IR or iterating over the elements via Attribute.
+  }];
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/[{
+        Return the number of bits required to store one element in dense
+        storage.
+        
+        Note: The DenseElementsAttr infrastructure will automatically align
+        every element to a full byte in storage. This limitation could be lifted
+        in the future to support dense packing of non-byte-sized elements.
+      }],
+      /*retTy=*/"size_t",
+      /*methodName=*/"getDenseElementBitSize",
+      /*args=*/(ins)
+    >,
+    InterfaceMethod<
+      /*desc=*/[{
+        Attribute deserialization / attribute factory: Convert raw storage bytes
+        into an MLIR attribute. The size of `rawData` is
+        "ceilDiv(getDenseElementBitSize(), 8)".
+      }],
+      /*retTy=*/"::mlir::Attribute",
+      /*methodName=*/"convertToAttribute",
+      /*args=*/(ins "::llvm::ArrayRef<char>":$rawData)
+    >,
+    InterfaceMethod<
+      /*desc=*/[{
+        Attribute serialization: Convert an MLIR attribute into raw bytes.
+        Implementations must append "getDenseElementBitSize() / 8" values to
+        `result`. Return "failure" if the attribute is incompatible with this
+        element type.
+      }],
+      /*retTy=*/"::llvm::LogicalResult",
+      /*methodName=*/"convertFromAttribute",
+      /*args=*/(ins "::mlir::Attribute":$attr,
+                    "::llvm::SmallVectorImpl<char>&":$result)
+    >,
+  ];
+}
+
 //===----------------------------------------------------------------------===//
 // FloatTypeInterface
 //===----------------------------------------------------------------------===//
 
 def FloatTypeInterface : TypeInterface<"FloatType",
-    [VectorElementTypeInterface]> {
+    [DenseElementTypeInterface, VectorElementTypeInterface]> {
   let cppNamespace = "::mlir";
   let description = [{
     This type interface should be implemented by all floating-point types. It
@@ -83,6 +141,21 @@ def FloatTypeInterface : TypeInterface<"FloatType",
     /// The width includes the integer bit.
     unsigned getFPMantissaWidth();
   }];
+
+  let extraTraitClassDeclaration = [{
+    /// DenseElementTypeInterface implementations for float types.
+    size_t getDenseElementBitSize() const {
+      return ::mlir::detail::getFloatTypeDenseElementBitSize($_type);
+    }
+    ::mlir::Attribute convertToAttribute(::llvm::ArrayRef<char> rawData) const {
+      return ::mlir::detail::convertFloatTypeToAttribute($_type, rawData);
+    }
+    ::llvm::LogicalResult
+    convertFromAttribute(::mlir::Attribute attr,
+                         ::llvm::SmallVectorImpl<char> &result) const {
+      return ::mlir::detail::convertFloatTypeFromAttribute($_type, attr, result);
+    }
+  }];
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/IR/BuiltinTypes.td b/mlir/include/mlir/IR/BuiltinTypes.td
index 806064faeda00..e7d0a03a85e7d 100644
--- a/mlir/include/mlir/IR/BuiltinTypes.td
+++ b/mlir/include/mlir/IR/BuiltinTypes.td
@@ -45,7 +45,10 @@ def ValueSemantics : NativeTypeTrait<"ValueSemantics"> {
 // ComplexType
 //===----------------------------------------------------------------------===//
 
-def Builtin_Complex : Builtin_Type<"Complex", "complex"> {
+def Builtin_Complex : Builtin_Type<"Complex", "complex",
+    [DeclareTypeInterfaceMethods<DenseElementTypeInterface,
+      ["getDenseElementBitSize", "convertToAttribute", "convertFromAttribute"]>
+    ]> {
   let summary = "Complex number with a parameterized element type";
   let description = [{
     Syntax:
@@ -560,7 +563,9 @@ def Builtin_Graph : Builtin_FunctionLike<"Graph", "graph">;
 //===----------------------------------------------------------------------===//
 
 def Builtin_Index : Builtin_Type<"Index", "index",
-    [VectorElementTypeInterface]> {
+    [DeclareTypeInterfaceMethods<DenseElementTypeInterface,
+      ["getDenseElementBitSize", "convertToAttribute", "convertFromAttribute"]>,
+     VectorElementTypeInterface]> {
   let summary = "Integer-like type with unknown platform-dependent bit width";
   let description = [{
     Syntax:
@@ -591,7 +596,10 @@ def Builtin_Index : Builtin_Type<"Index", "index",
 //===----------------------------------------------------------------------===//
 
 def Builtin_Integer : Builtin_Type<"Integer", "integer",
-    [VectorElementTypeInterface, QuantStorageTypeInterface]> {
+    [VectorElementTypeInterface, QuantStorageTypeInterface,
+     DeclareTypeInterfaceMethods<DenseElementTypeInterface, [
+         "getDenseElementBitSize", "convertToAttribute",
+         "convertFromAttribute"]>]> {
   let summary = "Integer type with arbitrary precision up to a fixed limit";
   let description = [{
     Syntax:
diff --git a/mlir/lib/AsmParser/AttributeParser.cpp b/mlir/lib/AsmParser/AttributeParser.cpp
index 5978a11d06bc9..dc9744a42b730 100644
--- a/mlir/lib/AsmParser/AttributeParser.cpp
+++ b/mlir/lib/AsmParser/AttributeParser.cpp
@@ -16,6 +16,7 @@
 #include "mlir/IR/AffineMap.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/BuiltinDialect.h"
+#include "mlir/IR/BuiltinTypeInterfaces.h"
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/DialectResourceBlobManager.h"
 #include "mlir/IR/IntegerSet.h"
@@ -953,6 +954,119 @@ Attribute Parser::parseDenseArrayAttr(Type attrType) {
   return eltParser.getAttr();
 }
 
+/// Try to parse a dense elements attribute with the type-first syntax.
+/// Syntax: dense<TYPE : [ATTR, ATTR, ...]>
+/// This syntax is used for types other than int, float, index and complex.
+///
+/// Returns:
+///   - "null" attribute if this is not the type-first syntax.
+///   - "failure" in case of a parse error.
+///   - A valid Attribute otherwise.
+static FailureOr<Attribute> parseDenseElementsAttrTyped(Parser &p, SMLoc loc) {
+  // Skip l_paren because "parseType" would try to parse it as a tuple/function
+  // type, but '(' starts a complex literal like in the literal-first syntax.
+  if (p.getToken().is(Token::l_paren))
+    return Attribute();
+
+  // Parse type and valdiate that it's a shaped type.
+  auto typeLoc = p.getToken().getLoc();
+  Type type;
+  OptionalParseResult typeResult = p.parseOptionalType(type);
+  if (!typeResult.has_value())
+    return Attribute(); // Not type-first syntax.
+  if (failed(*typeResult))
+    return failure(); // Type parse error.
+
+  auto shapedType = dyn_cast<ShapedType>(type);
+  if (!shapedType) {
+    p.emitError(typeLoc, "expected a shaped type for dense elements");
+    return failure();
+  }
+  if (!shapedType.hasStaticShape()) {
+    p.emitError(typeLoc, "dense elements type must have static shape");
+    return failure();
+  }
+
+  // Check that the element type implements DenseElementTypeInterface.
+  auto denseEltType = dyn_cast<DenseElementType>(shapedType.getElementType());
+  if (!denseEltType) {
+    p.emitError(typeLoc,
+                "element type must implement DenseElementTypeInterface "
+                "for type-first dense syntax");
+    return failure();
+  }
+
+  // Parse colon.
+  if (p.parseToken(Token::colon, "expected ':' after type in dense attribute"))
+    return failure();
+
+  // Parse the element attributes and convert to raw bytes.
+  SmallVector<char> rawData;
+
+  // Helper to parse a single element.
+  auto parseSingleElement = [&]() -> ParseResult {
+    Attribute elemAttr = p.parseAttribute();
+    if (!elemAttr)
+      return failure();
+    if (failed(denseEltType.convertFromAttribute(elemAttr, rawData))) {
+      p.emitError("incompatible attribute for element type");
+      return failure();
+    }
+    return success();
+  };
+
+  // Recursively parse elements matching the expected shape.
+  std::function<ParseResult(ArrayRef<int64_t>)> parseElements;
+  parseElements = [&](ArrayRef<int64_t> remainingShape) -> ParseResult {
+    // Leaf: parse a single element.
+    if (remainingShape.empty())
+      return parseSingleElement();
+
+    // Non-leaf: expect a list with the correct number of elements.
+    int64_t expectedCount = remainingShape.front();
+    ArrayRef<int64_t> innerShape = remainingShape.drop_front();
+    int64_t actualCount = 0;
+
+    auto parseOne = [&]() -> ParseResult {
+      if (parseElements(innerShape))
+        return failure();
+      ++actualCount;
+      return success();
+    };
+
+    if (p.parseCommaSeparatedList(Parser::Delimiter::Square, parseOne))
+      return failure();
+
+    if (actualCount != expectedCount) {
+      p.emitError() << "expected " << expectedCount
+                    << " elements in dimension, got " << actualCount;
+      return failure();
+    }
+    return success();
+  };
+
+  // Parse elements.
+  if (!p.getToken().is(Token::l_square)) {
+    // Single element - parse as splat.
+    if (parseSingleElement())
+      return failure();
+  } else if (shapedType.getShape().empty()) {
+    // Scalar type shouldn't have a list.
+    p.emitError(loc, "expected single element for scalar type, got list");
+    return failure();
+  } else {
+    // Parse structured literal matching the shape.
+    if (parseElements(shapedType.getShape()))
+      return failure();
+  }
+
+  if (p.parseToken(Token::greater, "expected '>' to close dense attribute"))
+    return failure();
+
+  // Create the attribute from raw buffer.
+  return DenseElementsAttr::getFromRawBuffer(shapedType, rawData);
+}
+
 /// Parse a dense elements attribute.
 Attribute Parser::parseDenseElementsAttr(Type attrType) {
   auto attribLoc = getToken().getLoc();
@@ -960,7 +1074,16 @@ Attribute Parser::parseDenseElementsAttr(Type attrType) {
   if (parseToken(Token::less, "expected '<' after 'dense'"))
     return nullptr;
 
-  // Parse the literal data if necessary.
+  // Try to parse the type-first syntax: dense<TYPE : [ATTR, ...]>
+  FailureOr<Attribute> typedResult =
+      parseDenseElementsAttrTyped(*this, attribLoc);
+  if (failed(typedResult))
+    return nullptr;
+  if (*typedResult)
+    return *typedResult;
+
+  // Try to parse the literal-first syntax, which is the default format for
+  // int, float, index and complex element types.
   TensorLiteralParser literalParser(*this);
   if (!consumeIf(Token::greater)) {
     if (literalParser.parse(/*allowHex=*/true) ||
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index 81455699421cc..b3242f838fc1d 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -507,11 +507,18 @@ class AsmPrinter::Impl {
   /// Print a dense string elements attribute.
   void printDenseStringElementsAttr(DenseStringElementsAttr attr);
 
-  /// Print a dense elements attribute. If 'allowHex' is true, a hex string is
-  /// used instead of individual elements when the elements attr is large.
+  /// Print a dense elements attribute in the literal-first syntax. If
+  /// 'allowHex' is true, a hex string is used instead of individual elements
+  /// when the elements attr is large.
   void printDenseIntOrFPElementsAttr(DenseIntOrFPElementsAttr attr,
                                      bool allowHex);
 
+  /// Print a dense elements attribute using the type-first syntax and the
+  /// DenseElementTypeInterface, which provides the attribute printer for each
+  /// element.
+  void printTypeFirstDenseElementsAttr(DenseElementsAttr attr,
+                                       DenseElementType denseEltType);
+
   /// Print a dense array attribute.
   void printDenseArrayAttr(DenseArrayAttr attr);
 
@@ -2507,7 +2514,17 @@ void AsmPrinter::Impl::printAttributeImpl(Attribute attr,
       printElidedElementsAttr(os);
     } else {
       os << "dense<";
-      printDenseIntOrFPElementsAttr(intOrFpEltAttr, /*allowHex=*/true);
+      // Check if the element type implements DenseElementTypeInterface and is
+      // not a built-in type. Built-in types (int, float, index, complex) use
+      // the existing printing format for backwards compatibility.
+      Type eltType = intOrFpEltAttr.getElementType();
+      if (isa<FloatType, IntegerType, IndexType, ComplexType>(eltType)) {
+        printDenseIntOrFPElementsAttr(intOrFpEltAttr, /*allowHex=*/true);
+      } else {
+        printTypeFirstDenseElementsAttr(intOrFpEltAttr,
+                                        cast<DenseElementType>(eltType));
+        typeElision = AttrTypeElision::Must;
+      }
       os << '>';
     }
 
@@ -2705,6 +2722,27 @@ void AsmPrinter::Impl::printDenseStringElementsAttr(
   printDenseElementsAttrImpl(attr.isSplat(), attr.getType(), os, printFn);
 }
 
+void AsmPrinter::Impl::printTypeFirstDenseElementsAttr(
+    DenseElementsAttr attr, DenseElementType denseEltType) {
+  // Print the type first: dense<TYPE : [ELEMENTS]>
+  printType(attr.getType());
+  os << " : ";
+
+  ArrayRef<char> rawData = attr.getRawData();
+  // Storage is byte-aligned: align bit size up to next byte boundary.
+  size_t bitSize = denseEltType.getDenseElementBitSize();
+  size_t byteSize = llvm::divideCeil(bitSize, static_cast<size_t>(CHAR_BIT));
+
+  // Print elements: convert raw bytes to attribute, then print attribute.
+  printDenseElementsAttrImpl(
+      attr.isSplat(), attr.getType(), os, [&](unsigned index) {
+        size_t offset = attr.isSplat() ? 0 : index * byteSize;
+        ArrayRef<char> elemData = rawData.slice(offset, byteSize);
+        Attribute elemAttr = denseEltType.convertToAttribute(elemData);
+        printAttributeImpl(elemAttr);
+      });
+}
+
 void AsmPrinter::Impl::printDenseArrayAttr(DenseArrayAttr attr) {
   Type type = attr.getElementType();
   unsigned bitwidth = type.isInteger(1) ? 8 : type.getIntOrFloatBitWidth();
diff --git a/mlir/lib/IR/AttributeDetail.h b/mlir/lib/IR/AttributeDetail.h
index 1f268603cf37f..8505149afdd9c 100644
--- a/mlir/lib/IR/AttributeDetail.h
+++ b/mlir/lib/IR/AttributeDetail.h
@@ -16,6 +16,7 @@
 #include "mlir/IR/AffineMap.h"
 #include "mlir/IR/AttributeSupport.h"
 #include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/BuiltinTypeInterfaces.h"
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/IntegerSet.h"
 #include "mlir/IR/MLIRContext.h"
@@ -32,12 +33,9 @@ namespace detail {
 
 /// Return the bit width which DenseElementsAttr should use for this type.
 inline size_t getDenseElementBitWidth(Type eltType) {
-  // Align the width for complex to 8 to make storage and interpretation easier.
-  if (ComplexType comp = llvm::dyn_cast<ComplexType>(eltType))
-    return llvm::alignTo<8>(getDenseElementBitWidth(comp.getElementType())) * 2;
-  if (eltType.isIndex())
-    return IndexType::kInternalStorageBitWidth;
-  return eltType.getIntOrFloatBitWidth();
+  if (auto denseEltType = llvm::dyn_cast<DenseElementType>(eltType))
+    return denseEltType.getDenseElementBitSize();
+  llvm_unreachable("unsupported element type");
 }
 
 /// An attribute representing a reference to a dense vector or tensor object.
diff --git a/mlir/lib/IR/Builtin...
[truncated]

@matthias-springer

Copy link
Copy Markdown
Member Author

This PR was approved + merged earlier, but then reverted due to build bot breakage. That breakage no longer reproduces. Passed build bot run here: https://lab.llvm.org/buildbot/#/builders/207/builds/14071.

@matthias-springer
matthias-springer merged commit e655c36 into main Feb 28, 2026
17 checks passed
@matthias-springer
matthias-springer deleted the users/matthias-springer/dense_elements_attr_generalized2 branch February 28, 2026 13:03
@llvm-ci

llvm-ci commented Feb 28, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder flang-arm64-windows-msvc running on linaro-armv8-windows-msvc-01 while building mlir at step 6 "test-build-unified-tree-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/207/builds/14077

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-mlir) failure: test (failure)
******************** TEST 'MLIR :: Conversion/NVGPUToNVVM/nvgpu-to-nvvm.mlir' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 1
c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\mlir-opt.exe C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\mlir\test\Conversion\NVGPUToNVVM\nvgpu-to-nvvm.mlir -convert-nvgpu-to-nvvm | c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\mlir\test\Conversion\NVGPUToNVVM\nvgpu-to-nvvm.mlir
# executed command: 'c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\mlir-opt.exe' 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\mlir\test\Conversion\NVGPUToNVVM\nvgpu-to-nvvm.mlir' -convert-nvgpu-to-nvvm
# .---command stderr------------
# | Assertion failed: implDenseElementType && "`::mlir::FloatType` expected its base interface `::mlir::DenseElementType` to be registered", file C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/tools/mlir/include\mlir/IR/BuiltinTypeInterfaces.h.inc, line 251
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
# | Stack dump:
# | 0.	Program arguments: c:\\users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\build\\bin\\mlir-opt.exe C:\\Users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\llvm-project\\mlir\\test\\Conversion\\NVGPUToNVVM\\nvgpu-to-nvvm.mlir -convert-nvgpu-to-nvvm
# | 1.	MLIR Parser: custom op parser 'func.func'�
# | Exception Code: 0xC000001D
# | #0 0x00007ff7df3ea330 mlir::detail::FallbackTypeIDResolver::registerImplicitTypeID(class llvm::StringRef) (c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\mlir-opt.exe+0x2bfa330)
# | #1 0x00007ffcf206ae50 (C:\WINDOWS\System32\ucrtbase.dll+0x7ae50)
# | #2 0x4c7cfffcf206ba5c
# `-----------------------------
# error: command failed with exit status: 0xc000001d
# executed command: 'c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe' 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\mlir\test\Conversion\NVGPUToNVVM\nvgpu-to-nvvm.mlir'
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\mlir\test\Conversion\NVGPUToNVVM\nvgpu-to-nvvm.mlir
# `-----------------------------
# error: command failed with exit status: 2

--

********************

Step 7 (test-build-unified-tree-check-flang) failure: test (failure)
******************** TEST 'Flang :: Intrinsics/math-codegen.fir' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 1
split-file C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\flang\test\Intrinsics\math-codegen.fir C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp
# executed command: split-file 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\flang\test\Intrinsics\math-codegen.fir' 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp'
# RUN: at line 5
fir-opt C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp/abs_fast.fir --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" | c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp/abs_fast.fir
# executed command: fir-opt 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp/abs_fast.fir' --fir-to-llvm-ir=target=x86_64-unknown-linux-gnu
# .---command stderr------------
# | Assertion failed: implDenseElementType && "`::mlir::FloatType` expected its base interface `::mlir::DenseElementType` to be registered", file C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/tools/mlir/include\mlir/IR/BuiltinTypeInterfaces.h.inc, line 251
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
# | Stack dump:
# | 0.	Program arguments: fir-opt C:\\Users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\build\\tools\\flang\\test\\Intrinsics\\Output\\math-codegen.fir.tmp/abs_fast.fir --fir-to-llvm-ir=target=x86_64-unknown-linux-gnu
# | 1.	MLIR Parser: custom op parser 'func.func'�
# | Exception Code: 0xC000001D
# | #0 0x00007ff6b4054cfc mlir::detail::FallbackTypeIDResolver::registerImplicitTypeID(class llvm::StringRef) (c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\fir-opt.exe+0x1594cfc)
# | #1 0x00007ffcf206ae50 (C:\WINDOWS\System32\ucrtbase.dll+0x7ae50)
# | #2 0xc553fffcf206ba5c
# `-----------------------------
# error: command failed with exit status: 0xc000001d
# executed command: 'c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe' 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp/abs_fast.fir'
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  c:\users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\filecheck.exe C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\tools\flang\test\Intrinsics\Output\math-codegen.fir.tmp/abs_fast.fir
# `-----------------------------
# error: command failed with exit status: 2

--

********************

Step 8 (test-build-unified-tree-check-flang-rt) failure: test (failure)
******************** TEST 'flang-rt :: Driver/exec.f90' FAILED ********************
Exit Code: 3221225501

Command Output (stdout):
--
# RUN: at line 5
C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/bin/flang.exe  -L"C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/lib/clang/23/lib/aarch64-pc-windows-msvc" C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\flang-rt\test\Driver\exec.f90 -o C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\runtimes\runtimes-bins\flang-rt\test\Driver\Output\exec.f90.tmp
# executed command: C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/bin/flang.exe -LC:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/lib/clang/23/lib/aarch64-pc-windows-msvc 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\llvm-project\flang-rt\test\Driver\exec.f90' -o 'C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\runtimes\runtimes-bins\flang-rt\test\Driver\Output\exec.f90.tmp'
# .---command stderr------------
# | Assertion failed: implDenseElementType && "`::mlir::FloatType` expected its base interface `::mlir::DenseElementType` to be registered", file C:/Users/tcwg/llvm-worker/flang-arm64-windows-msvc/build/tools/mlir/include\mlir/IR/BuiltinTypeInterfaces.h.inc, line 251
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
# | Stack dump:
# | 0.	Program arguments: C:\\Users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\build\\bin\\flang -fc1 -triple aarch64-pc-windows-msvc19.39.33523 -emit-obj -mrelocation-model pic -pic-level 2 -target-cpu generic -target-feature +v8a -target-feature +fp-armv8 -target-feature +neon --dependent-lib=clang_rt.builtins-aarch64.lib -D_MT --dependent-lib=libcmt --dependent-lib=flang_rt.runtime.static.lib -D_MSC_VER=1939 -D_MSC_FULL_VER=193933523 -D_WIN32 -D_M_ARM64=1 -resource-dir C:\\Users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\build\\lib\\clang\\23 -mframe-pointer=reserved -o C:\\Users\\tcwg\\AppData\\Local\\Temp\\lit-tmp-pg34yfr3\\exec-0784f5.o -x f95 C:\\Users\\tcwg\\llvm-worker\\flang-arm64-windows-msvc\\llvm-project\\flang-rt\\test\\Driver\\exec.f90
# | Exception Code: 0xC000001D
# | #0 0x00007ff60e89200c (C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin\flang.exe+0x90200c)
# | #1 0x00007ffcf206ae50 (C:\WINDOWS\System32\ucrtbase.dll+0x7ae50)
# | #2 0x7a61fffcf206ba5c
# | flang: error: flang frontend command failed due to signal (use -v to see invocation)
# | flang version 23.0.0git (https://github.com/llvm/llvm-project.git e655c36c16c118e3f8ae0c95854f33119218a4bf)
# | Target: aarch64-pc-windows-msvc
# | Thread model: posix
# | InstalledDir: C:\Users\tcwg\llvm-worker\flang-arm64-windows-msvc\build\bin
# | Build config: +assertions
# | flang: note: diagnostic msg: 
# | ********************
# | 
# | PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
# | Preprocessed source(s) and associated run script(s) are located at:
# | flang: note: diagnostic msg: C:\Users\tcwg\AppData\Local\Temp\lit-tmp-pg34yfr3\exec-06fcfc
# | flang: note: diagnostic msg: C:\Users\tcwg\AppData\Local\Temp\lit-tmp-pg34yfr3\exec-06fcfc.sh
# | flang: note: diagnostic msg: 
# | 
# | ********************
# `-----------------------------
# error: command failed with exit status: 0xc000001d

--

********************


matthias-springer added a commit that referenced this pull request Feb 28, 2026
…ypes" (#183917)

Reverts #183891

Reverting a second time. The build bot failure seems to be
non-deterministic.
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Feb 28, 2026
…m element types" (#183917)

Reverts llvm/llvm-project#183891

Reverting a second time. The build bot failure seems to be
non-deterministic.
sahas3 pushed a commit to sahas3/llvm-project that referenced this pull request Mar 4, 2026
…vm#183891)

`DenseElementsAttr` supports only a hard-coded list of element types:
`int`, `index`, `float`, `complex`. This commit generalizes the
`DenseElementsAttr` infrastructure: it now supports arbitrary element
types, as long as they implement the new `DenseElementTypeInterface`.

The `DenseElementTypeInterface` has the following helper functions:
- `getDenseElementBitSize`: Query the size of an element in bits. (When
storing an element in memory, each element is padded to a full byte.
This is an existing limitation of the `DenseElementsAttr`; with an
exception for `i1`.)
- `convertToAttribute`: Attribute factory / deserializer. Converts bytes
into an MLIR attribute. The attribute provides the assembly format /
printer for a single element.
- `convertFromAttribute`: Serializer. Converts an MLIR attribute into
bytes.

Note: `convertToAttribute` / `convertFromAttribute` are mainly for
writing test cases. For performance reasons, `DenseElementsAttr` users
should work with raw bytes / elements and avoid any API that
materializes MLIR attributes. However, MLIR attributes typically have
human-readable parsers/printers, making them suitable for lit tests and
debugging.

This PR introduces an additional assembly format for
`DenseElementsAttrs`. There are now two formats. (The existing one is
kept for compatibility reasons.)
- Literal-first (existing): `dense<[1, 2, 3]> : tensor<3xi32>`
- Type-first (new): `dense<tensor<3xi32> : [1 : i32, 2 : i32, 3 : i32]>`

The new syntax is needed to disambiguate between "literal" (e.g., `1`)
and attribute (e.g., `1 : i32`) when parsing the first token. In the
literal-first syntax, we only parse literals. In the type-first syntax,
we only parse attributes.

The existing `int`, `index`, `float`, `complex` types also implement the
`DenseElementTypeInterface`. This allows us to implement
`DenseElementsAttr::get` and `AttributeElementIterator::operator*` in a
generic way.

RFC:

https://discourse.llvm.org/t/rfc-allow-custom-element-types-in-denseelementattr/89656

This is a re-upload of llvm#179122.
sahas3 pushed a commit to sahas3/llvm-project that referenced this pull request Mar 4, 2026
…ypes" (llvm#183917)

Reverts llvm#183891

Reverting a second time. The build bot failure seems to be
non-deterministic.
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
…vm#183891)

`DenseElementsAttr` supports only a hard-coded list of element types:
`int`, `index`, `float`, `complex`. This commit generalizes the
`DenseElementsAttr` infrastructure: it now supports arbitrary element
types, as long as they implement the new `DenseElementTypeInterface`.

The `DenseElementTypeInterface` has the following helper functions:
- `getDenseElementBitSize`: Query the size of an element in bits. (When
storing an element in memory, each element is padded to a full byte.
This is an existing limitation of the `DenseElementsAttr`; with an
exception for `i1`.)
- `convertToAttribute`: Attribute factory / deserializer. Converts bytes
into an MLIR attribute. The attribute provides the assembly format /
printer for a single element.
- `convertFromAttribute`: Serializer. Converts an MLIR attribute into
bytes.

Note: `convertToAttribute` / `convertFromAttribute` are mainly for
writing test cases. For performance reasons, `DenseElementsAttr` users
should work with raw bytes / elements and avoid any API that
materializes MLIR attributes. However, MLIR attributes typically have
human-readable parsers/printers, making them suitable for lit tests and
debugging.

This PR introduces an additional assembly format for
`DenseElementsAttrs`. There are now two formats. (The existing one is
kept for compatibility reasons.)
- Literal-first (existing): `dense<[1, 2, 3]> : tensor<3xi32>`
- Type-first (new): `dense<tensor<3xi32> : [1 : i32, 2 : i32, 3 : i32]>`

The new syntax is needed to disambiguate between "literal" (e.g., `1`)
and attribute (e.g., `1 : i32`) when parsing the first token. In the
literal-first syntax, we only parse literals. In the type-first syntax,
we only parse attributes.

The existing `int`, `index`, `float`, `complex` types also implement the
`DenseElementTypeInterface`. This allows us to implement
`DenseElementsAttr::get` and `AttributeElementIterator::operator*` in a
generic way.

RFC:

https://discourse.llvm.org/t/rfc-allow-custom-element-types-in-denseelementattr/89656

This is a re-upload of llvm#179122.
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
…ypes" (llvm#183917)

Reverts llvm#183891

Reverting a second time. The build bot failure seems to be
non-deterministic.
markrvmurray pushed a commit to markrvmurray/llvm-mc6809 that referenced this pull request Jun 14, 2026
…ypes" (#183917)

Reverts llvm/llvm-project#183891

Reverting a second time. The build bot failure seems to be
non-deterministic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mlir:core MLIR Core Infrastructure mlir:ods mlir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants