From 10b2bd4db2090a08c46a69de8e1d9288fa41bbf6 Mon Sep 17 00:00:00 2001 From: Morten Borup Petersen Date: Mon, 1 Nov 2021 09:35:03 +0000 Subject: [PATCH] [Handshake] Add control operand to `handshake.instance` operation (#2055) When lowering a `handshake.instance` operation, we need to be able to decide when the instance is activated; or in other words, when the input control signal is asserted. Lowering from a CDFG to handshake, this will be whenever control flow arrived to the basic block of the source `builtin.call` operation, which the `handshake.instance` op originated from. With this new operand, we are able to be explicit about the activation of the `handshake.instance` op, and be able to implement lowering of the operation to FIRRTL. --- .../circt/Dialect/Handshake/HandshakeOps.td | 7 ++ .../StandardToHandshake.cpp | 13 +++- lib/Dialect/Handshake/HandshakeOps.cpp | 11 ++++ .../StandardToHandshake/test_call.mlir | 64 ++++++++++++++++++- test/Dialect/Handshake/errors.mlir | 24 +++++++ test/handshake-runner/call_controlflow.mlir | 26 ++++++++ 6 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 test/handshake-runner/call_controlflow.mlir diff --git a/include/circt/Dialect/Handshake/HandshakeOps.td b/include/circt/Dialect/Handshake/HandshakeOps.td index 76c69dc8f736..91553f6f6bf8 100644 --- a/include/circt/Dialect/Handshake/HandshakeOps.td +++ b/include/circt/Dialect/Handshake/HandshakeOps.td @@ -157,11 +157,18 @@ def InstanceOp : Handshake_Op<"instance", [CallOpInterface]> { CallInterfaceCallable getCallableForCallee() { return (*this)->getAttrOfType("module"); } + + /// Get the control operand of this instance op + Value getControl() { + return getOperands().back(); + } }]; let assemblyFormat = [{ $module `(` $operands `)` attr-dict `:` functional-type($operands, results) }]; + + let verifier = [{ return ::verify$cppClass(*this); }]; } // This is almost exactly like a standard FuncOp, except that it has some diff --git a/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp b/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp index fab9853ad5d0..fc909b5866d3 100644 --- a/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp +++ b/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp @@ -1636,12 +1636,19 @@ struct HandshakeCanonicalizePattern : public ConversionPattern { LogicalResult replaceCallOps(handshake::FuncOp f, ConversionPatternRewriter &rewriter) { for (Block &block : f) { + /// An instance is activated whenever control arrives at the basic block of + /// the source callOp. + Operation *cntrlMg = + block.isEntryBlock() ? getStartOp(&block) : getControlMerge(&block); + assert(cntrlMg); for (Operation &op : block) { if (auto callOp = dyn_cast(op)) { + llvm::SmallVector operands; + llvm::copy(callOp.getOperands(), std::back_inserter(operands)); + operands.push_back(cntrlMg->getResult(0)); rewriter.setInsertionPoint(callOp); rewriter.replaceOpWithNewOp( - callOp, callOp.getCallee(), callOp.getResultTypes(), - callOp.getOperands()); + callOp, callOp.getCallee(), callOp.getResultTypes(), operands); } } } @@ -1701,10 +1708,10 @@ LogicalResult lowerFuncOp(mlir::FuncOp funcOp, MLIRContext *ctx) { }, ctx, newFuncOp); - (void)partiallyLowerFuncOp(replaceCallOps, ctx, newFuncOp); (void)partiallyLowerFuncOp(setControlOnlyPath, ctx, newFuncOp); (void)partiallyLowerFuncOp(addMergeOps, ctx, newFuncOp); + (void)partiallyLowerFuncOp(replaceCallOps, ctx, newFuncOp); (void)partiallyLowerFuncOp(addBranchOps, ctx, newFuncOp); (void)partiallyLowerFuncOp(addSinkOps, ctx, newFuncOp); (void)partiallyLowerFuncOp(connectConstantsToControl, ctx, diff --git a/lib/Dialect/Handshake/HandshakeOps.cpp b/lib/Dialect/Handshake/HandshakeOps.cpp index bfd683d699f2..d3783f60d513 100644 --- a/lib/Dialect/Handshake/HandshakeOps.cpp +++ b/lib/Dialect/Handshake/HandshakeOps.cpp @@ -861,6 +861,17 @@ bool handshake::JoinOp::tryExecute( return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 1); } +static LogicalResult verifyInstanceOp(handshake::InstanceOp op) { + if (op->getNumOperands() == 0) + return op.emitOpError() << "must provide at least a control operand."; + + if (!op.getControl().getType().dyn_cast()) + return op.emitOpError() + << "last operand must be a control (none-typed) operand."; + + return success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/test/Conversion/StandardToHandshake/test_call.mlir b/test/Conversion/StandardToHandshake/test_call.mlir index b4e398d2fe7b..e9a7da455daa 100644 --- a/test/Conversion/StandardToHandshake/test_call.mlir +++ b/test/Conversion/StandardToHandshake/test_call.mlir @@ -1,4 +1,5 @@ -// RUN: circt-opt -lower-std-to-handshake %s | FileCheck %s +// RUN: circt-opt -lower-std-to-handshake -split-input-file %s | FileCheck %s + func @bar(%0 : i32) -> i32 { // CHECK-LABEL: handshake.func @bar( // CHECK-SAME: %[[VAL_0:.*]]: i32, @@ -15,10 +16,67 @@ func @foo(%0 : i32) -> i32 { // CHECK-SAME: %[[VAL_0:.*]]: i32, // CHECK-SAME: %[[VAL_1:.*]]: none, ...) -> (i32, none) { // CHECK: %[[VAL_2:.*]] = "handshake.merge"(%[[VAL_0]]) : (i32) -> i32 -// CHECK: %[[VAL_3:.*]] = handshake.instance @bar(%[[VAL_2]]) : (i32) -> i32 -// CHECK: handshake.return %[[VAL_3]], %[[VAL_1]] : i32, none +// CHECK: %[[VAL_4:.*]]:2 = "handshake.fork"(%[[VAL_1]]) {control = true} : (none) -> (none, none) +// CHECK: %[[VAL_3:.*]] = handshake.instance @bar(%[[VAL_2]], %[[VAL_4]]#0) : (i32, none) -> i32 +// CHECK: handshake.return %[[VAL_3]], %[[VAL_4]]#1 : i32, none // CHECK: } %a1 = call @bar(%0) : (i32) -> i32 return %a1 : i32 } + +// ----- + +// Branching control flow with calls in each branch. + +// CHECK-LABEL: handshake.func @add( +func @add(%arg0 : i32, %arg1: i32) -> i32 { + %0 = arith.addi %arg0, %arg1 : i32 + return %0 : i32 +} + +// CHECK-LABEL: handshake.func @sub( +func @sub(%arg0 : i32, %arg1: i32) -> i32 { + %0 = arith.subi %arg0, %arg1 : i32 + return %0 : i32 +} + +// CHECK: handshake.func @main(%[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: none, ...) -> (i32, none) { +// CHECK: %[[VAL_4:.*]] = "handshake.merge"(%[[VAL_0]]) : (i32) -> i32 +// CHECK: %[[VAL_5:.*]] = "handshake.merge"(%[[VAL_1]]) : (i32) -> i32 +// CHECK: %[[VAL_6:.*]] = "handshake.merge"(%[[VAL_2]]) : (i1) -> i1 +// CHECK: %[[VAL_7:.*]]:3 = "handshake.fork"(%[[VAL_6]]) {control = false} : (i1) -> (i1, i1, i1) +// CHECK: %[[VAL_8:.*]], %[[VAL_9:.*]] = "handshake.conditional_branch"(%[[VAL_7]]#2, %[[VAL_4]]) {control = false} : (i1, i32) -> (i32, i32) +// CHECK: %[[VAL_10:.*]], %[[VAL_11:.*]] = "handshake.conditional_branch"(%[[VAL_7]]#1, %[[VAL_5]]) {control = false} : (i1, i32) -> (i32, i32) +// CHECK: %[[VAL_12:.*]], %[[VAL_13:.*]] = "handshake.conditional_branch"(%[[VAL_7]]#0, %[[VAL_3]]) {control = true} : (i1, none) -> (none, none) +// CHECK: %[[VAL_14:.*]] = "handshake.merge"(%[[VAL_8]]) : (i32) -> i32 +// CHECK: %[[VAL_15:.*]] = "handshake.merge"(%[[VAL_10]]) : (i32) -> i32 +// CHECK: %[[VAL_16:.*]]:2 = "handshake.control_merge"(%[[VAL_12]]) {control = true} : (none) -> (none, index) +// CHECK: %[[VAL_17:.*]]:2 = "handshake.fork"(%[[VAL_16]]#0) {control = true} : (none) -> (none, none) +// CHECK: "handshake.sink"(%[[VAL_16]]#1) : (index) -> () +// CHECK: %[[VAL_18:.*]] = handshake.instance @add(%[[VAL_14]], %[[VAL_15]], %[[VAL_17]]#1) : (i32, i32, none) -> i32 +// CHECK: %[[VAL_19:.*]] = "handshake.branch"(%[[VAL_17]]#0) {control = true} : (none) -> none +// CHECK: %[[VAL_20:.*]] = "handshake.branch"(%[[VAL_18]]) {control = false} : (i32) -> i32 +// CHECK: %[[VAL_21:.*]] = "handshake.merge"(%[[VAL_9]]) : (i32) -> i32 +// CHECK: %[[VAL_22:.*]] = "handshake.merge"(%[[VAL_11]]) : (i32) -> i32 +// CHECK: %[[VAL_23:.*]]:2 = "handshake.control_merge"(%[[VAL_13]]) {control = true} : (none) -> (none, index) +// CHECK: %[[VAL_24:.*]]:2 = "handshake.fork"(%[[VAL_23]]#0) {control = true} : (none) -> (none, none) +// CHECK: "handshake.sink"(%[[VAL_23]]#1) : (index) -> () +// CHECK: %[[VAL_25:.*]] = handshake.instance @sub(%[[VAL_21]], %[[VAL_22]], %[[VAL_24]]#1) : (i32, i32, none) -> i32 +// CHECK: %[[VAL_26:.*]] = "handshake.branch"(%[[VAL_24]]#0) {control = true} : (none) -> none +// CHECK: %[[VAL_27:.*]] = "handshake.branch"(%[[VAL_25]]) {control = false} : (i32) -> i32 +// CHECK: %[[VAL_28:.*]]:2 = "handshake.control_merge"(%[[VAL_26]], %[[VAL_19]]) {control = true} : (none, none) -> (none, index) +// CHECK: %[[VAL_29:.*]] = "handshake.mux"(%[[VAL_28]]#1, %[[VAL_27]], %[[VAL_20]]) : (index, i32, i32) -> i32 +// CHECK: handshake.return %[[VAL_29]], %[[VAL_28]]#0 : i32, none +// CHECK: } +func @main(%arg0 : i32, %arg1 : i32, %cond : i1) -> i32 { + cond_br %cond, ^bb1, ^bb2 +^bb1: + %0 = call @add(%arg0, %arg1) : (i32, i32) -> i32 + br ^bb3(%0 : i32) +^bb2: + %1 = call @sub(%arg0, %arg1) : (i32, i32) -> i32 + br ^bb3(%1 : i32) +^bb3(%res : i32): + return %res : i32 +} diff --git a/test/Dialect/Handshake/errors.mlir b/test/Dialect/Handshake/errors.mlir index 1e9d35ed9d7b..fe539b0101f5 100644 --- a/test/Dialect/Handshake/errors.mlir +++ b/test/Dialect/Handshake/errors.mlir @@ -37,3 +37,27 @@ handshake.func @invalid_mux_narrow_select(%arg0: i1, %arg1: i32, %arg2: i32, %ar %0 = "handshake.mux"(%arg0, %arg1, %arg2, %arg3) : (i1, i32, i32, i32) -> (i32) return %0 : i32 } + +// ----- + +handshake.func @foo(%ctrl : none) -> none{ + handshake.return %ctrl : none +} + +handshake.func @invalid_instance_op(%arg0 : i32, %ctrl : none) -> none { + // expected-error @+1 {{'handshake.instance' op last operand must be a control (none-typed) operand.}} + handshake.instance @foo(%ctrl, %arg0) : (none, i32) -> () + handshake.return %ctrl : none +} + +// ----- + +handshake.func @foo(%ctrl : none) -> none{ + handshake.return %ctrl : none +} + +handshake.func @invalid_instance_op(%ctrl : none) -> none { + // expected-error @+1 {{'handshake.instance' op must provide at least a control operand.}} + handshake.instance @foo() : () -> () + handshake.return %ctrl : none +} diff --git a/test/handshake-runner/call_controlflow.mlir b/test/handshake-runner/call_controlflow.mlir new file mode 100644 index 000000000000..a956ea9ed621 --- /dev/null +++ b/test/handshake-runner/call_controlflow.mlir @@ -0,0 +1,26 @@ +// RUN: handshake-runner %s 3 2 1 | FileCheck %s +// RUN: circt-opt -lower-std-to-handshake %s > handshake.mlir +// RUN handshake-runner handshake.mlir 3 2 1 | FileCheck %s +// CHECK: 5 + +func @add(%arg0 : i32, %arg1: i32) -> i32 { + %0 = arith.addi %arg0, %arg1 : i32 + return %0 : i32 +} + +func @sub(%arg0 : i32, %arg1: i32) -> i32 { + %0 = arith.subi %arg0, %arg1 : i32 + return %0 : i32 +} + +func @main(%arg0 : i32, %arg1 : i32, %cond : i1) -> i32 { + cond_br %cond, ^bb1, ^bb2 +^bb1: + %0 = call @add(%arg0, %arg1) : (i32, i32) -> i32 + br ^bb3(%0 : i32) +^bb2: + %1 = call @sub(%arg0, %arg1) : (i32, i32) -> i32 + br ^bb3(%1 : i32) +^bb3(%res : i32): + return %res : i32 +}