[Mlir-commits] [mlir] [MLIR] Print (u)int8_t args as numbers and properly support char args (PR #225680)

Robert Konicar llvmlistbot at llvm.org
Thu Oct 8 01:24:54 PDT 2026


https://github.com/Jezurko updated https://github.com/llvm/llvm-project/pull/225680

>From bf63a64aabf4ce8409973fd6c9267c277591e2d5 Mon Sep 17 00:00:00 2001
From: Robert Konicar <robert.konicar at nextsilicon.com>
Date: Tue, 22 Sep 2026 22:20:45 +0200
Subject: [PATCH 1/2] [MLIR] Print (u)int8_t args as numbers and properly
 support char args

Adds printing overloads for 8-bit integers to make sure that the
tablegen generated assemblyFromat printer prints them as numbers
in parameters instead of resolving to the raw_ostream char overload.

Adds support for printing/parsing raw char arguments to make sure that
it is possible to have real glyphs as arguments.
---
 mlir/include/mlir/IR/DialectImplementation.h  | 32 +++++++++++++++++++
 mlir/include/mlir/IR/OpImplementation.h       | 12 +++++++
 mlir/test/lib/Dialect/Test/TestAttrDefs.td    | 14 ++++++++
 .../attr-or-type-format-roundtrip.mlir        |  7 ++++
 4 files changed, 65 insertions(+)

diff --git a/mlir/include/mlir/IR/DialectImplementation.h b/mlir/include/mlir/IR/DialectImplementation.h
index 2652114b508e3e..c888f81a66a94e 100644
--- a/mlir/include/mlir/IR/DialectImplementation.h
+++ b/mlir/include/mlir/IR/DialectImplementation.h
@@ -110,6 +110,38 @@ struct FieldParser<
   }
 };
 
