[Ibis] Reblock should also create blocks around non-delimited ops (#6218)

e.g. any MLIR block which contains ops should be moved into ibis `sblocks`. The `ibis.sblock.inline.begin/end` ops are only intended to delimit a scope of operations which has block constraints on them. However, this pass was always intended to also work on non-delimited ops. In the end, the result of the pass should be that the IR consists strictly of terminators (control flow) and `ibis.sblock` ops.
This commit is contained in:
Morten Borup Petersen 2023-09-28 11:59:03 +02:00 committed by GitHub
parent 303fddeb8b
commit a0ce6e73a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 111 deletions

View File

@ -42,9 +42,14 @@ public:
ConversionPatternRewriter &rewriter) const override {
Location loc = op.getLoc();
// Start the inline block...
auto inlineStart = rewriter.create<ibis::InlineStaticBlockBeginOp>(loc);
inlineStart->setAttrs(op->getAttrs());
// We only create block markers in case the sblock has attributes.
bool hasAttributes = !op->getAttrs().empty();
if (hasAttributes) {
// Start the inline block...
auto inlineStart = rewriter.create<ibis::InlineStaticBlockBeginOp>(loc);
inlineStart->setAttrs(op->getAttrs());
}
// Inline the sblock.
Block *sblockBody = op.getBodyBlock();
@ -56,9 +61,11 @@ public:
for (auto [res, val] : llvm::zip(op.getResults(), ret.getRetValues()))
rewriter.replaceAllUsesWith(res, val);
// Close the inline block
rewriter.setInsertionPoint(ret);
rewriter.create<ibis::InlineStaticBlockEndOp>(loc);
if (hasAttributes) {
// Close the inline block
rewriter.setInsertionPoint(ret);
rewriter.create<ibis::InlineStaticBlockEndOp>(loc);
}
rewriter.eraseOp(ret);
rewriter.eraseOp(op);

View File

@ -39,7 +39,8 @@ void circt::ibis::loadIbisLowLevelPassPipeline(mlir::PassManager &pm) {
}
void circt::ibis::loadIbisHighLevelPassPipeline(mlir::PassManager &pm) {
pm.nest<ibis::MethodOp>().addPass(ibis::createInlineSBlocksPass());
pm.nest<ibis::ClassOp>().nest<ibis::MethodOp>().addPass(
ibis::createInlineSBlocksPass());
pm.addPass(mlir::createMem2Reg());
// TODO @mortbopet: Add a verification pass to ensure that there are no more
@ -51,7 +52,8 @@ void circt::ibis::loadIbisHighLevelPassPipeline(mlir::PassManager &pm) {
pm.addPass(circt::createMaximizeSSAPass());
// SSA maximal form achieved. Reconstruct the Ibis sblocks.
pm.nest<ibis::MethodOp>().addPass(ibis::createReblockPass());
pm.nest<ibis::ClassOp>().nest<ibis::MethodOp>().addPass(
ibis::createReblockPass());
pm.addPass(ibis::createArgifyBlocksPass());
pm.addPass(createSimpleCanonicalizerPass());

View File

@ -25,29 +25,55 @@ struct ReblockPass : public IbisReblockBase<ReblockPass> {
void runOnOperation() override;
// Transforms an `ibis.sblock.inline.begin/end` scope into an `ibis.sblock`.
LogicalResult reblock(ibis::InlineStaticBlockBeginOp);
LogicalResult reblock(ArrayRef<Operation *> ops, Operation *blockTerminator);
};
// Returns true if the given op signal that the existing set of sblock
// operations should be closed.
static bool isSBlockTerminator(Operation *op) {
return op->hasTrait<OpTrait::IsTerminator>() ||
isa<ibis::InlineStaticBlockEndOp, InlineStaticBlockBeginOp>(op);
}
} // anonymous namespace
void ReblockPass::runOnOperation() {
MethodOp parent = getOperation();
for (auto blockBeginOp : llvm::make_early_inc_range(
parent.getOps<ibis::InlineStaticBlockBeginOp>())) {
if (failed(reblock(blockBeginOp)))
return signalPassFailure();
llvm::SmallVector<Operation *> opsToBlock;
for (Block &block : parent.getRegion()) {
for (Operation &op : llvm::make_early_inc_range(block)) {
if (isSBlockTerminator(&op)) {
if (opsToBlock.empty())
continue;
if (failed(reblock(opsToBlock, &op)))
return signalPassFailure();
opsToBlock.clear();
if (isa<InlineStaticBlockBeginOp>(op))
opsToBlock.push_back(&op);
} else
opsToBlock.push_back(&op);
}
}
}
LogicalResult ReblockPass::reblock(ibis::InlineStaticBlockBeginOp beginOp) {
LogicalResult ReblockPass::reblock(ArrayRef<Operation *> ops,
Operation *blockTerminator) {
// Determine which values we need to return from within the block scope.
// This is done by collecting all values defined within the start/end scope,
// and recording uses that exist outside of the scope.
ibis::InlineStaticBlockEndOp endOp = beginOp.getEndOp();
assert(endOp);
ibis::InlineStaticBlockBeginOp blockBeginOp;
if (isa<InlineStaticBlockEndOp>(blockTerminator)) {
blockBeginOp = dyn_cast<InlineStaticBlockBeginOp>(ops.front());
assert(blockBeginOp &&
"Expected block begin op when a block end block was provided");
ops = ops.drop_front();
}
auto startIt = beginOp->getIterator();
auto endIt = endOp->getIterator();
Operation *beginOp = ops.front();
auto startIt = ops.front()->getIterator();
Operation *endOp = ops.back();
auto terminatorIt = blockTerminator->getIterator();
Block *sblockParentBlock = beginOp->getBlock();
auto usedOutsideBlock = [&](OpOperand &use) {
@ -61,7 +87,7 @@ LogicalResult ReblockPass::reblock(ibis::InlineStaticBlockBeginOp beginOp) {
};
llvm::MapVector<Value, llvm::SmallVector<OpOperand *>> returnValueUses;
for (auto &op : llvm::make_range(startIt, endIt)) {
for (auto &op : llvm::make_range(startIt, terminatorIt)) {
for (Value result : op.getResults()) {
for (OpOperand &use : result.getUses())
if (usedOutsideBlock(use))
@ -79,16 +105,19 @@ LogicalResult ReblockPass::reblock(ibis::InlineStaticBlockBeginOp beginOp) {
auto b = OpBuilder(beginOp);
auto ibisBlock =
b.create<StaticBlockOp>(beginOp.getLoc(), blockRetTypes, ValueRange{});
b.create<StaticBlockOp>(beginOp->getLoc(), blockRetTypes, ValueRange{});
// The new `ibis.sblock` should inherit the attributes of the block begin op.
ibisBlock->setAttrs(beginOp->getAttrs());
// The new `ibis.sblock` should inherit the attributes of the block begin op,
// if provided.
if (blockBeginOp)
ibisBlock->setAttrs(blockBeginOp->getAttrs());
// Move operations into the `ibis.sblock` op.
BlockReturnOp blockReturn =
cast<BlockReturnOp>(ibisBlock.getBodyBlock()->getTerminator());
for (auto &op : llvm::make_early_inc_range(llvm::make_range(startIt, endIt)))
for (auto &op :
llvm::make_early_inc_range(llvm::make_range(startIt, terminatorIt)))
op.moveBefore(blockReturn);
// Append the terminator operands to the block return.
@ -102,9 +131,13 @@ LogicalResult ReblockPass::reblock(ibis::InlineStaticBlockBeginOp beginOp) {
for (OpOperand *use : uses)
use->set(blockRet);
}
// Erase the start/end ops.
beginOp.erase();
endOp.erase();
// If this was an explicit sblock, erase the markers.
if (blockBeginOp) {
blockBeginOp.erase();
blockTerminator->erase();
}
return success();
}

View File

@ -1,4 +1,4 @@
// RUN: circt-opt %s --pass-pipeline='builtin.module(ibis.class(ibis.method(ibis-inline-sblocks)))' --allow-unregistered-dialect | FileCheck %s
// RUN: circt-opt --pass-pipeline='builtin.module(ibis.class(ibis.method(ibis-inline-sblocks)))' --allow-unregistered-dialect %s | FileCheck %s
// CHECK-LABEL: ibis.class @Inline1 {
// CHECK: %[[VAL_0:.*]] = ibis.this @Inline1
@ -24,41 +24,6 @@ ibis.class @Inline1 {
// CHECK-LABEL: ibis.class @Inline2 {
// CHECK: %[[VAL_0:.*]] = ibis.this @Inline2
// CHECK: ibis.method @foo(%[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32) {
// CHECK: ibis.sblock.inline.begin {maxThreads = 1 : i64}
// CHECK: %[[VAL_3:.*]] = "foo.op1"(%[[VAL_1]], %[[VAL_2]]) : (i32, i32) -> i32
// CHECK: ibis.sblock.inline.end
// CHECK: cf.br ^bb1
// CHECK: ^bb1:
// CHECK: ibis.sblock.inline.begin
// CHECK: %[[VAL_4:.*]] = "foo.op2"(%[[VAL_3]], %[[VAL_1]]) : (i32, i32) -> i32
// CHECK: ibis.sblock.inline.end
// CHECK: ibis.return
// CHECK: }
// CHECK: }
ibis.class @Inline2 {
%this = ibis.this @Inline2
// Given the "simple" blocks (i.e. the ibis.sblock is the entirety of the MLIR
// block), we should see that no superflous MLIR blocks are created.
ibis.method @foo(%a : i32, %b : i32) {
%0 = ibis.sblock() -> (i32) attributes {maxThreads = 1} {
%res = "foo.op1"(%a, %b) : (i32, i32) -> i32
ibis.sblock.return %res : i32
}
cf.br ^bb1
^bb1:
%1 = ibis.sblock() -> (i32) {
%res = "foo.op2"(%0, %a) : (i32, i32) -> i32
ibis.sblock.return %res : i32
}
ibis.return
}
}
// CHECK-LABEL: ibis.class @Inline3 {
// CHECK: %[[VAL_0:.*]] = ibis.this @Inline3
// CHECK: ibis.method @foo(%[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32) {
// CHECK: "foo.unused1"() : () -> ()
// CHECK: ibis.sblock.inline.begin {maxThreads = 1 : i64}
// CHECK: %[[VAL_3:.*]] = "foo.op1"(%[[VAL_1]], %[[VAL_2]]) : (i32, i32) -> i32
@ -67,18 +32,13 @@ ibis.class @Inline2 {
// CHECK: cf.br ^bb1
// CHECK: ^bb1:
// CHECK: "foo.unused3"() : () -> ()
// CHECK: ibis.sblock.inline.begin
// CHECK: %[[VAL_4:.*]] = "foo.op2"(%[[VAL_3]], %[[VAL_1]]) : (i32, i32) -> i32
// CHECK: ibis.sblock.inline.end
// CHECK: "foo.unused4"() : () -> ()
// CHECK: ibis.return
// CHECK: }
// CHECK: }
ibis.class @Inline3 {
%this = ibis.this @Inline3
// Opposite to the above, we have extra ops surrounding the ibis.sblock,
// which requires the addition of new MLIR blocks and cf ops.
ibis.class @Inline2 {
%this = ibis.this @Inline2
ibis.method @foo(%a : i32, %b : i32) {
"foo.unused1"() : () -> ()
%0 = ibis.sblock() -> (i32) attributes {maxThreads = 1} {

View File

@ -3,15 +3,26 @@
// CHECK-LABEL: ibis.class @Reblock {
// CHECK: %[[VAL_0:.*]] = ibis.this @Reblock
// CHECK: ibis.method @foo(%[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32) -> i32 {
// CHECK: %[[VAL_3:.*]]:2 = ibis.sblock () -> (i32, i32) attributes {maxThreads = 2 : i64} {
// CHECK: %[[VAL_3:.*]] = ibis.sblock () -> i32 {
// CHECK: %[[VAL_4:.*]] = arith.addi %[[VAL_1]], %[[VAL_2]] : i32
// CHECK: %[[VAL_5:.*]] = arith.addi %[[VAL_4]], %[[VAL_2]] : i32
// CHECK: %[[VAL_6:.*]] = arith.addi %[[VAL_5]], %[[VAL_2]] : i32
// CHECK: ibis.sblock.return %[[VAL_5]], %[[VAL_6]] : i32, i32
// CHECK: ibis.sblock.return %[[VAL_4]] : i32
// CHECK: }
// CHECK: cf.br ^bb1(%[[VAL_7:.*]]#0 : i32)
// CHECK: ^bb1(%[[VAL_8:.*]]: i32):
// CHECK: ibis.return %[[VAL_7]]#1 : i32
// CHECK: %[[VAL_5:.*]] = ibis.sblock () -> i32 attributes {maxThreads = 2 : i64} {
// CHECK: %[[VAL_6:.*]] = arith.addi %[[VAL_3]], %[[VAL_2]] : i32
// CHECK: %[[VAL_7:.*]] = arith.addi %[[VAL_6]], %[[VAL_2]] : i32
// CHECK: ibis.sblock.return %[[VAL_7]] : i32
// CHECK: }
// CHECK: %[[VAL_8:.*]] = ibis.sblock () -> i32 {
// CHECK: %[[VAL_9:.*]] = arith.addi %[[VAL_5]], %[[VAL_2]] : i32
// CHECK: ibis.sblock.return %[[VAL_9]] : i32
// CHECK: }
// CHECK: cf.br ^bb1(%[[VAL_8]] : i32)
// CHECK: ^bb1(%[[VAL_10:.*]]: i32):
// CHECK: %[[VAL_11:.*]] = ibis.sblock () -> i32 {
// CHECK: %[[VAL_12:.*]] = arith.addi %[[VAL_10]], %[[VAL_8]] : i32
// CHECK: ibis.sblock.return %[[VAL_12]] : i32
// CHECK: }
// CHECK: ibis.return %[[VAL_11]] : i32
// CHECK: }
// CHECK: }
@ -19,15 +30,17 @@ ibis.class @Reblock {
%this = ibis.this @Reblock
ibis.method @foo(%arg0 : i32, %arg1 : i32) -> i32 {
%0 = arith.addi %arg0, %arg1 : i32
ibis.sblock.inline.begin {maxThreads = 2}
%inner = arith.addi %arg0, %arg1 : i32
%0 = arith.addi %inner, %arg1 : i32
%1 = arith.addi %0, %arg1 : i32
%inner = arith.addi %0, %arg1 : i32
%1 = arith.addi %inner, %arg1 : i32
ibis.sblock.inline.end
%2 = arith.addi %1, %arg1 : i32
// %0 is used within the same MLIR block but outside the scope.
cf.br ^bb1(%0 : i32)
cf.br ^bb1(%2 : i32)
^bb1(%barg : i32):
%3 = arith.addi %barg, %2 : i32
// %1 is used in a different MLIR block (dominated by the sblock parent block).
ibis.return %1 : i32
ibis.return %3 : i32
}
}

View File

@ -3,37 +3,55 @@
// CHECK-LABEL: ibis.class @ToHandshake {
// CHECK: %[[VAL_0:.*]] = ibis.this @ToHandshake
// CHECK: ibis.method.df @foo(%[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: none) -> (i32, none) {
// CHECK: %[[VAL_5:.*]] = handshake.constant %[[VAL_4]] {value = 2 : i32} : i32
// CHECK: %[[VAL_5:.*]] = handshake.constant %[[VAL_4]] {value = 0 : i32} : i32
// CHECK: %[[VAL_6:.*]] = handshake.constant %[[VAL_4]] {value = 1 : index} : index
// CHECK: %[[VAL_7:.*]] = handshake.constant %[[VAL_4]] {value = 0 : i32} : i32
// CHECK: %[[VAL_8:.*]] = handshake.buffer [1] seq %[[VAL_9:.*]] {initValues = [0]} : i1
// CHECK: %[[VAL_10:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_4]], %[[VAL_11:.*]]] : i1, none
// CHECK: %[[VAL_12:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_1]], %[[VAL_13:.*]]] : i1, index
// CHECK: %[[VAL_14:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_7]], %[[VAL_15:.*]]] : i1, i32
// CHECK: %[[VAL_16:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_2]], %[[VAL_17:.*]]] : i1, index
// CHECK: %[[VAL_18:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_5]], %[[VAL_19:.*]]] : i1, i32
// CHECK: %[[VAL_20:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_6]], %[[VAL_21:.*]]] : i1, index
// CHECK: %[[VAL_22:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_7]], %[[VAL_23:.*]]] : i1, i32
// CHECK: %[[VAL_9]] = arith.cmpi slt, %[[VAL_12]], %[[VAL_16]] : index
// CHECK: %[[VAL_24:.*]], %[[VAL_25:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_12]] : index
// CHECK: %[[VAL_26:.*]], %[[VAL_27:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_14]] : i32
// CHECK: %[[VAL_17]], %[[VAL_28:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_16]] : index
// CHECK: %[[VAL_19]], %[[VAL_29:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_18]] : i32
// CHECK: %[[VAL_21]], %[[VAL_30:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_20]] : index
// CHECK: %[[VAL_23]], %[[VAL_31:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_22]] : i32
// CHECK: %[[VAL_32:.*]], %[[VAL_33:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_10]] : none
// CHECK: %[[VAL_34:.*]] = arith.index_cast %[[VAL_24]] : index to i32
// CHECK: %[[VAL_35:.*]] = arith.remsi %[[VAL_26]], %[[VAL_19]] : i32
// CHECK: %[[VAL_36:.*]] = arith.cmpi eq, %[[VAL_35]], %[[VAL_23]] : i32
// CHECK: %[[VAL_37:.*]], %[[VAL_38:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_26]] : i32
// CHECK: %[[VAL_39:.*]], %[[VAL_40:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_32]] : none
// CHECK: %[[VAL_41:.*]], %[[VAL_42:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_34]] : i32
// CHECK: %[[VAL_43:.*]] = arith.addi %[[VAL_37]], %[[VAL_41]] : i32
// CHECK: %[[VAL_44:.*]] = arith.subi %[[VAL_38]], %[[VAL_42]] : i32
// CHECK: %[[VAL_15]] = handshake.mux %[[VAL_45:.*]] {{\[}}%[[VAL_44]], %[[VAL_43]]] : index, i32
// CHECK: %[[VAL_11]], %[[VAL_45]] = handshake.control_merge %[[VAL_40]], %[[VAL_39]] : none, index
// CHECK: %[[VAL_13]] = arith.addi %[[VAL_24]], %[[VAL_21]] : index
// CHECK: ibis.return %[[VAL_27]], %[[VAL_33]] : i32, none
// CHECK: %[[VAL_7:.*]] = handshake.constant %[[VAL_4]] {value = 2 : i32} : i32
// CHECK: %[[VAL_8:.*]]:3 = ibis.sblock () -> (i32, index, i32) {
// CHECK: ibis.sblock.return %[[VAL_7]], %[[VAL_6]], %[[VAL_5]] : i32, index, i32
// CHECK: }
// CHECK: %[[VAL_9:.*]] = handshake.buffer [1] seq %[[VAL_10:.*]] {initValues = [0]} : i1
// CHECK: %[[VAL_11:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_4]], %[[VAL_12:.*]]] : i1, none
// CHECK: %[[VAL_13:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_1]], %[[VAL_14:.*]]] : i1, index
// CHECK: %[[VAL_15:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_16:.*]]#2, %[[VAL_17:.*]]] : i1, i32
// CHECK: %[[VAL_18:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_2]], %[[VAL_19:.*]]] : i1, index
// CHECK: %[[VAL_20:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_16]]#0, %[[VAL_21:.*]]] : i1, i32
// CHECK: %[[VAL_22:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_16]]#1, %[[VAL_23:.*]]] : i1, index
// CHECK: %[[VAL_24:.*]] = handshake.mux %[[VAL_9]] {{\[}}%[[VAL_16]]#2, %[[VAL_25:.*]]] : i1, i32
// CHECK: %[[VAL_10]] = ibis.sblock (%[[VAL_26:.*]] : index = %[[VAL_13]], %[[VAL_27:.*]] : index = %[[VAL_18]]) -> i1 {
// CHECK: %[[VAL_28:.*]] = arith.cmpi slt, %[[VAL_26]], %[[VAL_27]] : index
// CHECK: ibis.sblock.return %[[VAL_28]] : i1
// CHECK: }
// CHECK: %[[VAL_29:.*]], %[[VAL_30:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_13]] : index
// CHECK: %[[VAL_31:.*]], %[[VAL_32:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_15]] : i32
// CHECK: %[[VAL_19]], %[[VAL_33:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_18]] : index
// CHECK: %[[VAL_21]], %[[VAL_34:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_20]] : i32
// CHECK: %[[VAL_23]], %[[VAL_35:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_22]] : index
// CHECK: %[[VAL_25]], %[[VAL_36:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_24]] : i32
// CHECK: %[[VAL_37:.*]], %[[VAL_38:.*]] = handshake.cond_br %[[VAL_10]], %[[VAL_11]] : none
// CHECK: %[[VAL_39:.*]]:2 = ibis.sblock (%[[VAL_26]] : index = %[[VAL_29]], %[[VAL_40:.*]] : i32 = %[[VAL_31]], %[[VAL_41:.*]] : i32 = %[[VAL_21]], %[[VAL_42:.*]] : i32 = %[[VAL_25]]) -> (i32, i1) {
// CHECK: %[[VAL_43:.*]] = arith.index_cast %[[VAL_26]] : index to i32
// CHECK: %[[VAL_44:.*]] = arith.remsi %[[VAL_40]], %[[VAL_41]] : i32
// CHECK: %[[VAL_45:.*]] = arith.cmpi eq, %[[VAL_44]], %[[VAL_42]] : i32
// CHECK: ibis.sblock.return %[[VAL_43]], %[[VAL_45]] : i32, i1
// CHECK: }
// CHECK: %[[VAL_46:.*]], %[[VAL_47:.*]] = handshake.cond_br %[[VAL_48:.*]]#1, %[[VAL_31]] : i32
// CHECK: %[[VAL_49:.*]], %[[VAL_50:.*]] = handshake.cond_br %[[VAL_48]]#1, %[[VAL_37]] : none
// CHECK: %[[VAL_51:.*]], %[[VAL_52:.*]] = handshake.cond_br %[[VAL_48]]#1, %[[VAL_48]]#0 : i32
// CHECK: %[[VAL_53:.*]] = ibis.sblock (%[[VAL_26]] : i32 = %[[VAL_46]], %[[VAL_54:.*]] : i32 = %[[VAL_51]]) -> i32 {
// CHECK: %[[VAL_55:.*]] = arith.addi %[[VAL_26]], %[[VAL_54]] : i32
// CHECK: ibis.sblock.return %[[VAL_55]] : i32
// CHECK: }
// CHECK: %[[VAL_56:.*]] = ibis.sblock (%[[VAL_26]] : i32 = %[[VAL_47]], %[[VAL_57:.*]] : i32 = %[[VAL_52]]) -> i32 {
// CHECK: %[[VAL_58:.*]] = arith.subi %[[VAL_26]], %[[VAL_57]] : i32
// CHECK: ibis.sblock.return %[[VAL_58]] : i32
// CHECK: }
// CHECK: %[[VAL_17]] = handshake.mux %[[VAL_59:.*]] {{\[}}%[[VAL_56]], %[[VAL_53]]] : index, i32
// CHECK: %[[VAL_12]], %[[VAL_59]] = handshake.control_merge %[[VAL_50]], %[[VAL_49]] : none, index
// CHECK: %[[VAL_14]] = ibis.sblock (%[[VAL_26]] : index = %[[VAL_29]], %[[VAL_60:.*]] : index = %[[VAL_23]]) -> index {
// CHECK: %[[VAL_61:.*]] = arith.addi %[[VAL_26]], %[[VAL_60]] : index
// CHECK: ibis.sblock.return %[[VAL_61]] : index
// CHECK: }
// CHECK: ibis.return %[[VAL_32]], %[[VAL_38]] : i32, none
// CHECK: }
// CHECK: }