diff --git a/mlir/include/mlir/Tools/PDLL/AST/Nodes.h b/mlir/include/mlir/Tools/PDLL/AST/Nodes.h --- a/mlir/include/mlir/Tools/PDLL/AST/Nodes.h +++ b/mlir/include/mlir/Tools/PDLL/AST/Nodes.h @@ -786,7 +786,7 @@ : public Node::NodeBase { public: static ValueRangeConstraintDecl *create(Context &ctx, SMRange loc, - Expr *typeExpr); + Expr *typeExpr = nullptr); /// Return the optional type the value range is constrained to. Expr *getTypeExpr() { return typeExpr; } diff --git a/mlir/lib/Tools/PDLL/Parser/Parser.cpp b/mlir/lib/Tools/PDLL/Parser/Parser.cpp --- a/mlir/lib/Tools/PDLL/Parser/Parser.cpp +++ b/mlir/lib/Tools/PDLL/Parser/Parser.cpp @@ -1588,9 +1588,28 @@ if (failed(opNameDecl)) return failure(); + // Functor used to create an implicit range variable, used for implicit "all" + // operand or results variables. + auto createImplicitRangeVar = [&](ast::ConstraintDecl *cst, ast::Type type) { + FailureOr rangeVar = + defineVariableDecl("_", loc, type, ast::ConstraintRef(cst, loc)); + assert(succeeded(rangeVar) && "expected range variable to be valid"); + return ast::DeclRefExpr::create(ctx, loc, *rangeVar, type); + }; + // Check for the optional list of operands. SmallVector operands; - if (consumeIf(Token::l_paren)) { + if (!consumeIf(Token::l_paren)) { + // If the operand list isn't specified and we are in a match context, define + // an inplace unconstrained operand range corresponding to all of the + // operands of the operation. This avoids treating zero operands the same + // way as "unconstrained operands". + if (parserContext != ParserContext::Rewrite) { + operands.push_back(createImplicitRangeVar( + ast::ValueRangeConstraintDecl::create(ctx, loc), valueRangeTy)); + } + } else if (!consumeIf(Token::r_paren)) { + // If the operand list was specified and non-empty, parse the operands. do { FailureOr operand = parseExpr(); if (failed(operand)) @@ -1625,16 +1644,26 @@ "expected `(` before operation result type list"))) return failure(); - do { - FailureOr resultTypeExpr = parseExpr(); - if (failed(resultTypeExpr)) - return failure(); - resultTypes.push_back(*resultTypeExpr); - } while (consumeIf(Token::comma)); + // Handle the case of an empty result list. + if (!consumeIf(Token::r_paren)) { + do { + FailureOr resultTypeExpr = parseExpr(); + if (failed(resultTypeExpr)) + return failure(); + resultTypes.push_back(*resultTypeExpr); + } while (consumeIf(Token::comma)); - if (failed(parseToken(Token::r_paren, - "expected `)` after operation result type list"))) - return failure(); + if (failed(parseToken(Token::r_paren, + "expected `)` after operation result type list"))) + return failure(); + } + } else if (parserContext != ParserContext::Rewrite) { + // If the result list isn't specified and we are in a match context, define + // an inplace unconstrained result range corresponding to all of the results + // of the operation. This avoids treating zero results the same way as + // "unconstrained results". + resultTypes.push_back(createImplicitRangeVar( + ast::TypeRangeConstraintDecl::create(ctx, loc), typeRangeTy)); } return createOperationExpr(loc, *opNameDecl, operands, attributes, diff --git a/mlir/test/mlir-pdll/CodeGen/MLIR/expr.pdll b/mlir/test/mlir-pdll/CodeGen/MLIR/expr.pdll --- a/mlir/test/mlir-pdll/CodeGen/MLIR/expr.pdll +++ b/mlir/test/mlir-pdll/CodeGen/MLIR/expr.pdll @@ -6,7 +6,7 @@ // CHECK: pdl.pattern @AttrExpr // CHECK: %[[ATTR:.*]] = attribute 10 -// CHECK: operation {"attr" = %[[ATTR]]} +// CHECK: operation({{.*}}) {"attr" = %[[ATTR]]} Pattern AttrExpr => erase op<> { attr = attr<"10"> }; // ----- @@ -89,5 +89,5 @@ // CHECK: pdl.pattern @TypeExpr // CHECK: %[[TYPE:.*]] = type : i32 -// CHECK: operation -> (%[[TYPE]] : !pdl.type) +// CHECK: operation({{.*}}) -> (%[[TYPE]] : !pdl.type) Pattern TypeExpr => erase op<> -> (type<"i32">); diff --git a/mlir/test/mlir-pdll/Parser/expr.pdll b/mlir/test/mlir-pdll/Parser/expr.pdll --- a/mlir/test/mlir-pdll/Parser/expr.pdll +++ b/mlir/test/mlir-pdll/Parser/expr.pdll @@ -75,15 +75,42 @@ // OperationExpr //===----------------------------------------------------------------------===// +// Test a non-constrained operation expression, and ensure that we don't treat +// unconstrained as "not present"(e.g. zero operands). + // CHECK: Module // CHECK: `-OperationExpr {{.*}} Type // CHECK: `-OpNameDecl +// CHECK: `Operands` +// CHECK: `-DeclRefExpr {{.*}} Type +// CHECK: `-VariableDecl {{.*}} Name<_> Type +// CHECK: `Constraints` +// CHECK: `-ValueRangeConstraintDecl +// CHECK: `Result Types` +// CHECK: `-DeclRefExpr {{.*}} Type +// CHECK: `-VariableDecl {{.*}} Name<_> Type +// CHECK: `Constraints` +// CHECK: `-TypeRangeConstraintDecl Pattern { erase op<>; } // ----- +// Test explicitly empty operand/result/etc. lists, which are different from the +// "unconstrained" examples above. + +// CHECK: Module +// CHECK: `-OperationExpr {{.*}} Type +// CHECK: `-OpNameDecl +// CHECK-NOT: `Operands` +// CHECK-NOT: `Result Types` +Pattern { + erase op<>() -> (); +} + +// ----- + // CHECK: Module // CHECK: `-OperationExpr {{.*}} Type> // CHECK: `-OpNameDecl {{.*}} Name