[Mlir-commits] [mlir] 0973b4c - [MLIR][ODS] Add separator support to oilist (#217891)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Aug 24 05:27:16 PDT 2026
Author: Mehdi Amini
Date: 2026-08-24T14:27:09+02:00
New Revision: 0973b4c1293e44417a4cbcc4342328b8bed82e2d
URL: https://github.com/llvm/llvm-project/commit/0973b4c1293e44417a4cbcc4342328b8bed82e2d
DIFF: https://github.com/llvm/llvm-project/commit/0973b4c1293e44417a4cbcc4342328b8bed82e2d.diff
LOG: [MLIR][ODS] Add separator support to oilist (#217891)
Allow an optional literal separator in oilist formats. Parse and print
separators only between present clauses, and diagnose missing, trailing,
and ambiguous separators.
Assisted-by: Codex
Added:
Modified:
mlir/docs/DefiningDialects/Operations.md
mlir/test/IR/traits.mlir
mlir/test/lib/Dialect/Test/TestOpsSyntax.td
mlir/test/mlir-tblgen/op-format-invalid.td
mlir/test/mlir-tblgen/op-format-spec.td
mlir/tools/mlir-tblgen/OpFormatGen.cpp
Removed:
################################################################################
diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index 1ef96130d836d..9c3994cdb5396 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -778,10 +778,15 @@ The available directives are as follows:
- The constraints on `inputs` and `outputs` are the same as the `input` of
the `type` directive.
-* ``oilist ( `keyword` elements | `otherKeyword` elements ...)``
+* ``oilist ( `keyword` elements | `otherKeyword` elements ...)`` or
+ ``oilist < `separator` > ( `keyword` elements | `otherKeyword` elements ...)``
- Represents an optional order-independent list of clauses. Each clause
has a keyword and corresponding assembly format.
+ - The separator specification is optional. When present, the separator
+ is parsed and printed between clauses. For example,
+ ``oilist<`,`>(...)`` formats a comma-separated list without a trailing
+ comma.
- Each clause can appear 0 or 1 time (in any order).
- Only literals, types and variables can be used within an oilist element.
- All the variables must be optional or variadic.
diff --git a/mlir/test/IR/traits.mlir b/mlir/test/IR/traits.mlir
index b20edde07a6c7..5e5c2afe88cbd 100644
--- a/mlir/test/IR/traits.mlir
+++ b/mlir/test/IR/traits.mlir
@@ -654,6 +654,42 @@ func.func @succeededOilistCustom(%arg0: i32, %arg1: i32, %arg2: i32) {
// -----
+// CHECK-LABEL: @succeededOilistWithSeparator
+func.func @succeededOilistWithSeparator() {
+ // CHECK: test.oilist_with_separator
+ test.oilist_with_separator
+ // CHECK: test.oilist_with_separator keyword
+ test.oilist_with_separator keyword
+ // CHECK: test.oilist_with_separator keyword, otherKeyword
+ test.oilist_with_separator otherKeyword, keyword
+ // CHECK: test.oilist_with_separator keyword, otherKeyword, thirdKeyword
+ test.oilist_with_separator thirdKeyword, keyword, otherKeyword
+ return
+}
+
+// -----
+
+func.func @failedOilistWithDuplicateSeparatedClause() {
+ // expected-error at +1 {{`keyword` clause can appear at most once in the expansion of the oilist directive}}
+ test.oilist_with_separator keyword, keyword
+}
+
+// -----
+
+func.func @failedOilistWithMissingSeparator() {
+ // expected-error at +1 {{expected ',' between oilist clauses}}
+ test.oilist_with_separator keyword otherKeyword
+}
+
+// -----
+
+func.func @failedOilistWithTrailingSeparator() {
+ // expected-error at +1 {{expected oilist clause after separator}}
+ test.oilist_with_separator keyword,
+}
+
+// -----
+
func.func @failedHasDominanceScopeOutsideDominanceFreeScope() -> () {
"test.ssacfg_region"() ({
test.graph_region {
diff --git a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
index 1d9ff9bdef3a2..3ab3c8b9db6e7 100644
--- a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
+++ b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
@@ -137,6 +137,16 @@ def OIListCustom : TEST_Op<"oilist_custom", [AttrSizedOperandSegments]> {
}];
}
+def OIListWithSeparator : TEST_Op<"oilist_with_separator"> {
+ let arguments = (ins UnitAttr:$keyword, UnitAttr:$otherKeyword,
+ UnitAttr:$
diff NameUnitAttrKeyword);
+ let assemblyFormat = [{
+ oilist<`,`>( `keyword` $keyword
+ | `otherKeyword` $otherKeyword
+ | `thirdKeyword` $
diff NameUnitAttrKeyword) attr-dict
+ }];
+}
+
def OIListAllowedLiteral : TEST_Op<"oilist_allowed_literal"> {
let assemblyFormat = [{
oilist( `foo` | `bar` ) `buzz` attr-dict
diff --git a/mlir/test/mlir-tblgen/op-format-invalid.td b/mlir/test/mlir-tblgen/op-format-invalid.td
index 1944bc4feb634..38c8d57a342b6 100644
--- a/mlir/test/mlir-tblgen/op-format-invalid.td
+++ b/mlir/test/mlir-tblgen/op-format-invalid.td
@@ -372,10 +372,30 @@ def OIListErrorNoLiteral : TestFormat_Op<[{
def OIListLiteralAmbiguity : TestFormat_Op<[{
oilist( `foo` | `bar` ) `foo` attr-dict
}]>;
+// CHECK: error: format ambiguity because foo is used as both an oilist separator and clause keyword.
+def OIListSeparatorClauseAmbiguity : TestFormat_Op<[{
+ oilist<`foo`>(`foo`) attr-dict
+}]>;
+// CHECK: error: format ambiguity because , is used both in oilist element and the adjacent literal.
+def OIListSeparatorLiteralAmbiguity : TestFormat_Op<[{
+ oilist<`,`>(`foo`) `,` attr-dict
+}]>;
+// CHECK: error: expected literal, but got '>'
+def OIListSeparatorMissing : TestFormat_Op<[{
+ oilist<>(`foo`) attr-dict
+}]>;
// CHECK: error: expected '(' before oilist argument list
def OIListStartingToken : TestFormat_Op<[{
oilist `wrong` attr-dict
}]>;
+// CHECK: error: expected '>' after oilist separator
+def OIListUnterminatedSeparator : TestFormat_Op<[{
+ oilist<`,` (`foo`) attr-dict
+}]>;
+// CHECK: error: oilist separator must be a non-whitespace literal
+def OIListWhitespaceSeparator : TestFormat_Op<[{
+ oilist<` `>(`foo`) attr-dict
+}]>;
//===----------------------------------------------------------------------===//
// Optional Groups
diff --git a/mlir/test/mlir-tblgen/op-format-spec.td b/mlir/test/mlir-tblgen/op-format-spec.td
index 0ad6d0959809d..fac4386f92f80 100644
--- a/mlir/test/mlir-tblgen/op-format-spec.td
+++ b/mlir/test/mlir-tblgen/op-format-spec.td
@@ -152,6 +152,10 @@ def OIListCustom : TestFormat_Op<[{
| `nowait`
| `reduction` custom<ReductionClause>($arg1, type($arg1))) attr-dict
}], [AttrSizedOperandSegments]>, Arguments<(ins Optional<AnyType>:$arg0, Optional<AnyType>:$arg1)>;
+def OIListWithSeparator : TestFormat_Op<[{
+ oilist<`,`>( `keyword` $arg0 `:` type($arg0)
+ | `otherkeyword` $arg1 `:` type($arg1)) attr-dict
+}], [AttrSizedOperandSegments]>, Arguments<(ins Optional<AnyType>:$arg0, Optional<AnyType>:$arg1)>;
//===----------------------------------------------------------------------===//
// Optional Groups
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index f4080948db10a..77d4f0b5704bf 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -224,16 +224,20 @@ class TypeDirective : public DirectiveElementBase<DirectiveElement::Type> {
};
/// This class represents a group of order-independent optional clauses. Each
-/// clause starts with a literal element and has a coressponding parsing
-/// element. A parsing element is a continous sequence of format elements.
-/// Each clause can appear 0 or 1 time.
+/// clause starts with a literal element and has a corresponding parsing
+/// element. A parsing element is a continuous sequence of format elements.
+/// Each clause can appear 0 or 1 time. An optional literal separates clauses.
class OIListElement : public DirectiveElementBase<DirectiveElement::OIList> {
public:
- OIListElement(std::vector<FormatElement *> &&literalElements,
+ OIListElement(LiteralElement *separator,
+ std::vector<FormatElement *> &&literalElements,
std::vector<std::vector<FormatElement *>> &&parsingElements)
- : literalElements(std::move(literalElements)),
+ : separator(separator), literalElements(std::move(literalElements)),
parsingElements(std::move(parsingElements)) {}
+ /// Returns the optional separator between clauses.
+ LiteralElement *getSeparator() const { return separator; }
+
/// Returns a range to iterate over the LiteralElements.
auto getLiteralElements() const {
return llvm::map_range(literalElements, [](FormatElement *el) {
@@ -265,6 +269,9 @@ class OIListElement : public DirectiveElementBase<DirectiveElement::OIList> {
}
private:
+ /// An optional literal printed and parsed between clauses.
+ LiteralElement *separator;
+
/// A vector of `LiteralElement` objects. Each element stores the keyword
/// for one case of oilist element. For example, an oilist element along with
/// the `literalElements` vector:
@@ -1633,11 +1640,41 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
/// OIList Directive
} else if (OIListElement *oilist = dyn_cast<OIListElement>(element)) {
+ if (oilist->getSeparator()) {
+ body << " {\n";
+ body.indent();
+ }
+
for (LiteralElement *le : oilist->getLiteralElements())
body << " bool " << le->getSpelling() << "Clause = false;\n";
+ if (oilist->getSeparator())
+ body << " bool oilistClauseParsed = false;\n";
// Generate the parsing loop
body << " while(true) {\n";
+ if (LiteralElement *separator = oilist->getSeparator()) {
+ body << " auto oilistSeparatorLoc = parser.getCurrentLocation();\n";
+ body << " if (oilistClauseParsed) {\n";
+ body << " if (failed(parser.parseOptional";
+ genLiteralParser(separator->getSpelling(), body);
+ body << ")) {\n";
+ body << " if (";
+ llvm::interleave(
+ oilist->getLiteralElements(),
+ [&](LiteralElement *literal) {
+ body << "succeeded(parser.parseOptional";
+ genLiteralParser(literal->getSpelling(), body);
+ body << ")";
+ },
+ [&] { body << " || "; });
+ body << ")\n";
+ body << " return parser.emitError(oilistSeparatorLoc,\n"
+ " \"expected '"
+ << separator->getSpelling() << "' between oilist clauses\");\n";
+ body << " break;\n";
+ body << " }\n";
+ body << " }\n";
+ }
for (auto clause : oilist->getClauses()) {
LiteralElement *lelement = std::get<0>(clause);
ArrayRef<FormatElement *> pelement = std::get<1>(clause);
@@ -1646,6 +1683,8 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
body << ")) {\n";
StringRef lelementName = lelement->getSpelling();
body << formatv(oilistParserCode, lelementName);
+ if (oilist->getSeparator())
+ body << " oilistClauseParsed = true;\n";
if (AttributeLikeVariable *unitVarElem =
oilist->getUnitVariableParsingElement(pelement)) {
if (isa<PropertyVariable>(unitVarElem)) {
@@ -1665,9 +1704,16 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
body << " } else ";
}
body << " {\n";
+ if (oilist->getSeparator()) {
+ body << " if (oilistClauseParsed)\n";
+ body << " return parser.emitError(oilistSeparatorLoc,\n"
+ " \"expected oilist clause after separator\");\n";
+ }
body << " break;\n";
body << " }\n";
body << "}\n";
+ if (oilist->getSeparator())
+ body.unindent() << " }\n";
/// Literals.
} else if (LiteralElement *literal = dyn_cast<LiteralElement>(element)) {
@@ -2565,6 +2611,11 @@ void OperationFormat::genElementPrinter(FormatElement *element,
// Emit the OIList
if (auto *oilist = dyn_cast<OIListElement>(element)) {
+ if (oilist->getSeparator()) {
+ body << " {\n";
+ body.indent();
+ body << " bool oilistClausePrinted = false;\n";
+ }
for (auto clause : oilist->getClauses()) {
LiteralElement *lelement = std::get<0>(clause);
ArrayRef<FormatElement *> pelement = std::get<1>(clause);
@@ -2607,6 +2658,13 @@ void OperationFormat::genElementPrinter(FormatElement *element,
}
body << ") {\n";
+ if (LiteralElement *separator = oilist->getSeparator()) {
+ body << " if (oilistClausePrinted) {\n";
+ genLiteralPrinter(separator->getSpelling(), body, shouldEmitSpace,
+ lastWasPunctuation);
+ body << " }\n";
+ body << " oilistClausePrinted = true;\n";
+ }
genLiteralPrinter(lelement->getSpelling(), body, shouldEmitSpace,
lastWasPunctuation);
if (oilist->getUnitVariableParsingElement(pelement) == nullptr) {
@@ -2616,6 +2674,8 @@ void OperationFormat::genElementPrinter(FormatElement *element,
}
body << " }\n";
}
+ if (oilist->getSeparator())
+ body.unindent() << " }\n";
return;
}
@@ -3344,10 +3404,20 @@ LogicalResult OpFormatParser::verifySuccessors(SMLoc loc) {
LogicalResult
OpFormatParser::verifyOIListElements(SMLoc loc,
ArrayRef<FormatElement *> elements) {
- // Check that all of the successors are within the format.
+ // Check for ambiguous literals in and around oilist elements.
SmallVector<StringRef> prohibitedLiterals;
for (FormatElement *it : elements) {
if (auto *oilist = dyn_cast<OIListElement>(it)) {
+ if (LiteralElement *separator = oilist->getSeparator()) {
+ for (LiteralElement *literal : oilist->getLiteralElements()) {
+ if (literal->getSpelling() == separator->getSpelling()) {
+ return emitError(
+ loc, "format ambiguity because " + separator->getSpelling() +
+ " is used as both an oilist separator and clause "
+ "keyword.");
+ }
+ }
+ }
if (!prohibitedLiterals.empty()) {
// We just saw an oilist element in last iteration. Literals should not
// match.
@@ -3362,6 +3432,8 @@ OpFormatParser::verifyOIListElements(SMLoc loc,
}
for (LiteralElement *literal : oilist->getLiteralElements())
prohibitedLiterals.push_back(literal->getSpelling());
+ if (LiteralElement *separator = oilist->getSeparator())
+ prohibitedLiterals.push_back(separator->getSpelling());
} else if (auto *literal = dyn_cast<LiteralElement>(it)) {
if (find(prohibitedLiterals, literal->getSpelling()) !=
prohibitedLiterals.end()) {
@@ -3718,6 +3790,21 @@ OpFormatParser::parseSuccessorsDirective(SMLoc loc, Context context) {
FailureOr<FormatElement *>
OpFormatParser::parseOIListDirective(SMLoc loc, Context context) {
+ LiteralElement *separator = nullptr;
+ if (peekToken().is(FormatToken::less)) {
+ consumeToken();
+ SMLoc separatorLoc = peekToken().getLoc();
+ FailureOr<FormatElement *> separatorElement = parseLiteral(context);
+ if (failed(separatorElement))
+ return failure();
+ separator = dyn_cast<LiteralElement>(*separatorElement);
+ if (!separator)
+ return emitError(separatorLoc,
+ "oilist separator must be a non-whitespace literal");
+ if (failed(parseToken(FormatToken::greater,
+ "expected '>' after oilist separator")))
+ return failure();
+ }
if (failed(parseToken(FormatToken::l_paren,
"expected '(' before oilist argument list")))
return failure();
@@ -3748,7 +3835,7 @@ OpFormatParser::parseOIListDirective(SMLoc loc, Context context) {
}
} while (true);
- return create<OIListElement>(std::move(literalElements),
+ return create<OIListElement>(separator, std::move(literalElements),
std::move(parsingElements));
}
More information about the Mlir-commits
mailing list