Skip to content

Commit

Permalink
Add concat operation (#63)
Browse files Browse the repository at this point in the history
Signed-off-by: Vladimir Shiryaev <[email protected]>
  • Loading branch information
tagolog authored Feb 11, 2025
1 parent 797b447 commit 2efc3aa
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 0 deletions.
41 changes: 41 additions & 0 deletions include/p4mlir/Dialect/P4HIR/P4HIR_Ops.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<N>` or `p4hir.int<N>`, 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<BitsType>();
auto rhsBits = rhs.getType().cast<BitsType>();
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">;
Expand Down
21 changes: 21 additions & 0 deletions lib/Dialect/P4HIR/P4HIR_Ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@ void P4HIR::BinOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
setNameFn(getResult(), stringifyEnum(getKind()));
}

//===----------------------------------------------------------------------===//
// ConcatOp
//===----------------------------------------------------------------------===//

LogicalResult P4HIR::ConcatOp::verify() {
auto lhsType = cast<BitsType>(getOperand(0).getType());
auto rhsType = cast<BitsType>(getOperand(1).getType());
auto resultType = cast<BitsType>(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
//===----------------------------------------------------------------------===//
Expand Down
22 changes: 22 additions & 0 deletions test/Dialect/P4HIR/concat.mlir
Original file line number Diff line number Diff line change
@@ -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>
}
194 changes: 194 additions & 0 deletions test/Translate/Ops/concat.p4
Original file line number Diff line number Diff line change
@@ -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] : <!b5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_3]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b5i) : !b5i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!b5i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!b5i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!b5i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !b5i) : !b10i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["r", init] : <!b10i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!b10i>
// 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] : <!b5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_2]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b10i) : !b10i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!b10i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!b10i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!b10i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !b10i) : !b15i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!b15i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!b15i>
// 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] : <!i5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_5]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i5i) : !i5i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!i5i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!i5i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!i5i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !i5i) : !i10i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!i10i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!i10i>
// 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] : <!i5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_4]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i10i) : !i10i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!i10i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!i10i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!i10i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !i10i) : !i15i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!i15i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!i15i>
// 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] : <!b5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_5]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i5i) : !i5i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!i5i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!i5i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!i5i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !i5i) : !b10i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!b10i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!b10i>
// 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] : <!b5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_4]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !i10i) : !i10i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!i10i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!i10i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!b5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!i10i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !b5i, %[[VAL_7]] : !i10i) : !b15i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!b15i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!b15i>
// 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] : <!i5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_3]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b5i) : !b5i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!b5i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!b5i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!b5i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !b5i) : !i10i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!i10i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!i10i>
// 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] : <!i5i>
// CHECK: p4hir.assign %[[VAL_1]], %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_3:.*]] = p4hir.const #[[$ATTR_2]]
// CHECK: %[[VAL_4:.*]] = p4hir.cast(%[[VAL_3]] : !b10i) : !b10i
// CHECK: %[[VAL_5:.*]] = p4hir.variable ["rhs", init] : <!b10i>
// CHECK: p4hir.assign %[[VAL_4]], %[[VAL_5]] : <!b10i>
// CHECK: %[[VAL_6:.*]] = p4hir.read %[[VAL_2]] : <!i5i>
// CHECK: %[[VAL_7:.*]] = p4hir.read %[[VAL_5]] : <!b10i>
// CHECK: %[[VAL_8:.*]] = p4hir.concat(%[[VAL_6]] : !i5i, %[[VAL_7]] : !b10i) : !i15i
// CHECK: %[[VAL_9:.*]] = p4hir.variable ["result", init] : <!i15i>
// CHECK: p4hir.assign %[[VAL_8]], %[[VAL_9]] : <!i15i>
// CHECK: p4hir.return
// CHECK: }
15 changes: 15 additions & 0 deletions tools/p4mlir-translate/translate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
};

Expand Down Expand Up @@ -588,6 +592,12 @@ mlir::Value P4HIRConverter::emitBinOp(const P4::IR::Operation_Binary *binop,
return builder.create<P4HIR::BinOp>(getLoc(builder, binop), kind, getValue(binop->left),
getValue(binop->right));
}

mlir::Value P4HIRConverter::emitConcatOp(const P4::IR::Concat *concatop) {
return builder.create<P4HIR::ConcatOp>(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<P4HIR::CmpOp>(getLoc(builder, relop), kind, getValue(relop->left),
Expand Down Expand Up @@ -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); \
Expand Down

0 comments on commit 2efc3aa

Please sign in to comment.