[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