From 2efc3aad928d8cdf623fd18ce029d5984bc1df31 Mon Sep 17 00:00:00 2001 From: Vladimir Shiryaev Date: Tue, 11 Feb 2025 15:49:34 -0800 Subject: [PATCH] Add concat operation (#63) Signed-off-by: Vladimir Shiryaev --- include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td | 41 +++++ lib/Dialect/P4HIR/P4HIR_Ops.cpp | 21 +++ test/Dialect/P4HIR/concat.mlir | 22 +++ test/Translate/Ops/concat.p4 | 194 ++++++++++++++++++++++ tools/p4mlir-translate/translate.cpp | 15 ++ 5 files changed, 293 insertions(+) create mode 100644 test/Dialect/P4HIR/concat.mlir create mode 100644 test/Translate/Ops/concat.p4 diff --git a/include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td b/include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td index 46f0200..17803cd 100644 --- a/include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td +++ b/include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td @@ -294,6 +294,47 @@ def BinOp : P4HIR_Op<"binop", [Pure, let hasVerifier = 0; } +def ConcatOp : P4HIR_Op<"concat", [Pure]> { + + let summary = "Concatenation of bit-strings and/or fixed-width signed integers"; + let description = [{ + `p4hir.concat` performs concatenation of bit-strings and/or fixed-width signed integers. + + The two operands must be either `p4hir.bit` or `p4hir.int`, and they can be of different signedness and width. + The result has the same signedness as the left operand and the width equal to the sum of the two operands' width. + In concatenation, the left operand is placed as the most significant bits. + + ```mlir + %0 = p4hir.const #p4hir.int<3> : !p4hir.bit<5> + %1 = p4hir.const #p4hir.int<4> : !p4hir.int<10> + %2 = p4hir.concat(%0 : !p4hir.bit<5>, %0 : !p4hir.bit<5>) : !p4hir.bit<10> + %3 = p4hir.concat(%1 : !p4hir.int<10>, %1 : !p4hir.int<10>) : !p4hir.int<20> + %4 = p4hir.concat(%0: !p4hir.bit<5>, %1 : !p4hir.int<10>) : !p4hir.bit<15> + %5 = p4hir.concat(%1 : !p4hir.int<5>, %0 : !p4hir.bit<10>) : !p4hir.int<15> + ``` + }]; + + let results = (outs BitsType:$result); + let arguments = (ins BitsType:$lhs, BitsType:$rhs); + + // Custom builder to infer the result type with the proper width + let builders = [ + OpBuilder<(ins "::mlir::Value":$lhs, "::mlir::Value":$rhs), [{ + auto lhsBits = lhs.getType().cast(); + auto rhsBits = rhs.getType().cast(); + auto resultWidth = lhsBits.getWidth() + rhsBits.getWidth(); + auto resultType = BitsType::get($_builder.getContext(), resultWidth, lhsBits.isSigned()); + build($_builder, $_state, resultType, lhs, rhs); + }]> + ]; + + let hasVerifier = 1; + + let assemblyFormat = [{ + `(` $lhs `:` type($lhs) `,` $rhs `:` type($rhs) `)` `:` type($result) attr-dict + }]; +} + def CmpOpKind_LT : I32EnumAttrCase<"Lt", 1, "lt">; def CmpOpKind_LE : I32EnumAttrCase<"Le", 2, "le">; def CmpOpKind_GT : I32EnumAttrCase<"Gt", 3, "gt">; diff --git a/lib/Dialect/P4HIR/P4HIR_Ops.cpp b/lib/Dialect/P4HIR/P4HIR_Ops.cpp index 29a51f7..bd535c3 100644 --- a/lib/Dialect/P4HIR/P4HIR_Ops.cpp +++ b/lib/Dialect/P4HIR/P4HIR_Ops.cpp @@ -115,6 +115,27 @@ void P4HIR::BinOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), stringifyEnum(getKind())); } +//===----------------------------------------------------------------------===// +// ConcatOp +//===----------------------------------------------------------------------===// + +LogicalResult P4HIR::ConcatOp::verify() { + auto lhsType = cast(getOperand(0).getType()); + auto rhsType = cast(getOperand(1).getType()); + auto resultType = cast(getResult().getType()); + + auto expectedWidth = lhsType.getWidth() + rhsType.getWidth(); + if (resultType.getWidth() != expectedWidth) + return emitOpError() << "the resulting width of a concatenation operation must equal the " + "sum of the operand widths"; + + if (resultType.isSigned() != lhsType.isSigned()) + return emitOpError() << "the signedness of the concatenation result must match the " + "signedness of the left-hand side operand"; + + return success(); +} + //===----------------------------------------------------------------------===// // CmpOp //===----------------------------------------------------------------------===// diff --git a/test/Dialect/P4HIR/concat.mlir b/test/Dialect/P4HIR/concat.mlir new file mode 100644 index 0000000..74fd246 --- /dev/null +++ b/test/Dialect/P4HIR/concat.mlir @@ -0,0 +1,22 @@ +// RUN: p4mlir-opt %s | FileCheck %s + +// No need to check stuff. If it parses, it's fine. +// CHECK: module +module { + %bit5 = p4hir.const #p4hir.int<2> : !p4hir.bit<5> + %bit10 = p4hir.const #p4hir.int<4> : !p4hir.bit<10> + %int5 = p4hir.const #p4hir.int<6> : !p4hir.int<5> + %int10 = p4hir.const #p4hir.int<8> : !p4hir.int<10> + + %0 = p4hir.concat(%bit5 : !p4hir.bit<5>, %bit5 : !p4hir.bit<5>) : !p4hir.bit<10> + %1 = p4hir.concat(%bit5 : !p4hir.bit<5>, %bit10 : !p4hir.bit<10>) : !p4hir.bit<15> + + %2 = p4hir.concat(%int5 : !p4hir.int<5>, %int5 : !p4hir.int<5>) : !p4hir.int<10> + %3 = p4hir.concat(%int5 : !p4hir.int<5>, %int10 : !p4hir.int<10>) : !p4hir.int<15> + + %4 = p4hir.concat(%bit5 : !p4hir.bit<5>, %int5 : !p4hir.int<5>) : !p4hir.bit<10> + %5 = p4hir.concat(%bit5 : !p4hir.bit<5>, %int10 : !p4hir.int<10>) : !p4hir.bit<15> + + %6 = p4hir.concat(%int5 : !p4hir.int<5>, %bit5 : !p4hir.bit<5>) : !p4hir.int<10> + %7 = p4hir.concat(%int5 : !p4hir.int<5>, %bit10 : !p4hir.bit<10>) : !p4hir.int<15> +} diff --git a/test/Translate/Ops/concat.p4 b/test/Translate/Ops/concat.p4 new file mode 100644 index 0000000..735a7cc --- /dev/null +++ b/test/Translate/Ops/concat.p4 @@ -0,0 +1,194 @@ +// RUN: p4mlir-translate --typeinference-only %s | FileCheck %s + +// NOTE: Assertions have been autogenerated by utils/generate-test-checks.py + +action concat_bit5_and_bit5() { + bit<5> lhs = 1; + bit<5> rhs = 2; + bit<10> r = lhs ++ rhs; +} + +action concat_bit5_and_bit10() { + bit<5> lhs = 1; + bit<10> rhs = 2; + bit<15> result = lhs ++ rhs; +} + +action concat_int5_and_int5() { + int<5> lhs = 1; + int<5> rhs = 2; + int<10> result = lhs ++ rhs; +} + +action concat_int5_and_int10() { + int<5> lhs = 1; + int<10> rhs = 2; + int<15> result = lhs ++ rhs; +} + +action concat_bit5_and_int5() { + bit<5> lhs = 1; + int<5> rhs = 2; + bit<10> result = lhs ++ rhs; +} + +action concat_bit5_and_int10() { + bit<5> lhs = 1; + int<10> rhs = 2; + bit<15> result = lhs ++ rhs; +} + +action concat_int5_and_bit5() { + int<5> lhs = 1; + bit<5> rhs = 2; + int<10> result = lhs ++ rhs; +} + +action concat_int5_and_bit10() { + int<5> lhs = 1; + bit<10> rhs = 2; + int<15> result = lhs ++ rhs; +} + +// CHECK: #[[$ATTR_0:.+]] = #p4hir.int<1> : !b5i +// CHECK: #[[$ATTR_1:.+]] = #p4hir.int<1> : !i5i +// CHECK: #[[$ATTR_2:.+]] = #p4hir.int<2> : !b10i +// CHECK: #[[$ATTR_3:.+]] = #p4hir.int<2> : !b5i +// CHECK: #[[$ATTR_4:.+]] = #p4hir.int<2> : !i10i +// CHECK: #[[$ATTR_5:.+]] = #p4hir.int<2> : !i5i + +// CHECK-LABEL: p4hir.func action @concat_bit5_and_bit5() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_0]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !b5i) : !b5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_3]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b5i) : !b5i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !b5i) : !b10i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["r", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_bit5_and_bit10() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_0]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !b5i) : !b5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_2]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b10i) : !b10i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !b10i) : !b15i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_int5_and_int5() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_1]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !i5i) : !i5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_5]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i5i) : !i5i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !i5i) : !i10i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_int5_and_int10() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_1]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !i5i) : !i5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_4]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i10i) : !i10i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !i10i) : !i15i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_bit5_and_int5() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_0]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !b5i) : !b5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_5]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i5i) : !i5i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !i5i) : !b10i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_bit5_and_int10() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_0]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !b5i) : !b5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_4]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i10i) : !i10i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !i10i) : !b15i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_int5_and_bit5() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_1]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !i5i) : !i5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_3]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b5i) : !b5i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !b5i) : !i10i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } + +// CHECK-LABEL: p4hir.func action @concat_int5_and_bit10() { +// CHECK: %[[VAL_0:.*]] = p4hir.const #[[$ATTR_1]] +// CHECK: %[[VAL_1:.*]] = p4hir.cast(%[[VAL_0]] : !i5i) : !i5i +// CHECK: %[[VAL_2:.*]] = p4hir.variable ["lhs", init] : +// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : +// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_2]] +// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b10i) : !b10i +// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : +// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : +// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : +// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : +// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !b10i) : !i15i +// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : +// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : +// CHECK: p4hir.return +// CHECK: } diff --git a/tools/p4mlir-translate/translate.cpp b/tools/p4mlir-translate/translate.cpp index e4452ed..75c2b51 100644 --- a/tools/p4mlir-translate/translate.cpp +++ b/tools/p4mlir-translate/translate.cpp @@ -337,6 +337,9 @@ class P4HIRConverter : public P4::Inspector, public P4::ResolutionContext { HANDLE_IN_POSTORDER(BAnd) HANDLE_IN_POSTORDER(BXor) + // Concat + HANDLE_IN_POSTORDER(Concat) + // Comparisons HANDLE_IN_POSTORDER(Equ) HANDLE_IN_POSTORDER(Neq) @@ -366,6 +369,7 @@ class P4HIRConverter : public P4::Inspector, public P4::ResolutionContext { mlir::Value emitUnOp(const P4::IR::Operation_Unary *unop, P4HIR::UnaryOpKind kind); mlir::Value emitBinOp(const P4::IR::Operation_Binary *binop, P4HIR::BinOpKind kind); + mlir::Value emitConcatOp(const P4::IR::Concat *concatop); mlir::Value emitCmp(const P4::IR::Operation_Relation *relop, P4HIR::CmpOpKind kind); }; @@ -588,6 +592,12 @@ mlir::Value P4HIRConverter::emitBinOp(const P4::IR::Operation_Binary *binop, return builder.create(getLoc(builder, binop), kind, getValue(binop->left), getValue(binop->right)); } + +mlir::Value P4HIRConverter::emitConcatOp(const P4::IR::Concat *concatop) { + return builder.create(getLoc(builder, concatop), getValue(concatop->left), + getValue(concatop->right)); +} + mlir::Value P4HIRConverter::emitCmp(const P4::IR::Operation_Relation *relop, P4HIR::CmpOpKind kind) { return builder.create(getLoc(builder, relop), kind, getValue(relop->left), @@ -626,6 +636,11 @@ CONVERT_BINOP(BXor, Xor); #undef CONVERT_BINOP +void P4HIRConverter::postorder(const P4::IR::Concat *concat) { + ConversionTracer trace("Converting ", concat); + setValue(concat, emitConcatOp(concat)); +} + #define CONVERT_CMP(Node, Kind) \ void P4HIRConverter::postorder(const P4::IR::Node *node) { \ ConversionTracer trace("Converting ", node); \