[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