+/// Parse a plain `char` as a single-character keyword-or-string. Identifier
+/// characters (e.g. `A`) are accepted as bare keywords; all other values use a
+/// quoted+escaped string with two-digit hex escapes (e.g. `"\0A"` for newline),
+/// covering all 256 byte values. Named escapes such as `\n` are also accepted
+/// as input but are not the canonical printed form.
+template <>
+struct FieldParser<char> {
+  static FailureOr<char> parse(AsmParser &parser) {
+    std::string str;
+    auto loc = parser.getCurrentLocation();
+    if (parser.parseKeywordOrString(&str))
+      return failure();
+    if (str.size() != 1)
+      return parser.emitError(loc, "expected a single character");
+    return str[0];
+  }
+};
+
+/// Parse a plain `char` wrapped in optional — delegates to FieldParser<char>
+/// so that optional char params use keyword-or-string, matching the printer.
+template <>
+struct FieldParser<std::optional<char>> {
+  static constexpr bool isKeyValueCompositional = false;
+
+  static FailureOr<std::optional<char>> parse(AsmParser &parser) {
+    auto result = FieldParser<char>::parse(parser);
+    if (failed(result))
+      return failure();
+    return std::optional<char>(*result);
+  }
+};
+
 /// Parse any integer.
 template <typename IntT>
 struct FieldParser<IntT, std::enable_if_t<(std::is_integral<IntT>::value ||
diff --git a/mlir/include/mlir/IR/OpImplementation.h b/mlir/include/mlir/IR/OpImplementation.h
index ad5e43868e86c0..9c8a269777e103 100644
--- a/mlir/include/mlir/IR/OpImplementation.h
+++ b/mlir/include/mlir/IR/OpImplementation.h
@@ -218,6 +218,18 @@ class AsmPrinter {
     *this << attrOrType;
   }
 
+  /// Print `uint8_t`/`int8_t` parameters numerically. Streaming them through
+  /// `operator<<` would reach `raw_ostream`'s `char` overloads and emit the
+  /// byte as a glyph.
+  void printStrippedAttrOrType(uint8_t value) { printInteger(value); }
+  void printStrippedAttrOrType(int8_t value) { printInteger(value); }
+  /// Print a plain `char` parameter as a bare keyword for identifier characters
+  /// (e.g. `A`) or as a quoted+escaped string for everything else, so that all
+  /// 256 byte values round-trip correctly.
+  void printStrippedAttrOrType(char value) {
+    printKeywordOrString(StringRef(&value, 1));
+  }
+
   /// Print the given attribute without its type. The corresponding parser must
   /// provide a valid type for the attribute.
   virtual void printAttributeWithoutType(Attribute attr);
diff --git a/mlir/test/lib/Dialect/Test/TestAttrDefs.td b/mlir/test/lib/Dialect/Test/TestAttrDefs.td
index 2ec17e06628c9c..46a860330fce6f 100644
--- a/mlir/test/lib/Dialect/Test/TestAttrDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestAttrDefs.td
@@ -228,6 +228,20 @@ def TestAttrWithOptionalUnsigned : Test_Attr<"TestAttrWithOptionalUnsigned"> {
   let mnemonic = "attr_with_optional_unsigned";
 }
 
+def TestAttrWithInt8Params : Test_Attr<"TestAttrWithInt8Params"> {
+  let parameters = (ins "uint8_t":$unsigned8, "int8_t":$signed8);
+  let assemblyFormat = "`<` $unsigned8 `,` $signed8 `>`";
+  let mnemonic = "attr_with_int8_params";
+}
+
+// Test that plain `char` parameters print as bare keywords or quoted strings,
+// not as integers (unlike uint8_t/int8_t which are widened numerically).
+def TestAttrWithCharParam : Test_Attr<"TestAttrWithCharParam"> {
+  let parameters = (ins "char":$value);
+  let assemblyFormat = "`<` $value `>`";
+  let mnemonic = "attr_with_char_param";
+}
+
 def TestAttrWithOptionalEnum : Test_Attr<"TestAttrWithOptionalEnum"> {
   let parameters = (ins OptionalParameter<"std::optional<SimpleEnum>">:$value);
   let assemblyFormat = "`<` $value `>`";
diff --git a/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir b/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
index 5a9ce635408800..d38342e04258dd 100644
--- a/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
+++ b/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
@@ -37,6 +37,13 @@ attributes {
   attr_14 = #test.simple_enum<"+">,
   // CHECK: #test.simple_enum<"dash-separated-sentence">
   attr_15 = #test.simple_enum<"dash-separated-sentence">,
+  // CHECK: #test.attr_with_int8_params<200, -100>
+  attr_16 = #test.attr_with_int8_params<200, -100>,
+  // Test that chars use the correct overload and print as supposed to.
+  // CHECK: #test.attr_with_char_param<A>
+  attr_17 = #test.attr_with_char_param<A>,
+  // CHECK: #test.attr_with_char_param<"\0A">
+  attr_18 = #test.attr_with_char_param<"\0A">,
   // Test that ArrayRefParameter in non-last struct position is wrapped in
   // brackets to avoid ambiguity with the struct-level comma (issue #156623).
   // CHECK: #test.arr_struct<elements = [1, 2, 3], count = 42>

>From 4d31594fe22e909a3fab25c86627b2c727253376 Mon Sep 17 00:00:00 2001
From: Robert Konicar <robert.konicar at nextsilicon.com>
Date: Thu, 8 Oct 2026 10:15:27 +0200
Subject: [PATCH 2/2] fixup! [MLIR] Print (u)int8_t args as numbers and
 properly support char args

Adds printing overloads for 8-bit integers to make sure that the
tablegen generated assemblyFormat printer prints them as numbers
in parameters instead of resolving to the raw_ostream char overload.

Adds support for printing/parsing raw char arguments to make sure that
it is possible to have real glyphs as arguments.

Note that this is also an API-level behaviour change for hand-written
printers and parsers: streaming an `int8_t`/`uint8_t` into an
`AsmPrinter` now prints a decimal number instead of a raw character,
and `char` parameters are now parsed and printed as a single-character
keyword or quoted string instead of an integer.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 mlir/include/mlir/IR/DialectImplementation.h  | 42 +++++++++--------
 mlir/include/mlir/IR/OpImplementation.h       | 44 ++++++++++++++---
 mlir/test/lib/Dialect/Test/TestAttrDefs.td    | 24 ++++++++++
 .../attr-or-type-format-roundtrip.mlir        | 47 ++++++++++++++++++-
 .../test/mlir-tblgen/attr-or-type-format.mlir | 33 +++++++++++++
 5 files changed, 164 insertions(+), 26 deletions(-)

diff --git a/mlir/include/mlir/IR/DialectImplementation.h b/mlir/include/mlir/IR/DialectImplementation.h
index c888f81a66a94e..f68bb43fa8aab8 100644
--- a/mlir/include/mlir/IR/DialectImplementation.h
+++ b/mlir/include/mlir/IR/DialectImplementation.h
@@ -110,35 +110,39 @@ struct FieldParser<
   }
 };
 
-/// Parse a plain `char` as a single-character keyword-or-string. Identifier
-/// characters (e.g. `A`) are accepted as bare keywords; all other values use a
-/// quoted+escaped string with two-digit hex escapes (e.g. `"\0A"` for newline),
-/// covering all 256 byte values. Named escapes such as `\n` are also accepted
-/// as input but are not the canonical printed form.
+/// Parse an optional plain `char` as a single-character keyword-or-string.
+/// Identifier characters (e.g. `A`) are accepted as bare keywords; all other
+/// values use a quoted+escaped string with two-digit hex escapes (e.g. `"\0A"`
+/// for newline), covering all 256 byte values. Named escapes such as `\n` are
+/// also accepted as input but are not the canonical printed form. Returns
+/// `std::nullopt` if no keyword or string is present.
 template <>
-struct FieldParser<char> {
-  static FailureOr<char> parse(AsmParser &parser) {
+struct FieldParser<std::optional<char>> {
+  static constexpr bool isKeyValueCompositional = false;
+
+  static FailureOr<std::optional<char>> parse(AsmParser &parser) {
     std::string str;
     auto loc = parser.getCurrentLocation();
-    if (parser.parseKeywordOrString(&str))
-      return failure();
+    if (failed(parser.parseOptionalKeywordOrString(&str)))
+      return {std::nullopt};
     if (str.size() != 1)
       return parser.emitError(loc, "expected a single character");
-    return str[0];
+    return {std::optional<char>(str[0])};
   }
 };
 
-/// Parse a plain `char` wrapped in optional — delegates to FieldParser<char>
-/// so that optional char params use keyword-or-string, matching the printer.
+/// Parse a plain `char` as a single-character keyword-or-string, see
+/// `FieldParser<std::optional<char>>`.
 template <>
-struct FieldParser<std::optional<char>> {
-  static constexpr bool isKeyValueCompositional = false;
-
-  static FailureOr<std::optional<char>> parse(AsmParser &parser) {
-    auto result = FieldParser<char>::parse(parser);
-    if (failed(result))
+struct FieldParser<char> {
+  static FailureOr<char> parse(AsmParser &parser) {
+    std::string str;
+    auto loc = parser.getCurrentLocation();
+    if (failed(parser.parseKeywordOrString(&str)))
       return failure();
-    return std::optional<char>(*result);
+    if (str.size() != 1)
+      return parser.emitError(loc, "expected a single character");
+    return str[0];
   }
 };
 
diff --git a/mlir/include/mlir/IR/OpImplementation.h b/mlir/include/mlir/IR/OpImplementation.h
index 9c8a269777e103..e3f7c5d4732051 100644
--- a/mlir/include/mlir/IR/OpImplementation.h
+++ b/mlir/include/mlir/IR/OpImplementation.h
@@ -218,11 +218,6 @@ class AsmPrinter {
     *this << attrOrType;
   }
 
-  /// Print `uint8_t`/`int8_t` parameters numerically. Streaming them through
-  /// `operator<<` would reach `raw_ostream`'s `char` overloads and emit the
-  /// byte as a glyph.
-  void printStrippedAttrOrType(uint8_t value) { printInteger(value); }
-  void printStrippedAttrOrType(int8_t value) { printInteger(value); }
   /// Print a plain `char` parameter as a bare keyword for identifier characters
   /// (e.g. `A`) or as a quoted+escaped string for everything else, so that all
   /// 256 byte values round-trip correctly.
@@ -230,6 +225,25 @@ class AsmPrinter {
     printKeywordOrString(StringRef(&value, 1));
   }
 
+  /// Print an array of plain `char` parameters element-wise with the `char`
+  /// overload above, matching the element-wise `FieldParser<char>` used to
+  /// parse them.
+  void printStrippedAttrOrType(ArrayRef<char> values) {
+    llvm::interleaveComma(values, getStream(), [this](char value) {
+      printStrippedAttrOrType(value);
+    });
+  }
+
+  /// Print a present optional integer parameter through the overloads for its
+  /// underlying type, mirroring `FieldParser<std::optional<IntT>>`.
+  template <typename IntT,
+            std::enable_if_t<std::is_integral_v<IntT> &&
+                             !std::is_same_v<IntT, bool>> *sfinae = nullptr>
+  void printStrippedAttrOrType(std::optional<IntT> value) {
+    if (value)
+      printStrippedAttrOrType(*value);
+  }
+
   /// Print the given attribute without its type. The corresponding parser must
   /// provide a valid type for the attribute.
   virtual void printAttributeWithoutType(Attribute attr);
@@ -401,6 +415,23 @@ inline AsmPrinterT &operator<<(AsmPrinterT &p, double value) {
   return p << APFloat(value);
 }
 
+// Print `int8_t`/`uint8_t` numerically instead of reaching `raw_ostream`'s
+// character overloads. Plain `char` is still printed as a character.
+template <typename AsmPrinterT,
+          typename =
+              std::enable_if_t<std::is_base_of<AsmPrinter, AsmPrinterT>::value>>
+inline AsmPrinterT &operator<<(AsmPrinterT &p, int8_t value) {
+  p.printInteger(value);
+  return p;
+}
+template <typename AsmPrinterT,
+          typename =
+              std::enable_if_t<std::is_base_of<AsmPrinter, AsmPrinterT>::value>>
+inline AsmPrinterT &operator<<(AsmPrinterT &p, uint8_t value) {
+  p.printInteger(value);
+  return p;
+}
+
 // Support printing anything that isn't convertible to one of the other
 // streamable types, even if it isn't exactly one of them. For example, we want
 // to print FunctionType with the Type version above, not have it match this.
@@ -410,7 +441,8 @@ template <typename AsmPrinterT, typename T,
                                !std::is_convertible<T &, Attribute &>::value &&
                                !std::is_convertible<T &, ValueRange>::value &&
                                !std::is_convertible<T &, APFloat &>::value &&
-                               !llvm::is_one_of<T, bool, float, double>::value,
+                               !llvm::is_one_of<T, bool, float, double, int8_t,
+                                                uint8_t>::value,
                            T> * = nullptr,
           typename =
               std::enable_if_t<std::is_base_of<AsmPrinter, AsmPrinterT>::value>>
diff --git a/mlir/test/lib/Dialect/Test/TestAttrDefs.td b/mlir/test/lib/Dialect/Test/TestAttrDefs.td
index 46a860330fce6f..13119ecf601d6d 100644
--- a/mlir/test/lib/Dialect/Test/TestAttrDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestAttrDefs.td
@@ -242,6 +242,30 @@ def TestAttrWithCharParam : Test_Attr<"TestAttrWithCharParam"> {
   let mnemonic = "attr_with_char_param";
 }
 
+def TestAttrWithOptionalChar : Test_Attr<"TestAttrWithOptionalChar"> {
+  let parameters = (ins OptionalParameter<"std::optional<char>">:$value);
+  let assemblyFormat = "`<` $value `>`";
+  let mnemonic = "attr_with_optional_char";
+}
+
+def TestAttrWithOptionalUInt8 : Test_Attr<"TestAttrWithOptionalUInt8"> {
+  let parameters = (ins OptionalParameter<"std::optional<uint8_t>">:$value);
+  let assemblyFormat = "`<` $value `>`";
+  let mnemonic = "attr_with_optional_uint8";
+}
+
+def TestAttrWithUInt8Array : Test_Attr<"TestAttrWithUInt8Array"> {
+  let parameters = (ins ArrayRefParameter<"uint8_t">:$values);
+  let assemblyFormat = "`<` `[` $values `]` `>`";
+  let mnemonic = "attr_with_uint8_array";
+}
+
+def TestAttrWithCharArray : Test_Attr<"TestAttrWithCharArray"> {
+  let parameters = (ins ArrayRefParameter<"char">:$values);
+  let assemblyFormat = "`<` `[` $values `]` `>`";
+  let mnemonic = "attr_with_char_array";
+}
+
 def TestAttrWithOptionalEnum : Test_Attr<"TestAttrWithOptionalEnum"> {
   let parameters = (ins OptionalParameter<"std::optional<SimpleEnum>">:$value);
   let assemblyFormat = "`<` $value `>`";
diff --git a/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir b/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
index d38342e04258dd..62ed9fb12d85f0 100644
--- a/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
+++ b/mlir/test/mlir-tblgen/attr-or-type-format-roundtrip.mlir
@@ -39,11 +39,56 @@ attributes {
   attr_15 = #test.simple_enum<"dash-separated-sentence">,
   // CHECK: #test.attr_with_int8_params<200, -100>
   attr_16 = #test.attr_with_int8_params<200, -100>,
-  // Test that chars use the correct overload and print as supposed to.
+  // Test that chars print as a bare keyword or a quoted string, not a number.
   // CHECK: #test.attr_with_char_param<A>
   attr_17 = #test.attr_with_char_param<A>,
   // CHECK: #test.attr_with_char_param<"\0A">
   attr_18 = #test.attr_with_char_param<"\0A">,
+  // CHECK: #test.attr_with_int8_params<255, -128>
+  attr_19 = #test.attr_with_int8_params<255, -128>,
+  // CHECK: #test.attr_with_int8_params<0, 0>
+  attr_20 = #test.attr_with_int8_params<0, 0>,
+  // Characters that need escaping and a digit, which is not a bare identifier.
+  // CHECK: #test.attr_with_char_param<"\22">
+  attr_21 = #test.attr_with_char_param<"\22">,
+  // CHECK: #test.attr_with_char_param<"\\">
+  attr_22 = #test.attr_with_char_param<"\\">,
+  // CHECK: #test.attr_with_char_param<"5">
+  attr_23 = #test.attr_with_char_param<"5">,
+  // CHECK: #test.attr_with_optional_char<A>
+  attr_24 = #test.attr_with_optional_char<A>,
+  // CHECK: #test.attr_with_optional_char<>
+  attr_25 = #test.attr_with_optional_char<>,
+  // CHECK: #test.attr_with_optional_uint8<200>
+  attr_26 = #test.attr_with_optional_uint8<200>,
+  // CHECK: #test.attr_with_optional_uint8<>
+  attr_27 = #test.attr_with_optional_uint8<>,
+  // CHECK: #test.attr_with_uint8_array<[0, 65, 255]>
+  attr_28 = #test.attr_with_uint8_array<[0, 65, 255]>,
+  // CHECK: #test.attr_with_char_array<[A, "\0A", "5", ","]>
+  attr_29 = #test.attr_with_char_array<[A, "\0A", "5", ","]>,
+  // `_` is a valid bare identifier even though it is not a letter.
+  // CHECK: #test.attr_with_char_param<_>
+  attr_30 = #test.attr_with_char_param<_>,
+  // Named escapes are accepted as input but are canonicalized to hex escapes.
+  // CHECK: #test.attr_with_char_param<"\0A">
+  attr_31 = #test.attr_with_char_param<"\n">,
+  // CHECK: #test.attr_with_char_param<"\09">
+  attr_32 = #test.attr_with_char_param<"\t">,
+  // The NUL byte and bytes >= 0x80 round-trip through hex escapes.
+  // CHECK: #test.attr_with_char_param<"\00">
+  attr_33 = #test.attr_with_char_param<"\00">,
+  // CHECK: #test.attr_with_char_param<"\80">
+  attr_34 = #test.attr_with_char_param<"\80">,
+  // CHECK: #test.attr_with_char_param<"\FF">
+  attr_35 = #test.attr_with_char_param<"\FF">,
+  // Escaped values through the optional and array paths.
+  // CHECK: #test.attr_with_optional_char<"\FF">
+  attr_36 = #test.attr_with_optional_char<"\FF">,
+  // CHECK: #test.attr_with_optional_char<"\0A">
+  attr_37 = #test.attr_with_optional_char<"\n">,
+  // CHECK: #test.attr_with_char_array<[_, "\00", "\0A", "\80", "\FF", "\22"]>
+  attr_38 = #test.attr_with_char_array<[_, "\00", "\n", "\80", "\FF", "\22"]>,
   // Test that ArrayRefParameter in non-last struct position is wrapped in
   // brackets to avoid ambiguity with the struct-level comma (issue #156623).
   // CHECK: #test.arr_struct<elements = [1, 2, 3], count = 42>
diff --git a/mlir/test/mlir-tblgen/attr-or-type-format.mlir b/mlir/test/mlir-tblgen/attr-or-type-format.mlir
index 907aad369a3444..891a01ccc4eb02 100644
--- a/mlir/test/mlir-tblgen/attr-or-type-format.mlir
+++ b/mlir/test/mlir-tblgen/attr-or-type-format.mlir
@@ -133,3 +133,36 @@ func.func private @test_attr_with_type_failed_to_parse_type() -> () attributes {
   // expected-error at +1 {{failed to parse TestAttrWithTypeParam parameter 'int_type'}}
   attr = #test.attr_with_type<vector<4xi32>, vector<4xi32>>
 }
+
+// -----
+
+func.func private @test_char_param_multi_char_string() -> () attributes {
+  // expected-error at +2 {{expected a single character}}
+  // expected-error at +1 {{failed to parse TestAttrWithCharParam parameter 'value' which is to be a `char`}}
+  attr = #test.attr_with_char_param<"ab">
+}
+
+// -----
+
+func.func private @test_char_param_multi_char_keyword() -> () attributes {
+  // expected-error at +2 {{expected a single character}}
+  // expected-error at +1 {{failed to parse TestAttrWithCharParam parameter 'value' which is to be a `char`}}
+  attr = #test.attr_with_char_param<abc>
+}
+
+// -----
+
+// A digit is neither a keyword nor a string, so it must be quoted.
+func.func private @test_char_param_integer() -> () attributes {
+  // expected-error at +2 {{expected valid keyword or string}}
+  // expected-error at +1 {{failed to parse TestAttrWithCharParam parameter 'value' which is to be a `char`}}
+  attr = #test.attr_with_char_param<5>
+}
+
+// -----
+
+func.func private @test_char_param_missing() -> () attributes {
+  // expected-error at +2 {{expected valid keyword or string}}
+  // expected-error at +1 {{failed to parse TestAttrWithCharParam parameter 'value' which is to be a `char`}}
+  attr = #test.attr_with_char_param<>
+}



More information about the Mlir-commits mailing list