[Mlir-commits] [mlir] d5fa31c - [MLIR][ODS] Add enum operation format directive (#220412)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Sep 2 05:53:11 PDT 2026


Author: Mehdi Amini
Date: 2026-09-02T14:53:06+02:00
New Revision: d5fa31c3fa90a1b1ed4ee6d725d219d70accc002

URL: https://github.com/llvm/llvm-project/commit/d5fa31c3fa90a1b1ed4ee6d725d219d70accc002
DIFF: https://github.com/llvm/llvm-project/commit/d5fa31c3fa90a1b1ed4ee6d725d219d70accc002.diff

LOG: [MLIR][ODS] Add enum operation format directive (#220412)

Today enum handling is problematic: their format is defined manually
often with `<$value>` so that their generic printing is
`#dialect.mnemnonic<value>` instead of `#dialect<mnemnonic value>`.
However that prevents the Operation ODS assembly format from getting
just the value without the surrounding `<` `>`.

Add enum($attr) to declarative operation assembly formats. The directive
parses and prints the symbolic enum value independently of the
attribute's custom assembly format. This lets standalone attributes
retain delimiters while operations use a bare value.

Validate directive operands during TableGen processing and retain
keyword and quoted-string handling. Add separator-aware parsing and
unquoted printing for comma- and vertical-bar-separated bit enums.

Document the directive and add generated-code, diagnostic,
compatibility, and runtime round-trip coverage.

Assisted-by: Codex

Added: 
    

Modified: 
    mlir/docs/DefiningDialects/Operations.md
    mlir/test/IR/attribute.mlir
    mlir/test/IR/enum-attr-invalid.mlir
    mlir/test/IR/enum-attr-roundtrip.mlir
    mlir/test/lib/Dialect/Test/TestEnumDefs.td
    mlir/test/lib/Dialect/Test/TestOps.td
    mlir/test/mlir-tblgen/op-format-invalid.td
    mlir/test/mlir-tblgen/op-format.td
    mlir/tools/mlir-tblgen/FormatGen.cpp
    mlir/tools/mlir-tblgen/FormatGen.h
    mlir/tools/mlir-tblgen/OpFormatGen.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index 1125a280a589c..4d55148827aa0 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -858,6 +858,18 @@ The available directives are as follows:
         declarative assembly format will print it instead as:
         `vector.multi_reduction #vector.kind<minf>, ...`.
 
+*   `enum ( attribute )`
+
+    -   Represents the symbolic value of an enum-backed attribute without the
+        attribute's dialect prefix, mnemonic, or custom assembly format.
+    -   The argument must be an enum attribute. For example, if `$kind` has the
+        complete form `#vector.kind<minf>`, `enum($kind)` prints `minf`, `$kind`
+        prints the attribute's assembly-format body `<minf>`, and
+        `qualified($kind)` prints the complete attribute `#vector.kind<minf>`.
+    -   Bit enums must define a zero-valued case so every valid bitmask has a
+        symbolic spelling. An unquoted comma-separated bit enum cannot be
+        followed by a comma literal because the two uses would be ambiguous.
+
 #### Literals
 
 A literal is either a keyword or punctuation surrounded by \`\`.

diff  --git a/mlir/test/IR/attribute.mlir b/mlir/test/IR/attribute.mlir
index b6b57e2d587ce..7448ba9ddf6a3 100644
--- a/mlir/test/IR/attribute.mlir
+++ b/mlir/test/IR/attribute.mlir
@@ -543,7 +543,7 @@ func.func @allowed_cases_pass() {
 // -----
 
 func.func @disallowed_case_sticky_fail() {
-  // expected-error at +2 {{expected one of [read, write, execute] for a test bit enum, got: sticky}}
+  // expected-error at +2 {{expected one of [none, read, write, execute] for a test bit enum, got: sticky}}
   // expected-error at +1 {{failed to parse TestBitEnumAttr}}
   "test.op_with_bit_enum"() {value = #test.bit_enum<sticky>} : () -> ()
 }

diff  --git a/mlir/test/IR/enum-attr-invalid.mlir b/mlir/test/IR/enum-attr-invalid.mlir
index 7f9a80737480e..0702da66c20b3 100644
--- a/mlir/test/IR/enum-attr-invalid.mlir
+++ b/mlir/test/IR/enum-attr-invalid.mlir
@@ -62,7 +62,7 @@ func.func @test_bit_enum_prop_not_keyword() -> () {
 // -----
 
 func.func @test_bit_enum_prop_wrong_keyword() -> () {
-  // expected-error at +2 {{expected one of [read, write, execute] for a test bit enum, got: chroot}}
+  // expected-error at +2 {{expected one of [none, read, write, execute] for a test bit enum, got: chroot}}
   // expected-error at +1 {{invalid value for property value1, expected a test bit enum}}
   test.op_with_bit_enum_prop read, chroot : ()
   return

diff  --git a/mlir/test/IR/enum-attr-roundtrip.mlir b/mlir/test/IR/enum-attr-roundtrip.mlir
index 113b5f07ee6f3..2ce5680226765 100644
--- a/mlir/test/IR/enum-attr-roundtrip.mlir
+++ b/mlir/test/IR/enum-attr-roundtrip.mlir
@@ -18,6 +18,15 @@ func.func @test_op_with_enum() -> () {
   return
 }
 
+// CHECK-LABEL: @test_op_with_enum_directive
+func.func @test_op_with_enum_directive() -> () {
+  // CHECK: test.op_with_enum_directive third
+  test.op_with_enum_directive third
+  // CHECK: test.op_with_enum_directive "non-keyword"
+  test.op_with_enum_directive "non-keyword"
+  return
+}
+
 // CHECK-LABEL: @test_match_op_with_enum
 func.func @test_match_op_with_enum() -> () {
   // CHECK: test.op_with_enum third tag 0 : i32
@@ -36,6 +45,19 @@ func.func @test_match_op_with_bit_enum() -> () {
   return
 }
 
+// CHECK-LABEL: @test_op_with_bit_enum_directive
+func.func @test_op_with_bit_enum_directive() -> () {
+  // CHECK: test.op_with_bit_enum_directive none
+  test.op_with_bit_enum_directive none
+  // CHECK: test.op_with_bit_enum_directive read, execute
+  test.op_with_bit_enum_directive read, execute
+  // CHECK: test.op_with_bit_enum_vbar_directive none
+  test.op_with_bit_enum_vbar_directive none
+  // CHECK: test.op_with_bit_enum_vbar_directive user | other
+  test.op_with_bit_enum_vbar_directive user | other
+  return
+}
+
 // CHECK-LABEL: @test_enum_prop
 func.func @test_enum_prop() -> () {
   // CHECK: test.op_with_enum_prop first

diff  --git a/mlir/test/lib/Dialect/Test/TestEnumDefs.td b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
index 5d7c60d53c2c0..98b8823f73ef5 100644
--- a/mlir/test/lib/Dialect/Test/TestEnumDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
@@ -56,6 +56,16 @@ def TestOtherEnum
   let cppNamespace = "test";
 }
 
+def TestPrettyEnum
+    : I32Enum<"TestPrettyEnum", "an enum with bracketed attribute syntax", [
+        I32EnumCase<"First", 0, "first">,
+        I32EnumCase<"Second", 1, "second">,
+        I32EnumCase<"Third", 2, "third">,
+        I32EnumCase<"NonKeyword", 3, "non-keyword">,
+      ]> {
+  let cppNamespace = "test";
+}
+
 def TestSimpleEnum : I32Enum<"SimpleEnum", "", [
     I32EnumCase<"a", 0>,
     I32EnumCase<"b", 1>,
@@ -91,8 +101,10 @@ def TestSimpleEnum64 : I64Enum<"SimpleEnum64", "", [
 //===----------------------------------------------------------------------===//
 
 // Define the C++ enum.
+def TestBitEnumNone : I32BitEnumCaseNone<"None", "none">;
 def TestBitEnum
     : I32BitEnum<"TestBitEnum", "a test bit enum", [
+        TestBitEnumNone,
         I32BitEnumCaseBit<"Read", 0, "read">,
         I32BitEnumCaseBit<"Write", 1, "write">,
         I32BitEnumCaseBit<"Execute", 2, "execute">,
@@ -104,6 +116,7 @@ def TestBitEnum
 // Define an enum with a 
diff erent separator
 def TestBitEnumVerticalBar
     : I32BitEnum<"TestBitEnumVerticalBar", "another test bit enum", [
+        TestBitEnumNone,
         I32BitEnumCaseBit<"User", 0, "user">,
         I32BitEnumCaseBit<"Group", 1, "group">,
         I32BitEnumCaseBit<"Other", 2, "other">,

diff  --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index f966025dfded3..3060de7ac2b4b 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -489,12 +489,24 @@ def PromisedInterfacesOp : TEST_Op<"promised_interfaces"> {
 // Define the enum attribute.
 def TestEnumAttr : EnumAttr<Test_Dialect, TestEnum, "enum">;
 
+def TestPrettyEnumAttr
+    : EnumAttr<Test_Dialect, TestPrettyEnum, "pretty_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
 // Define an op that contains the enum attribute.
 def OpWithEnum : TEST_Op<"op_with_enum"> {
   let arguments = (ins TestEnumAttr:$value, OptionalAttr<AnyAttr>:$tag);
   let assemblyFormat = "$value (`tag` $tag^)? attr-dict";
 }
 
+// Define an op that prints just the symbolic value of an enum attribute whose
+// own assembly format includes angle brackets.
+def OpWithEnumDirective : TEST_Op<"op_with_enum_directive"> {
+  let arguments = (ins TestPrettyEnumAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
 // Define a pattern that matches and creates an enum attribute.
 def : Pat<(OpWithEnum ConstantEnumCase<TestEnumAttr, "first">:$value,
                       ConstantAttr<I32Attr, "0">:$tag),
@@ -594,6 +606,17 @@ def OpWithBitEnumVerticalBar : TEST_Op<"op_with_bit_enum_vbar"> {
   let assemblyFormat = "$value (`tag` $tag^)? attr-dict";
 }
 
+def OpWithBitEnumDirective : TEST_Op<"op_with_bit_enum_directive"> {
+  let arguments = (ins TestBitEnumAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
+def OpWithBitEnumVerticalBarDirective
+    : TEST_Op<"op_with_bit_enum_vbar_directive"> {
+  let arguments = (ins TestBitEnumVerticalBarAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
 // Define a pattern that matches and creates a bit enum attribute.
 def : Pat<(OpWithBitEnum ConstantEnumCase<TestBitEnumAttr, "write|execute">,
                          ConstantAttr<I32Attr, "0">),

diff  --git a/mlir/test/mlir-tblgen/op-format-invalid.td b/mlir/test/mlir-tblgen/op-format-invalid.td
index 38c8d57a342b6..c13bbedce3785 100644
--- a/mlir/test/mlir-tblgen/op-format-invalid.td
+++ b/mlir/test/mlir-tblgen/op-format-invalid.td
@@ -3,6 +3,7 @@
 // This file contains tests for the specification of the declarative op format.
 
 include "mlir/IR/OpBase.td"
+include "mlir/IR/EnumAttr.td"
 include "mlir/Interfaces/InferTypeOpInterface.td"
 
 def TestDialect : Dialect {
@@ -25,6 +26,79 @@ class TestStrictPropertiesFormat_Op<string fmt, list<Trait> traits = []>
 // Directives
 //===----------------------------------------------------------------------===//
 
+//===----------------------------------------------------------------------===//
+// enum
+//===----------------------------------------------------------------------===//
+
+def DirectiveEnumCase : I32EnumCase<"Case", 0>;
+def DirectiveEnum : I32Enum<"DirectiveEnum", "test enum",
+                            [DirectiveEnumCase]>;
+def DirectiveEnumAttr : EnumAttr<TestDialect, DirectiveEnum, "enum">;
+
+def DirectiveUnbuildableEnumAttr
+    : EnumAttr<TestDialect, DirectiveEnum, "unbuildable_enum"> {
+  let constBuilderCall = "";
+}
+
+def DirectiveBitEnumNone : I32BitEnumCaseNone<"None", "none">;
+def DirectiveBitEnumCase : I32BitEnumCaseBit<"Case", 0, "case">;
+def DirectiveCommaBitEnum
+    : I32BitEnum<"DirectiveCommaBitEnum", "comma bit enum",
+                 [DirectiveBitEnumNone, DirectiveBitEnumCase]> {
+  let cppNamespace = "::test";
+  let separator = ", ";
+}
+def DirectiveCommaBitEnumAttr
+    : EnumAttr<TestDialect, DirectiveCommaBitEnum, "comma_bit_enum">;
+
+def DirectiveNoZeroBitEnum
+    : I32BitEnum<"DirectiveNoZeroBitEnum", "bit enum without zero",
+                 [DirectiveBitEnumCase]> {
+  let cppNamespace = "::test";
+}
+def DirectiveNoZeroBitEnumAttr
+    : EnumAttr<TestDialect, DirectiveNoZeroBitEnum, "no_zero_bit_enum">;
+
+// CHECK: error: 'enum' directive expects an enum attribute
+def DirectiveAEnumInvalidA : TestFormat_Op<[{
+  enum($attr) attr-dict
+}]>, Arguments<(ins I64Attr:$attr)>;
+
+// CHECK: error: 'enum' directive expects an enum attribute
+def DirectiveAEnumInvalidB : TestFormat_Op<[{
+  enum($operand) attr-dict
+}]>, Arguments<(ins I64:$operand)>;
+
+// CHECK: error: 'enum' and 'qualified' directives cannot be combined
+def DirectiveAEnumInvalidC : TestFormat_Op<[{
+  qualified(enum($attr)) attr-dict
+}]>, Arguments<(ins DirectiveEnumAttr:$attr)>;
+
+// CHECK: error: 'enum' and 'qualified' directives cannot be combined
+def DirectiveAEnumInvalidD : TestFormat_Op<[{
+  enum(qualified($attr)) attr-dict
+}]>, Arguments<(ins DirectiveEnumAttr:$attr)>;
+
+// CHECK: error: 'enum' directive can only be used at the top level
+def DirectiveAEnumInvalidE : TestFormat_Op<[{
+  custom<Foo>(enum($attr)) attr-dict
+}]>, Arguments<(ins DirectiveEnumAttr:$attr)>;
+
+// CHECK: error: 'enum' directive requires an enum attribute with an underlying type and a constant builder
+def DirectiveAEnumInvalidF : TestFormat_Op<[{
+  enum($attr) attr-dict
+}]>, Arguments<(ins DirectiveUnbuildableEnumAttr:$attr)>;
+
+// CHECK: error: 'enum' directive requires a bit enum to define a zero-valued case
+def DirectiveAEnumInvalidG : TestFormat_Op<[{
+  enum($attr) attr-dict
+}]>, Arguments<(ins DirectiveNoZeroBitEnumAttr:$attr)>;
+
+// CHECK: error: unquoted bit enum cannot be followed by its ',' separator literal
+def DirectiveAEnumInvalidH : TestFormat_Op<[{
+  enum($attr) `,` attr-dict
+}]>, Arguments<(ins DirectiveCommaBitEnumAttr:$attr)>;
+
 //===----------------------------------------------------------------------===//
 // attr-dict
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/test/mlir-tblgen/op-format.td b/mlir/test/mlir-tblgen/op-format.td
index 530005ca4973f..baded36fa7b3e 100644
--- a/mlir/test/mlir-tblgen/op-format.td
+++ b/mlir/test/mlir-tblgen/op-format.td
@@ -109,6 +109,11 @@ def CustomStringLiteralD : TestFormat_Op<[{
   custom<Foo>(prop-dict) attr-dict
 }]>;
 
+// A directive keyword can still name a custom directive.
+def CustomEnumKeyword : TestFormat_Op<[{
+  custom<enum>($attr) attr-dict
+}]>, Arguments<(ins I64Attr:$attr)>;
+
 //===----------------------------------------------------------------------===//
 // EnumAttr formatting
 //===----------------------------------------------------------------------===//
@@ -125,6 +130,21 @@ def TestEnum : I32Enum<"TestEnum", "a test enum", [TestEnumCase0, TestEnumCase1]
 
 def TestEnumAttr : EnumAttr<TestDialect, TestEnum, "enum">;
 
+def TestPrettyEnumAttr : EnumAttr<TestDialect, TestEnum, "pretty_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
+def TestNonKeywordEnumCase : I32EnumCase<"NonKeyword", 0, "non-keyword">;
+def TestNonKeywordEnum
+    : I32Enum<"TestNonKeywordEnum", "a non-keyword enum",
+              [TestNonKeywordEnumCase]> {
+  let cppNamespace = "::test";
+}
+def TestNonKeywordEnumAttr
+    : EnumAttr<TestDialect, TestNonKeywordEnum, "non_keyword_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
 // Default-valued optional attributes have a non-optional getter.
 
 // CHECK-LABEL: DefaultOptionalEnumAttrOp::print
@@ -164,6 +184,40 @@ def TestBitEnumAttr : EnumAttr<TestDialect, TestBitEnum, "bit_enum">;
 def EnumAttrUnquotedBitOp : TestFormat_Op<"$attr attr-dict">,
     Arguments<(ins TestBitEnumAttr:$attr)>;
 
+// Non-keyword enum values use the quoted-string fallback.
+
+// CHECK-LABEL: EnumDirectiveNonKeywordOp::parse
+// CHECK: parser.parseOptionalKeyword(&attrStr, {})
+// CHECK: parser.parseOptionalAttribute
+// CHECK-LABEL: EnumDirectiveNonKeywordOp::print
+// CHECK: _odsPrinter << '"' << caseValueStr << '"'
+def EnumDirectiveNonKeywordOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestNonKeywordEnumAttr:$attr)>;
+
+// The enum directive formats the symbolic enum value independently of the
+// attribute's custom assembly format.
+
+// CHECK-LABEL: EnumDirectiveOp::parse
+// CHECK: symbolizeTestEnum
+// CHECK-NOT: parseCustomAttributeWithFallback
+// CHECK-LABEL: EnumDirectiveOp::print
+// CHECK: stringifyTestEnum
+// CHECK-NOT: printStrippedAttrOrType
+def EnumDirectiveOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestPrettyEnumAttr:$attr)>;
+
+// The enum directive preserves the separator-aware parser and unquoted printer
+// for an unquoted bit enum.
+
+// CHECK-LABEL: EnumDirectiveUnquotedBitOp::parse
+// CHECK: symbolizeTestBitEnum
+// CHECK: parseOptionalVerticalBar
+// CHECK-LABEL: EnumDirectiveUnquotedBitOp::print
+// CHECK: stringifyTestBitEnum
+// CHECK: _odsPrinter << caseValueStr
+def EnumDirectiveUnquotedBitOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestBitEnumAttr:$attr)>;
+
 // Test that legacy EnumAttrInfo-based attributes (I32EnumAttr) also use the
 // enum-keyword format path.
 
@@ -182,6 +236,31 @@ def LegacyTestEnum : I32EnumAttr<"LegacyTestEnum", "a legacy test enum",
 def LegacyEnumAttrOp : TestFormat_Op<"$attr attr-dict">,
     Arguments<(ins LegacyTestEnum:$attr)>;
 
+// Explicit enum formatting must not change the existing parser and printer
+// selected for legacy EnumAttrInfo attributes written as plain variables.
+
+def TestLegacyBitEnumNone : I32BitEnumAttrCaseNone<"None", "none">;
+def TestLegacyBitEnumBit0 : I32BitEnumAttrCaseBit<"Bit0", 0, "bit0">;
+def TestLegacyBitEnumBit1 : I32BitEnumAttrCaseBit<"Bit1", 1, "bit1">;
+def TestLegacyUnquotedBitEnum
+    : I32BitEnumAttr<"TestLegacyUnquotedBitEnum", "a legacy bit enum",
+                     [TestLegacyBitEnumNone, TestLegacyBitEnumBit0,
+                      TestLegacyBitEnumBit1]> {
+  let cppNamespace = "::test";
+  let printBitEnumQuoted = 0;
+}
+
+// CHECK-LABEL: LegacyUnquotedBitEnumOp::parse
+// CHECK: parser.parseOptionalKeyword
+// CHECK-NOT: parseOptionalVerticalBar
+// CHECK-NOT: parseOptionalComma
+// CHECK-LABEL: LegacyUnquotedBitEnumOp::print
+// CHECK: switch (caseValue)
+// CHECK: default:
+// CHECK-NEXT: _odsPrinter << '"' << caseValueStr << '"'
+def LegacyUnquotedBitEnumOp : TestFormat_Op<"$attr attr-dict">,
+    Arguments<(ins TestLegacyUnquotedBitEnum:$attr)>;
+
 //===----------------------------------------------------------------------===//
 // Optional Groups
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/tools/mlir-tblgen/FormatGen.cpp b/mlir/tools/mlir-tblgen/FormatGen.cpp
index 04d3ed1f3b70d..31843fabd5f4d 100644
--- a/mlir/tools/mlir-tblgen/FormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/FormatGen.cpp
@@ -180,6 +180,7 @@ FormatToken FormatLexer::lexIdentifier(const char *tokStart) {
           .Case("attr-dict-with-keyword", FormatToken::kw_attr_dict_w_keyword)
           .Case("prop-dict", FormatToken::kw_prop_dict)
           .Case("custom", FormatToken::kw_custom)
+          .Case("enum", FormatToken::kw_enum)
           .Case("functional-type", FormatToken::kw_functional_type)
           .Case("oilist", FormatToken::kw_oilist)
           .Case("operands", FormatToken::kw_operands)
@@ -405,13 +406,17 @@ FailureOr<FormatElement *> FormatParser::parseCustomDirective(SMLoc loc,
                           "or within a `struct` directive");
   }
 
-  FailureOr<FormatToken> nameTok;
   if (failed(parseToken(FormatToken::less,
-                        "expected '<' before custom directive name")) ||
-      failed(nameTok =
-                 parseToken(FormatToken::identifier,
-                            "expected custom directive name identifier")) ||
-      failed(parseToken(FormatToken::greater,
+                        "expected '<' before custom directive name")))
+    return failure();
+  // Preserve `custom<enum>` now that `enum` is also a directive keyword.
+  if (!curToken.is(FormatToken::identifier) &&
+      !curToken.is(FormatToken::kw_enum))
+    return emitError(curToken.getLoc(),
+                     "expected custom directive name identifier");
+  FormatToken nameTok = curToken;
+  consumeToken();
+  if (failed(parseToken(FormatToken::greater,
                         "expected '>' after custom directive name")) ||
       failed(parseToken(FormatToken::l_paren,
                         "expected '(' before custom directive parameters")))
@@ -435,7 +440,7 @@ FailureOr<FormatElement *> FormatParser::parseCustomDirective(SMLoc loc,
 
   if (failed(verifyCustomDirectiveArguments(loc, arguments)))
     return failure();
-  return create<CustomDirective>(nameTok->getSpelling(), std::move(arguments));
+  return create<CustomDirective>(nameTok.getSpelling(), std::move(arguments));
 }
 
 FailureOr<FormatElement *> FormatParser::parseRefDirective(SMLoc loc,

diff  --git a/mlir/tools/mlir-tblgen/FormatGen.h b/mlir/tools/mlir-tblgen/FormatGen.h
index 8e7d49bb37e71..b9600b9761992 100644
--- a/mlir/tools/mlir-tblgen/FormatGen.h
+++ b/mlir/tools/mlir-tblgen/FormatGen.h
@@ -61,6 +61,7 @@ class FormatToken {
     kw_attr_dict_w_keyword,
     kw_prop_dict,
     kw_custom,
+    kw_enum,
     kw_functional_type,
     kw_oilist,
     kw_operands,

diff  --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 9c849955ac5fd..29982d486b5c3 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -77,8 +77,13 @@ struct AttributeVariable
     shouldBeQualifiedFlag = qualified;
   }
 
+  /// Indicate if this attribute is formatted using its underlying enum value.
+  bool shouldFormatAsEnum() const { return shouldFormatAsEnumFlag; }
+  void setShouldFormatAsEnum() { shouldFormatAsEnumFlag = true; }
+
 private:
   bool shouldBeQualifiedFlag = false;
+  bool shouldFormatAsEnumFlag = false;
 };
 
 /// This class represents a variable that refers to an operand argument.
@@ -486,8 +491,8 @@ static bool canFormatEnumAttr(const NamedAttribute *attr) {
   EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
 
   // Unquoted bit enums may consist of multiple keywords separated by a comma
-  // or vertical bar. Their attribute parser handles this syntax, whereas the
-  // operation-level enum parser expects a single keyword or string.
+  // or vertical bar. Implicit formatting defers to the attribute parser;
+  // explicit `enum` directives select a separator-aware operation parser.
   if (baseAttr.isSubClassOf("EnumAttr") && enumInfo.isBitEnum() &&
       !enumInfo.printBitEnumQuoted())
     return false;
@@ -587,6 +592,44 @@ const char *const enumAttrParserCode = R"(
   }
 )";
 
+/// The code snippet used to generate a parser call for an unquoted bit enum
+/// attribute.
+///
+/// {0}: The name of the attribute.
+/// {1}: The C++ namespace for the enum symbolize functions.
+/// {2}: The function to symbolize a string of the enum.
+/// {3}: The constant builder call to create an attribute of the enum type.
+/// {4}: The set of allowed enum keywords.
+/// {5}: The error message on failure when the enum isn't present.
+/// {6}: The attribute assignment expression.
+/// {7}: The C++ enum type.
+/// {8}: The optional parser for the enum separator.
+const char *const unquotedBitEnumAttrParserCode = R"(
+  {
+    ::llvm::StringRef attrStr;
+    auto loc = parser.getCurrentLocation();
+    if (parser.parseOptionalKeyword(&attrStr, {4})) {
+      {5}
+    } else {
+      {7} flags = {{};
+      while (true) {
+        auto flag = {1}::{2}(attrStr);
+        if (!flag)
+          return parser.emitError(loc, "invalid ")
+                 << "{0} attribute specification: \"" << attrStr << '"';;
+        flags = flags | *flag;
+        if (failed(parser.{8}()))
+          break;
+        if (parser.parseKeyword(&attrStr))
+          return ::mlir::failure();
+      }
+      auto attrOptional = ::std::optional<{7}>(flags);
+      {0}Attr = {3};
+      {6}
+    }
+  }
+)";
+
 /// The code snippet used to generate a parser call for a property.
 /// {0}: The name of the property
 /// {1}: The property access expression
@@ -1216,7 +1259,8 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body,
 /// Generate the parser for a enum attribute.
 static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body,
                               FmtContext &attrTypeCtx, bool parseAsOptional,
-                              bool useProperties, StringRef opCppClassName) {
+                              bool useProperties, StringRef opCppClassName,
+                              bool formatAsEnumDirective) {
   Attribute baseAttr = var->attr.getBaseAttr();
   EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
   std::vector<EnumCase> cases = enumInfo.getAllCases();
@@ -1230,12 +1274,19 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body,
   }
 
   // Build a string containing the cases that can be formatted as a keyword.
-  std::string validCaseKeywordsStr = "{";
+  std::string validCaseKeywordsStr;
   llvm::raw_string_ostream validCaseKeywordsOS(validCaseKeywordsStr);
-  for (const EnumCase &attrCase : cases)
-    if (canFormatStringAsKeyword(attrCase.getStr()))
-      validCaseKeywordsOS << '"' << attrCase.getStr() << "\",";
-  validCaseKeywordsOS.str().back() = '}';
+  validCaseKeywordsOS << '{';
+  bool isFirstKeyword = true;
+  for (const EnumCase &attrCase : cases) {
+    if (!canFormatStringAsKeyword(attrCase.getStr()))
+      continue;
+    if (!isFirstKeyword)
+      validCaseKeywordsOS << ',';
+    isFirstKeyword = false;
+    validCaseKeywordsOS << '"' << attrCase.getStr() << '"';
+  }
+  validCaseKeywordsOS << '}';
 
   // If the attribute is not optional, build an error message for the missing
   // attribute.
@@ -1260,6 +1311,23 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body,
         formatv("result.addAttribute(\"{0}\", {0}Attr);", var->name);
   }
 
+  if (formatAsEnumDirective && enumInfo.isBitEnum() &&
+      !enumInfo.printBitEnumQuoted()) {
+    StringRef separator = enumInfo.getDef().getValueAsString("separator");
+    StringRef parseSeparatorFn = llvm::StringSwitch<StringRef>(separator.trim())
+                                     .Case("|", "parseOptionalVerticalBar")
+                                     .Case(",", "parseOptionalComma")
+                                     .Default("invalid bit enum separator");
+    std::string enumType =
+        (enumInfo.getCppNamespace() + "::" + enumInfo.getEnumClassName()).str();
+    body << formatv(unquotedBitEnumAttrParserCode, var->name,
+                    enumInfo.getCppNamespace(),
+                    enumInfo.getStringToSymbolFnName(), attrBuilderStr,
+                    validCaseKeywordsStr, errorMessage, attrAssignment,
+                    enumType, parseSeparatorFn);
+    return;
+  }
+
   body << formatv(enumAttrParserCode, var->name, enumInfo.getCppNamespace(),
                   enumInfo.getStringToSymbolFnName(), attrBuilderStr,
                   validCaseKeywordsStr, errorMessage, attrAssignment);
@@ -1296,9 +1364,10 @@ static void genAttrParser(AttributeVariable *attr, MethodBody &body,
   const NamedAttribute *var = attr->getVar();
 
   // Check to see if we can parse this as an enum attribute.
-  if (canFormatEnumAttr(var))
+  if (attr->shouldFormatAsEnum() || canFormatEnumAttr(var))
     return genEnumAttrParser(var, body, attrTypeCtx, parseAsOptional,
-                             useProperties, opCppClassName);
+                             useProperties, opCppClassName,
+                             attr->shouldFormatAsEnum());
 
   // Check to see if we should parse this as a symbol name attribute.
   if (shouldFormatSymbolNameAttr(var)) {
@@ -2450,7 +2519,8 @@ static void genVariadicSegmentElision(OperationFormat &fmt, Operator &op,
 }
 
 static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
-                               MethodBody &body, StringRef valueExpression);
+                               MethodBody &body, StringRef valueExpression,
+                               bool formatAsEnumDirective = false);
 
 /// Generate the key-value printer used by the default `prop-dict` printer.
 static void genKeyValuePropDictPrinter(OperationFormat &fmt, Operator &op,
@@ -2836,11 +2906,10 @@ static MethodBody &genTypeOperandPrinter(FormatElement *arg, const Operator &op,
 
 /// Generate the printer for an enum attribute.
 static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
-                               MethodBody &body,
-                               StringRef valueExpression = {}) {
+                               MethodBody &body, StringRef valueExpression,
+                               bool formatAsEnumDirective) {
   Attribute baseAttr = var->attr.getBaseAttr();
   const EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
-  std::vector<EnumCase> cases = enumInfo.getAllCases();
   bool dereferenceGetter =
       var->attr.isOptional() && !var->attr.hasDefaultValue();
 
@@ -2852,6 +2921,15 @@ static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
   body << formatv(enumAttrBeginPrinterCode, caseValue,
                   enumInfo.getSymbolToStringFnName());
 
+  if (formatAsEnumDirective && enumInfo.isBitEnum() &&
+      !enumInfo.printBitEnumQuoted()) {
+    body << "    _odsPrinter << caseValueStr;\n"
+            "  }\n";
+    return;
+  }
+
+  std::vector<EnumCase> cases = enumInfo.getAllCases();
+
   // Get a string containing all of the cases that can't be represented with a
   // keyword.
   BitVector nonKeywordCases(cases.size());
@@ -3130,8 +3208,9 @@ void OperationFormat::genElementPrinter(FormatElement *element,
     const NamedAttribute *var = attr->getVar();
 
     // If we are formatting as an enum, symbolize the attribute as a string.
-    if (canFormatEnumAttr(var))
-      return genEnumAttrPrinter(var, op, body);
+    if (attr->shouldFormatAsEnum() || canFormatEnumAttr(var))
+      return genEnumAttrPrinter(var, op, body, /*valueExpression=*/{},
+                                attr->shouldFormatAsEnum());
 
     // If we are formatting as a symbol name, handle it as a symbol name.
     if (shouldFormatSymbolNameAttr(var)) {
@@ -3326,6 +3405,10 @@ class OpFormatParser : public FormatParser {
   /// Verify that the attribute dictionary directive isn't followed by a region.
   LogicalResult verifyAttrDictRegion(SMLoc loc,
                                      ArrayRef<FormatElement *> elements);
+  /// Verify that an unquoted bit enum isn't followed by an indistinguishable
+  /// separator literal.
+  LogicalResult verifyEnumAdjacentLiterals(SMLoc loc,
+                                           ArrayRef<FormatElement *> elements);
 
   /// Verify the state of operation operands within the format.
   LogicalResult
@@ -3375,6 +3458,7 @@ class OpFormatParser : public FormatParser {
   FailureOr<FormatElement *> parsePropDictDirective(SMLoc loc, Context context);
   FailureOr<FormatElement *> parseAttrDictDirective(SMLoc loc, Context context,
                                                     bool withKeyword);
+  FailureOr<FormatElement *> parseEnumDirective(SMLoc loc, Context context);
   FailureOr<FormatElement *> parseFunctionalTypeDirective(SMLoc loc,
                                                           Context context);
   FailureOr<FormatElement *> parseOIListDirective(SMLoc loc, Context context);
@@ -3451,6 +3535,7 @@ LogicalResult OpFormatParser::verify(SMLoc loc,
       failed(verifyResults(loc, variableTyResolver)) ||
       failed(verifyOperands(loc, variableTyResolver)) ||
       failed(verifyRegions(loc)) || failed(verifySuccessors(loc)) ||
+      failed(verifyEnumAdjacentLiterals(loc, elements)) ||
       failed(verifyOIListElements(loc, elements)))
     return failure();
 
@@ -3829,6 +3914,38 @@ LogicalResult OpFormatParser::verifySuccessors(SMLoc loc) {
   return success();
 }
 
+LogicalResult
+OpFormatParser::verifyEnumAdjacentLiterals(SMLoc loc,
+                                           ArrayRef<FormatElement *> elements) {
+  for (auto [index, element] : llvm::enumerate(elements)) {
+    auto *attr = dyn_cast<AttributeVariable>(element);
+    if (!attr || !attr->shouldFormatAsEnum())
+      continue;
+
+    Attribute baseAttr = attr->getVar()->attr.getBaseAttr();
+    EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
+    if (!enumInfo.isBitEnum() || enumInfo.printBitEnumQuoted())
+      continue;
+
+    auto remaining = elements.drop_front(index + 1);
+    auto next = llvm::find_if_not(remaining, llvm::IsaPred<WhitespaceElement>);
+    if (next == remaining.end())
+      continue;
+
+    auto *literal = dyn_cast<LiteralElement>(*next);
+    if (!literal)
+      continue;
+
+    StringRef separator =
+        enumInfo.getDef().getValueAsString("separator").trim();
+    if (literal->getSpelling() == separator) {
+      return emitError(loc, "unquoted bit enum cannot be followed by its '" +
+                                separator + "' separator literal");
+    }
+  }
+  return success();
+}
+
 LogicalResult
 OpFormatParser::verifyOIListElements(SMLoc loc,
                                      ArrayRef<FormatElement *> elements) {
@@ -4057,6 +4174,8 @@ OpFormatParser::parseDirectiveImpl(SMLoc loc, FormatToken::Kind kind,
   case FormatToken::kw_attr_dict_w_keyword:
     return parseAttrDictDirective(loc, ctx,
                                   /*withKeyword=*/true);
+  case FormatToken::kw_enum:
+    return parseEnumDirective(loc, ctx);
   case FormatToken::kw_functional_type:
     return parseFunctionalTypeDirective(loc, ctx);
   case FormatToken::kw_operands:
@@ -4077,6 +4196,50 @@ OpFormatParser::parseDirectiveImpl(SMLoc loc, FormatToken::Kind kind,
   }
 }
 
+FailureOr<FormatElement *> OpFormatParser::parseEnumDirective(SMLoc loc,
+                                                              Context context) {
+  if (context != TopLevelContext)
+    return emitError(loc, "'enum' directive can only be used at the top level");
+
+  FailureOr<FormatElement *> element;
+  if (failed(parseToken(FormatToken::l_paren,
+                        "expected '(' before argument list")) ||
+      failed(element = parseElement(context)) ||
+      failed(
+          parseToken(FormatToken::r_paren, "expected ')' after argument list")))
+    return failure();
+
+  auto *attr = dyn_cast<AttributeVariable>(*element);
+  if (!attr)
+    return emitError(loc, "'enum' directive expects an enum attribute");
+
+  Attribute baseAttr = attr->getVar()->attr.getBaseAttr();
+  if (!baseAttr.isEnumAttr())
+    return emitError(loc, "'enum' directive expects an enum attribute");
+
+  EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
+  if (enumInfo.getUnderlyingType().empty() ||
+      baseAttr.getConstBuilderTemplate().empty()) {
+    return emitError(
+        loc, "'enum' directive requires an enum attribute with an underlying "
+             "type and a constant builder");
+  }
+
+  if (enumInfo.isBitEnum() &&
+      llvm::none_of(enumInfo.getAllCases(), [](const EnumCase &attrCase) {
+        return attrCase.getValue() == 0;
+      })) {
+    return emitError(loc, "'enum' directive requires a bit enum to define a "
+                          "zero-valued case");
+  }
+
+  if (attr->shouldBeQualified())
+    return emitError(loc,
+                     "'enum' and 'qualified' directives cannot be combined");
+  attr->setShouldFormatAsEnum();
+  return attr;
+}
+
 FailureOr<FormatElement *>
 OpFormatParser::parseAttrDictDirective(SMLoc loc, Context context,
                                        bool withKeyword) {
@@ -4338,7 +4501,14 @@ FailureOr<FormatElement *> OpFormatParser::parseTypeDirective(SMLoc loc,
 
 LogicalResult OpFormatParser::markQualified(SMLoc loc, FormatElement *element) {
   return TypeSwitch<FormatElement *, LogicalResult>(element)
-      .Case<AttributeVariable, TypeDirective>([](auto *element) {
+      .Case([&](AttributeVariable *element) {
+        if (element->shouldFormatAsEnum())
+          return emitError(
+              loc, "'enum' and 'qualified' directives cannot be combined");
+        element->setShouldBeQualified();
+        return success();
+      })
+      .Case<TypeDirective>([](auto *element) {
         element->setShouldBeQualified();
         return success();
       })
@@ -4400,6 +4570,8 @@ OpFormatParser::parseTypeDirectiveOperand(SMLoc loc, bool isRefChild) {
 
 LogicalResult OpFormatParser::verifyOptionalGroupElements(
     SMLoc loc, ArrayRef<FormatElement *> elements, FormatElement *anchor) {
+  if (failed(verifyEnumAdjacentLiterals(loc, elements)))
+    return failure();
   for (FormatElement *element : elements) {
     if (failed(verifyOptionalGroupElement(loc, element, element == anchor)))
       return failure();


        


More information about the Mlir-commits mailing list