From 8b824ebb0a015013cc9de2e59bb843c8919b9a41 Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Mon, 24 Feb 2025 20:42:20 -0800 Subject: [PATCH] Add parser tests and refine some things here and there Signed-off-by: Anton Korobeynikov --- .../p4mlir/Dialect/P4HIR/P4HIR_ParserOps.td | 9 +- lib/Dialect/P4HIR/P4HIR_Ops.cpp | 63 +++++- test/Dialect/P4HIR/parser.mlir | 70 ++++++ test/Translate/Parser/parse.p4 | 33 +++ test/Translate/Parser/select.p4 | 170 +++++++++++++++ test/Translate/Parser/verify.p4 | 33 +++ tools/p4mlir-translate/translate.cpp | 199 ++++++++++-------- 7 files changed, 481 insertions(+), 96 deletions(-) create mode 100644 test/Dialect/P4HIR/parser.mlir create mode 100644 test/Translate/Parser/parse.p4 create mode 100644 test/Translate/Parser/select.p4 create mode 100644 test/Translate/Parser/verify.p4 diff --git a/include/p4mlir/Dialect/P4HIR/P4HIR_ParserOps.td b/include/p4mlir/Dialect/P4HIR/P4HIR_ParserOps.td index 828f4e6..4facfbb 100644 --- a/include/p4mlir/Dialect/P4HIR/P4HIR_ParserOps.td +++ b/include/p4mlir/Dialect/P4HIR/P4HIR_ParserOps.td @@ -5,8 +5,7 @@ def ParserOp : P4HIR_Op<"parser", [Symbol, SymbolTable, IsolatedFromAbove, FunctionOpInterface, AutomaticAllocationScope]> { let arguments = (ins SymbolNameAttr:$sym_name, - TypeAttrOf:$applyType, - TypeAttrOf:$constructorType); + TypeAttrOf:$applyType); let regions = (region SizedRegion<1>:$body); let hasCustomAssemblyFormat = 1; @@ -26,6 +25,8 @@ def ParserOp : P4HIR_Op<"parser", llvm::ArrayRef getResultTypes() { return {}; } + + void createEntryBlock(); }]; } @@ -187,7 +188,7 @@ def RangeOp : P4HIR_Op<"range", [Pure, let arguments = (ins AnyIntP4Type:$lhs, AnyIntP4Type:$rhs); let assemblyFormat = [{ - `(` $lhs `,` $rhs `)` `:` type($result) attr-dict + `(` $lhs `,` $rhs `)` `:` qualified(type($result)) attr-dict }]; let builders = [ @@ -228,7 +229,7 @@ def MaskOp : P4HIR_Op<"mask", [Pure, let arguments = (ins AnyIntP4Type:$lhs, AnyIntP4Type:$rhs); let assemblyFormat = [{ - `(` $lhs `,` $rhs `)` `:` type($result) attr-dict + `(` $lhs `,` $rhs `)` `:` qualified(type($result)) attr-dict }]; let builders = [ diff --git a/lib/Dialect/P4HIR/P4HIR_Ops.cpp b/lib/Dialect/P4HIR/P4HIR_Ops.cpp index 2ec74bc..1ee09d6 100644 --- a/lib/Dialect/P4HIR/P4HIR_Ops.cpp +++ b/lib/Dialect/P4HIR/P4HIR_Ops.cpp @@ -1001,7 +1001,17 @@ void P4HIR::TupleExtractOp::build(OpBuilder &builder, OperationState &odsState, // ParserOp //===----------------------------------------------------------------------===// +void P4HIR::ParserOp::createEntryBlock() { + assert(empty() && "can only create entry block for empty parser"); + Block &first = getFunctionBody().emplaceBlock(); + auto loc = getFunctionBody().getLoc(); + for (auto argType : getFunctionType().getInputs()) first.addArgument(argType, loc); +} + void P4HIR::ParserOp::print(mlir::OpAsmPrinter &printer) { + // This is essentially function_interface_impl::printFunctionOp, but we + // always print body and we do not have result / argument attributes (for now) + auto funcName = getSymNameAttr().getValue(); printer << ' '; @@ -1009,21 +1019,64 @@ void P4HIR::ParserOp::print(mlir::OpAsmPrinter &printer) { function_interface_impl::printFunctionSignature(printer, *this, getApplyType().getInputs(), false, {}); - function_interface_impl::printFunctionSignature(printer, *this, - getConstructorType().getInputs(), false, {}); function_interface_impl::printFunctionAttributes( printer, *this, // These are all omitted since they are custom printed already. - {getApplyTypeAttrName(), getConstructorTypeAttrName()}); + {getApplyTypeAttrName()}); printer << ' '; printer.printRegion(getRegion(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/true); } mlir::ParseResult P4HIR::ParserOp::parse(mlir::OpAsmParser &parser, mlir::OperationState &result) { - // TODO - return mlir::failure(); + // This is essentially function_interface_impl::parseFunctionOp, but we do not have + // result / argument attributes (for now) + llvm::SMLoc loc = parser.getCurrentLocation(); + auto &builder = parser.getBuilder(); + + // Parse the name as a symbol. + StringAttr nameAttr; + if (parser.parseSymbolName(nameAttr, ::SymbolTable::getSymbolAttrName(), result.attributes)) + return mlir::failure(); + + // We default to private visibility + result.addAttribute(::SymbolTable::getVisibilityAttrName(), builder.getStringAttr("private")); + + llvm::SmallVector arguments; + llvm::SmallVector resultAttrs; + llvm::SmallVector argTypes; + llvm::SmallVector resultTypes; + bool isVariadic = false; + if (function_interface_impl::parseFunctionSignature(parser, /*allowVariadic=*/false, arguments, + isVariadic, resultTypes, resultAttrs)) + return mlir::failure(); + + // Parsers have no results + if (!resultTypes.empty()) + return parser.emitError(loc, "parsers should not produce any results"); + + // Build the function type. + for (auto &arg : arguments) argTypes.push_back(arg.type); + + if (auto fnType = P4HIR::FuncType::get(builder.getContext(), argTypes)) { + result.addAttribute(getApplyTypeAttrName(result.name), TypeAttr::get(fnType)); + } else + return mlir::failure(); + + // If additional attributes are present, parse them. + if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) return failure(); + + // TODO: Support argument attributes + + // Parse the parser body. + auto *body = result.addRegion(); + if (parser.parseRegion(*body, arguments, /*enableNameShadowing=*/false)) return mlir::failure(); + + // Make sure its not empty. + if (body->empty()) return parser.emitError(loc, "expected non-empty parser body"); + + return mlir::success(); } static mlir::ModuleOp getParentModule(Operation *from) { diff --git a/test/Dialect/P4HIR/parser.mlir b/test/Dialect/P4HIR/parser.mlir new file mode 100644 index 0000000..b7ed7bc --- /dev/null +++ b/test/Dialect/P4HIR/parser.mlir @@ -0,0 +1,70 @@ +// RUN: p4mlir-opt %s | FileCheck %s + +!b10i = !p4hir.bit<10> +#false = #p4hir.bool : !p4hir.bool +#true = #p4hir.bool : !p4hir.bool +#int0_b10i = #p4hir.int<0> : !b10i +#int10_b10i = #p4hir.int<10> : !b10i +#int1_b10i = #p4hir.int<1> : !b10i +#int20_b10i = #p4hir.int<20> : !b10i +// CHECK: module +module { + p4hir.parser @p2(%arg0: !b10i, %arg1: !p4hir.ref) { + p4hir.state @start { + %true = p4hir.const #true + %tuple = p4hir.tuple (%arg0, %true) : tuple + p4hir.transition_select %tuple : tuple { + p4hir.select_case { + %c1_b10i = p4hir.const #int1_b10i + %set = p4hir.set (%c1_b10i) : !p4hir.set + %false = p4hir.const #false + %set_0 = p4hir.set (%false) : !p4hir.set + %setproduct = p4hir.set_product (%set, %set_0) : !p4hir.set> + p4hir.yield %setproduct : !p4hir.set> + } to @p2::@drop + p4hir.select_case { + %c10_b10i = p4hir.const #int10_b10i + %c20_b10i = p4hir.const #int20_b10i + %range = p4hir.range(%c10_b10i, %c20_b10i) : !p4hir.set + %true_0 = p4hir.const #true + %set = p4hir.set (%true_0) : !p4hir.set + %setproduct = p4hir.set_product (%range, %set) : !p4hir.set> + p4hir.yield %setproduct : !p4hir.set> + } to @p2::@next + p4hir.select_case { + %c0_b10i = p4hir.const #int0_b10i + %c0_b10i_0 = p4hir.const #int0_b10i + %mask = p4hir.mask(%c0_b10i, %c0_b10i_0) : !p4hir.set + %everything = p4hir.universal_set : !p4hir.set + %setproduct = p4hir.set_product (%mask, %everything) : !p4hir.set> + p4hir.yield %setproduct : !p4hir.set> + } to @p2::@next + p4hir.select_case { + %everything = p4hir.universal_set : !p4hir.set + %everything_0 = p4hir.universal_set : !p4hir.set + %setproduct = p4hir.set_product (%everything, %everything_0) : !p4hir.set> + p4hir.yield %setproduct : !p4hir.set> + } to @p2::@reject + p4hir.select_case { + %everything = p4hir.universal_set : !p4hir.set + p4hir.yield %everything : !p4hir.set + } to @p2::@reject + } + } + p4hir.state @drop { + p4hir.transition to @p2::@reject + } + p4hir.state @next { + %true = p4hir.const #true + p4hir.assign %true, %arg1 : + p4hir.transition to @p2::@accept + } + p4hir.state @accept { + p4hir.parser_accept + } + p4hir.state @reject { + p4hir.parser_reject + } + p4hir.transition to @p2::@start + } +} diff --git a/test/Translate/Parser/parse.p4 b/test/Translate/Parser/parse.p4 new file mode 100644 index 0000000..d22b08a --- /dev/null +++ b/test/Translate/Parser/parse.p4 @@ -0,0 +1,33 @@ +// RUN: p4mlir-translate --typeinference-only %s | FileCheck %s + + +struct empty {} + +parser p(in empty e, in int<10> sinit) { + int<10> s = sinit; + + state start { + s = 1; + transition next; + } + + state next { + s = 2; + transition accept; + } + + state drop {} +} + +// CHECK-LABEL: p4hir.parser @p(%arg0: !empty, %arg1: !i10i) { +// CHECK: p4hir.state @start { +// CHECK: p4hir.transition to @p::@next +// CHECK: p4hir.state @next { +// CHECK: p4hir.transition to @p::@accept +// CHECK: p4hir.state @drop { +// CHECK-NEXT: p4hir.transition to @p::@reject +// CHECK: p4hir.state @accept { +// CHECK-NEXT: p4hir.parser_accept +// CHECK: p4hir.state @reject { +// CHECK-NEXT: p4hir.parser_reject +// CHECK: p4hir.transition to @p::@start diff --git a/test/Translate/Parser/select.p4 b/test/Translate/Parser/select.p4 new file mode 100644 index 0000000..f375a64 --- /dev/null +++ b/test/Translate/Parser/select.p4 @@ -0,0 +1,170 @@ +// RUN: p4mlir-translate --typeinference-only %s | FileCheck %s + +// Check basic types of selects & autogenerated transition to reject +parser p1(in bit<10> foo, out bool matches) { + state start { + transition select(foo) { + 1 : drop; + 10..20 : next; + 0 &&& 0 : next; + } + } + + state drop { + } + + state next { + matches = true; + transition accept; + } +} + +// CHECK-LABEL: p4hir.parser @p1 +// CHECK-LABEL: p4hir.state @start +// CHECK: p4hir.transition_select %{{.*}} : !b10i +// CHECK: p4hir.select_case { +// CHECK: %[[c1_b10i:.*]] = p4hir.const #int1_b10i +// CHECK: %[[set:.*]] = p4hir.set (%[[c1_b10i]]) : !p4hir.set +// CHECK: p4hir.yield %[[set]] : !p4hir.set +// CHECK: } to @p1::@drop +// CHECK: p4hir.select_case { +// CHECK: %[[c10_b10i:.*]] = p4hir.const #int10_b10i +// CHECK: %[[c20_b10i:.*]] = p4hir.const #int20_b10i +// CHECK: %[[range:.*]] = p4hir.range(%[[c10_b10i]], %[[c20_b10i]]) : !p4hir.set +// CHECK: p4hir.yield %[[range]] : !p4hir.set +// CHECK: } to @p1::@next +// CHECK: p4hir.select_case { +// CHECK: %[[c0_b10i:.*]] = p4hir.const #int0_b10i +// CHECK: %[[c0_b10i_0:.*]] = p4hir.const #int0_b10i +// CHECK: %[[mask:.*]] = p4hir.mask(%[[c0_b10i:.*]], %[[c0_b10i_0:.*]]) : !p4hir.set +// CHECK: p4hir.yield %[[mask:.*]] : !p4hir.set +// CHECK: } to @p1::@next +// CHECK: p4hir.select_case { +// CHECK: %[[everything:.*]] = p4hir.universal_set : !p4hir.set +// CHECK: p4hir.yield %[[everything]] : !p4hir.set +// CHECK: } to @p1::@reject +// CHECK: } +// CHECK-LABEL: p4hir.state @drop { +// CHECK: p4hir.transition to @p1::@reject +// CHECK-LABEL: p4hir.state @next { +// CHECK: p4hir.transition to @p1::@accept +// CHECK-LABEL: p4hir.state @accept { +// CHECK: p4hir.parser_accept +// CHECK-LABEL p4hir.state @reject { +// CHECK: p4hir.parser_reject +// CHECK: p4hir.transition to @p1::@start + +parser p2(in bit<10> foo, out bool matches) { + state start { + transition select(foo, true) { + (1, false) : drop; + (10..20, true) : next; + (0 &&& 0, _) : next; + (_, _) : reject; + _ : reject; + } + } + + state drop { + } + + state next { + matches = true; + transition accept; + } +} + +// Check more complex set product operations +// CHECK-LABEL: p4hir.parser @p2(%arg0: !b10i, %arg1: !p4hir.ref) { +// CHECK-LABEL: p4hir.state @start { +// CHECK: p4hir.transition_select %{{.*}} : tuple { +// CHECK: p4hir.select_case { +// CHECK: %[[c1_b10i:.*]] = p4hir.const #int1_b10i +// CHECK: %[[set:.*]] = p4hir.set (%[[c1_b10i]]) : !p4hir.set +// CHECK: %[[false:.*]] = p4hir.const #false +// CHECK: %[[set_0:.*]] = p4hir.set (%[[false]]) : !p4hir.set +// CHECK: %[[setproduct:.*]] = p4hir.set_product (%[[set]], %[[set_0]]) : !p4hir.set> +// CHECK: p4hir.yield %[[setproduct]] : !p4hir.set> +// CHECK: } to @p2::@drop +// CHECK: p4hir.select_case { +// CHECK: %[[c10_b10i:.*]] = p4hir.const #int10_b10i +// CHECK: %[[c20_b10i:.*]] = p4hir.const #int20_b10i +// CHECK: %[[range:.*]] = p4hir.range(%[[c10_b10i]], %[[c20_b10i]]) : !p4hir.set +// CHECK: %[[true_0:.*]] = p4hir.const #true +// CHECK: %[[set:.*]] = p4hir.set (%true_0) : !p4hir.set +// CHECK: %[[setproduct:.*]] = p4hir.set_product (%[[range]], %[[set]]) : !p4hir.set> +// CHECK: p4hir.yield %[[setproduct]] : !p4hir.set> +// CHECK: } to @p2::@next +// CHECK: p4hir.select_case { +// CHECK: %[[c0_b10i:.*]] = p4hir.const #int0_b10i +// CHECK: %[[c0_b10i_0:.*]] = p4hir.const #int0_b10i +// CHECK: %[[mask:.*]] = p4hir.mask(%[[c0_b10i]], %[[c0_b10i_0]]) : !p4hir.set +// CHECK: %[[everything:.*]] = p4hir.universal_set : !p4hir.set +// CHECK: %[[setproduct:.*]] = p4hir.set_product (%[[mask]], %[[everything]]) : !p4hir.set> +// CHECK: p4hir.yield %[[setproduct]] : !p4hir.set> +// CHECK: } to @p2::@next +// CHECK: p4hir.select_case { +// CHECK: %[[everything:.*]] = p4hir.universal_set : !p4hir.set +// CHECK: %[[everything_0:.*]] = p4hir.universal_set : !p4hir.set +// CHECK: %[[setproduct:.*]] = p4hir.set_product (%[[everything]], %[[everything_0]]) : !p4hir.set> +// CHECK: p4hir.yield %[[setproduct]] : !p4hir.set> +// CHECK: } to @p2::@reject +// CHECK: p4hir.select_case { +// CHECK: %[[everything:.*]] = p4hir.universal_set : !p4hir.set +// CHECK: p4hir.yield %[[everything]] : !p4hir.set +// CHECK: } to @p2::@reject +// CHECK-LABEL: p4hir.state @drop { +// CHECK: p4hir.transition to @p2::@reject +// CHECK-LABEL: p4hir.state @next { +// CHECK: p4hir.transition to @p2::@accept +// CHECK-LABEL: p4hir.state @accept { +// CHECK: p4hir.parser_accept +// CHECK-LABEL: p4hir.state @reject { +// CHECK: p4hir.parser_reject +// CHECK: p4hir.transition to @p2::@start + +// Do not check the output, just ensure this compiles :) +parser weird(in int<32> arg1, inout int<32> arg2) { + bit<32> val1 = 2; + bool flag; + + state start { + transition select (arg1, {val1, val1, val1}) { + (arg1, {1, 2, 3}): foo1; + (3..7, _): foo2; + (arg1 &&& (arg2 + 42), _) : foo3; + (_ , _): accept; + } + } + + state foo1 { + transition select (arg1) { + 4..10: foo2; + (4..10): foo2; + 2..(arg2+arg1*7): foo3; + (2..(arg2-3)): foo3; + 1: reject; + _: accept; + } + } + + state foo2 { + transition select (arg1, val1) { + (1, 3..4): foo2; + _: accept; + } + } + + state foo3 { + bool local_flag = flag; + if (flag == false) { + local_flag = false; + } else { + local_flag = true; + } + transition select (local_flag) { + false: foo2; + true: foo1; + } + } +} diff --git a/test/Translate/Parser/verify.p4 b/test/Translate/Parser/verify.p4 new file mode 100644 index 0000000..a2b5b4c --- /dev/null +++ b/test/Translate/Parser/verify.p4 @@ -0,0 +1,33 @@ +// RUN: p4mlir-translate --typeinference-only %s | FileCheck %s + +error { + NoError, + SomeError +} + +/// Check a predicate @check in the parser; if the predicate is true do nothing, +/// otherwise set the parser error to @toSignal, and transition to the `reject` state. +extern void verify(in bool check, in error toSignal); + +parser p2(in bool check, out bool matches) { + state start { + verify(check == true, error.SomeError); + transition next; + } + + state next { + matches = true; + transition accept; + } +} + +// CHECK-LABEL: p4hir.parser @p2(%arg0: !p4hir.bool, %arg1: !p4hir.ref) +// CHECK: p4hir.state @start { +// CHECK: %[[true:.*]] = p4hir.const #true +// CHECK: %[[eq:.*]] = p4hir.cmp(eq, %arg0, %[[true]]) : !p4hir.bool, !p4hir.bool +// CHECK: %[[not:.*]] = p4hir.unary(not, %[[eq]]) : !p4hir.bool +// CHECK: p4hir.if %[[not]] { +// CHECK: p4hir.parser_reject with error #error_SomeError +// CHECK: } +// CHECK: p4hir.transition to @p2::@next +// CHECK } diff --git a/tools/p4mlir-translate/translate.cpp b/tools/p4mlir-translate/translate.cpp index e5fcec6..527e346 100644 --- a/tools/p4mlir-translate/translate.cpp +++ b/tools/p4mlir-translate/translate.cpp @@ -161,8 +161,8 @@ class P4HIRConverter : public P4::Inspector, public P4::ResolutionContext { // llvm::DenseMap p4Constants; llvm::DenseMap p4Constants; llvm::DenseMap p4Values; - using P4Symbol = - std::variant; + using P4Symbol = std::variant; // TODO: Implement better scoped symbol table llvm::DenseMap p4Symbols; @@ -312,6 +312,7 @@ class P4HIRConverter : public P4::Inspector, public P4::ResolutionContext { bool preorder(const P4::IR::P4Parser *a) override; bool preorder(const P4::IR::ParserState *s) override; + bool preorder(const P4::IR::SelectExpression *s) override; bool preorder(const P4::IR::Method *m) override; bool preorder(const P4::IR::BlockStatement *block) override { @@ -1334,6 +1335,8 @@ bool P4HIRConverter::preorder(const P4::IR::MethodCallExpression *mce) { } bool P4HIRConverter::preorder(const P4::IR::Member *m) { + ConversionTracer trace("Converting in preorder ", m); + // This is just enum constant if (const auto *typeNameExpr = m->expr->to()) { auto type = getOrCreateType(typeNameExpr->typeName); @@ -1357,6 +1360,8 @@ bool P4HIRConverter::preorder(const P4::IR::Member *m) { } void P4HIRConverter::postorder(const P4::IR::Member *m) { + ConversionTracer trace("Converting in postorder ", m); + // Resolve member rvalue expression to something we can reason about // TODO: Likely we can do similar things for the majority of struct-like // types @@ -1373,6 +1378,8 @@ void P4HIRConverter::postorder(const P4::IR::Member *m) { } bool P4HIRConverter::preorder(const P4::IR::StructExpression *str) { + ConversionTracer trace("Converting ", str); + auto type = getOrCreateType(str->structType); auto loc = getLoc(builder, str); @@ -1391,6 +1398,8 @@ bool P4HIRConverter::preorder(const P4::IR::StructExpression *str) { } bool P4HIRConverter::preorder(const P4::IR::ListExpression *lst) { + ConversionTracer trace("Converting ", lst); + auto type = getOrCreateType(lst->type); auto loc = getLoc(builder, lst); @@ -1406,6 +1415,8 @@ bool P4HIRConverter::preorder(const P4::IR::ListExpression *lst) { } void P4HIRConverter::postorder(const P4::IR::ArrayIndex *arr) { + ConversionTracer trace("Converting ", arr); + auto lhs = getValue(arr->left); auto loc = getLoc(builder, arr); if (mlir::isa(lhs.getType())) { @@ -1418,6 +1429,8 @@ void P4HIRConverter::postorder(const P4::IR::ArrayIndex *arr) { } void P4HIRConverter::postorder(const P4::IR::Range *range) { + ConversionTracer trace("Converting ", range); + auto lhs = getValue(range->left); auto rhs = getValue(range->right); @@ -1426,6 +1439,8 @@ void P4HIRConverter::postorder(const P4::IR::Range *range) { } void P4HIRConverter::postorder(const P4::IR::Mask *range) { + ConversionTracer trace("Converting ", range); + auto lhs = getValue(range->left); auto rhs = getValue(range->right); @@ -1434,6 +1449,8 @@ void P4HIRConverter::postorder(const P4::IR::Mask *range) { } bool P4HIRConverter::preorder(const P4::IR::P4Parser *parser) { + ConversionTracer trace("Converting ", parser); + auto applyType = mlir::cast(getOrCreateType(parser->getApplyMethodType())); auto ctorType = mlir::cast(getOrCreateType(parser->getConstructorMethodType())); @@ -1441,15 +1458,11 @@ bool P4HIRConverter::preorder(const P4::IR::P4Parser *parser) { BUG_CHECK(ctorType.getNumInputs() == 0, "does not yet support constructors with parameters"); auto loc = getLoc(builder, parser); - auto parserOp = - builder.create(loc, parser->name.string_view(), applyType, ctorType); - - // TODO: Move to build() - mlir::Block &first = parserOp.getBody().emplaceBlock(); - for (auto argType : applyType.getInputs()) first.addArgument(argType, loc); + auto parserOp = builder.create(loc, parser->name.string_view(), applyType); + parserOp.createEntryBlock(); mlir::OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&first); + builder.setInsertionPointToStart(&parserOp.getBody().front()); // Iterate over parameters again binding parameter values to arguments of first BB auto &body = parserOp.getBody(); @@ -1471,15 +1484,18 @@ bool P4HIRConverter::preorder(const P4::IR::P4Parser *parser) { builder.create( getEndLoc(builder, parser), mlir::SymbolRefAttr::get(parserSymbol, {startStateSymbol})); - parserOp.dump(); + + auto [it, inserted] = p4Symbols.try_emplace(parser, mlir::SymbolRefAttr::get(parserOp)); + BUG_CHECK(inserted, "duplicate translation of %1%", parser); return false; } bool P4HIRConverter::preorder(const P4::IR::ParserState *state) { + ConversionTracer trace("Converting ", state); + auto stateOp = builder.create(getLoc(builder, state), state->name.string_view()); - // TODO: Move to build() mlir::Block &first = stateOp.getBody().emplaceBlock(); mlir::OpBuilder::InsertionGuard guard(builder); @@ -1502,6 +1518,7 @@ bool P4HIRConverter::preorder(const P4::IR::ParserState *state) { // Normal transition is either PathExpression or SelectExpression if (const auto *pe = state->selectExpression->to()) { + LOG4("Resolving direct transition: " << pe); auto loc = getLoc(builder, pe); const auto *nextState = resolvePath(pe->path, false)->checkedTo(); auto nextStateSymbol = @@ -1509,88 +1526,96 @@ bool P4HIRConverter::preorder(const P4::IR::ParserState *state) { builder.create( loc, mlir::SymbolRefAttr::get(parserSymbol, {nextStateSymbol})); } else { - const auto *select = state->selectExpression->checkedTo(); + LOG4("Resolving select transition: " << state->selectExpression); + visit(state->selectExpression); + } - // Materialize value to select over. Select is always a ListExpression, - // even if it contains a single value. Lists ae lowered to tuples, - // however, single value cases are not single-value tuples. Unwrap - // single-value ListExpression down to the sole component. - const P4::IR::Expression *selectArg = select->select; - if (select->select->components.size() == 1) selectArg = select->select->components.front(); + return false; +} - visit(selectArg); +bool P4HIRConverter::preorder(const P4::IR::SelectExpression *select) { + ConversionTracer trace("Converting ", select); - // Create select itself - auto transitionSelectOp = builder.create( - getLoc(builder, select), getValue(selectArg)); + const auto *parser = findContext(); + auto parserSymbol = mlir::StringAttr::get(builder.getContext(), parser->name.string_view()); - // TODO: Move to build() - mlir::Block &first = transitionSelectOp.getBody().emplaceBlock(); + // Materialize value to select over. Select is always a ListExpression, + // even if it contains a single value. Lists ae lowered to tuples, + // however, single value cases are not single-value tuples. Unwrap + // single-value ListExpression down to the sole component. + const P4::IR::Expression *selectArg = select->select; + if (select->select->components.size() == 1) selectArg = select->select->components.front(); - mlir::OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&first); - - bool hasDefaultCase = false; - for (const auto *selectCase : select->selectCases) { - const auto *nextState = - resolvePath(selectCase->state->path, false)->checkedTo(); - auto nextStateSymbol = - mlir::SymbolRefAttr::get(builder.getContext(), nextState->name.string_view()); - builder.create( - getLoc(builder, selectCase), - [&](mlir::OpBuilder &b, mlir::Location) { - const auto *keyset = selectCase->keyset; - auto endLoc = getEndLoc(builder, keyset); - mlir::Value keyVal; - - // Type inference does not do proper type unification for the key, - // so we'd need to do this by ourselves - auto convertElement = [&](const P4::IR::Expression *expr) -> mlir::Value { - // Universal set - if (expr->is()) - return b.create(endLoc).getResult(); - - visit(expr); - auto elVal = getValue(expr); - if (!mlir::isa(elVal.getType())) - elVal = b.create(getEndLoc(builder, expr), elVal); - return elVal; - }; - - if (const auto *keyList = keyset->to()) { - // Set product - llvm::SmallVector elements; - for (const auto *element : keyList->components) - elements.push_back(convertElement(element)); - // Treat product consisting entirely of universal sets as default case - hasDefaultCase |= llvm::all_of(elements, [](mlir::Value el) { - return mlir::isa(el.getDefiningOp()); - }); - keyVal = b.create(endLoc, elements); - } else { - keyVal = convertElement(keyset); - hasDefaultCase |= mlir::isa(keyVal.getDefiningOp()); - } - b.create(endLoc, keyVal); - }, - mlir::SymbolRefAttr::get(parserSymbol, {nextStateSymbol})); - } + visit(selectArg); - // If there is no default case, then synthesize one explicitly - // FIXME: signal `error.NoMatch` error. - if (!hasDefaultCase) { - auto rejectStateSymbol = mlir::SymbolRefAttr::get( - builder.getContext(), P4::IR::ParserState::reject.string_view()); - - auto endLoc = getEndLoc(builder, select); - builder.create( - endLoc, - [&](mlir::OpBuilder &b, mlir::Location) { - b.create( - endLoc, builder.create(endLoc).getResult()); - }, - mlir::SymbolRefAttr::get(parserSymbol, {rejectStateSymbol})); - } + // Create select itself + auto transitionSelectOp = builder.create( + getLoc(builder, select), getValue(selectArg)); + mlir::Block &first = transitionSelectOp.getBody().emplaceBlock(); + + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&first); + + bool hasDefaultCase = false; + for (const auto *selectCase : select->selectCases) { + const auto *nextState = + resolvePath(selectCase->state->path, false)->checkedTo(); + auto nextStateSymbol = + mlir::SymbolRefAttr::get(builder.getContext(), nextState->name.string_view()); + builder.create( + getLoc(builder, selectCase), + [&](mlir::OpBuilder &b, mlir::Location) { + const auto *keyset = selectCase->keyset; + auto endLoc = getEndLoc(builder, keyset); + mlir::Value keyVal; + + // Type inference does not do proper type unification for the key, + // so we'd need to do this by ourselves + auto convertElement = [&](const P4::IR::Expression *expr) -> mlir::Value { + // Universal set + if (expr->is()) + return b.create(endLoc).getResult(); + + visit(expr); + auto elVal = getValue(expr); + if (!mlir::isa(elVal.getType())) + elVal = b.create(getEndLoc(builder, expr), elVal); + return elVal; + }; + + if (const auto *keyList = keyset->to()) { + // Set product + llvm::SmallVector elements; + for (const auto *element : keyList->components) + elements.push_back(convertElement(element)); + // Treat product consisting entirely of universal sets as default case + hasDefaultCase |= llvm::all_of(elements, [](mlir::Value el) { + return mlir::isa(el.getDefiningOp()); + }); + keyVal = b.create(endLoc, elements); + } else { + keyVal = convertElement(keyset); + hasDefaultCase |= mlir::isa(keyVal.getDefiningOp()); + } + b.create(endLoc, keyVal); + }, + mlir::SymbolRefAttr::get(parserSymbol, {nextStateSymbol})); + } + + // If there is no default case, then synthesize one explicitly + // FIXME: signal `error.NoMatch` error. + if (!hasDefaultCase) { + auto rejectStateSymbol = mlir::SymbolRefAttr::get( + builder.getContext(), P4::IR::ParserState::reject.string_view()); + + auto endLoc = getEndLoc(builder, select); + builder.create( + endLoc, + [&](mlir::OpBuilder &b, mlir::Location) { + b.create(endLoc, + builder.create(endLoc).getResult()); + }, + mlir::SymbolRefAttr::get(parserSymbol, {rejectStateSymbol})); } return false;