forked from OSchip/llvm-project
Add an eager API version for BR and COND_BR
When building unstructured control-flow there is a need to construct mlir::Block* before being able to fill them. This invites goto-style programming. This CL introduces an alternative eager API for BR and COND_BR in which blocks are created eagerly and captured on the fly. This allows reducing the number of calls to `BlockBuilder` from 4 to 2 in the `builder_blocks_eager` test and from 3 to 2 in the `builder_cond_branch_eager` test. PiperOrigin-RevId: 237046114
This commit is contained in:
parent
38f1d2d77e
commit
ee4a80bbd6
|
@ -43,6 +43,22 @@ namespace intrinsics {
|
||||||
/// All Handles have already captured previously constructed IR objects.
|
/// All Handles have already captured previously constructed IR objects.
|
||||||
ValueHandle BR(BlockHandle bh, ArrayRef<ValueHandle> operands);
|
ValueHandle BR(BlockHandle bh, ArrayRef<ValueHandle> operands);
|
||||||
|
|
||||||
|
/// Creates a new mlir::Block* and branches to it from the current block.
|
||||||
|
/// Argument types are specified by `operands`.
|
||||||
|
/// Captures the new block in `bh` and the actual `operands` in `captures`. To
|
||||||
|
/// insert the new mlir::Block*, a local ScopedContext is constructed and
|
||||||
|
/// released to the current block. The branch instruction is then added to the
|
||||||
|
/// new block.
|
||||||
|
///
|
||||||
|
/// Prerequisites:
|
||||||
|
/// `b` has not yet captured an mlir::Block*.
|
||||||
|
/// No `captures` have captured any mlir::Value*.
|
||||||
|
/// All `operands` have already captured an mlir::Value*
|
||||||
|
/// captures.size() == operands.size()
|
||||||
|
/// captures and operands are pairwise of the same type.
|
||||||
|
ValueHandle BR(BlockHandle *bh, ArrayRef<ValueHandle *> captures,
|
||||||
|
ArrayRef<ValueHandle> operands);
|
||||||
|
|
||||||
/// Branches into the mlir::Block* captured by BlockHandle `trueBranch` with
|
/// Branches into the mlir::Block* captured by BlockHandle `trueBranch` with
|
||||||
/// `trueOperands` if `cond` evaluates to `true` (resp. `falseBranch` and
|
/// `trueOperands` if `cond` evaluates to `true` (resp. `falseBranch` and
|
||||||
/// `falseOperand` if `cond` evaluates to `false`).
|
/// `falseOperand` if `cond` evaluates to `false`).
|
||||||
|
@ -53,6 +69,29 @@ ValueHandle COND_BR(ValueHandle cond, BlockHandle trueBranch,
|
||||||
ArrayRef<ValueHandle> trueOperands, BlockHandle falseBranch,
|
ArrayRef<ValueHandle> trueOperands, BlockHandle falseBranch,
|
||||||
ArrayRef<ValueHandle> falseOperands);
|
ArrayRef<ValueHandle> falseOperands);
|
||||||
|
|
||||||
|
/// Eagerly creates new mlir::Block* with argument types specified by
|
||||||
|
/// `trueOperands`/`falseOperands`.
|
||||||
|
/// Captures the new blocks in `trueBranch`/`falseBranch` and the arguments in
|
||||||
|
/// `trueCaptures/falseCaptures`.
|
||||||
|
/// To insert the new mlir::Block*, a local ScopedContext is constructed and
|
||||||
|
/// released. The branch instruction is then added in the original location and
|
||||||
|
/// targeting the eagerly constructed blocks.
|
||||||
|
///
|
||||||
|
/// Prerequisites:
|
||||||
|
/// `trueBranch`/`falseBranch` has not yet captured an mlir::Block*.
|
||||||
|
/// No `trueCaptures`/`falseCaptures` have captured any mlir::Value*.
|
||||||
|
/// All `trueOperands`/`trueOperands` have already captured an mlir::Value*
|
||||||
|
/// `trueCaptures`.size() == `trueOperands`.size()
|
||||||
|
/// `falseCaptures`.size() == `falseOperands`.size()
|
||||||
|
/// `trueCaptures` and `trueOperands` are pairwise of the same type
|
||||||
|
/// `falseCaptures` and `falseOperands` are pairwise of the same type.
|
||||||
|
ValueHandle COND_BR(ValueHandle cond, BlockHandle *trueBranch,
|
||||||
|
ArrayRef<ValueHandle *> trueCaptures,
|
||||||
|
ArrayRef<ValueHandle> trueOperands,
|
||||||
|
BlockHandle *falseBranch,
|
||||||
|
ArrayRef<ValueHandle *> falseCaptures,
|
||||||
|
ArrayRef<ValueHandle> falseOperands);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// TODO(ntv): Intrinsics below this line should be TableGen'd.
|
// TODO(ntv): Intrinsics below this line should be TableGen'd.
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -31,6 +31,36 @@ ValueHandle mlir::edsc::intrinsics::BR(BlockHandle bh,
|
||||||
SmallVector<Value *, 4> ops(operands.begin(), operands.end());
|
SmallVector<Value *, 4> ops(operands.begin(), operands.end());
|
||||||
return ValueHandle::create<BranchOp>(bh.getBlock(), ops);
|
return ValueHandle::create<BranchOp>(bh.getBlock(), ops);
|
||||||
}
|
}
|
||||||
|
static void enforceEmptyCapturesMatchOperands(ArrayRef<ValueHandle *> captures,
|
||||||
|
ArrayRef<ValueHandle> operands) {
|
||||||
|
assert(captures.size() == operands.size() &&
|
||||||
|
"Expected same number of captures as operands");
|
||||||
|
for (auto it : llvm::zip(captures, operands)) {
|
||||||
|
(void)it;
|
||||||
|
assert(!std::get<0>(it)->hasValue() &&
|
||||||
|
"Unexpected already captured ValueHandle");
|
||||||
|
assert(std::get<1>(it) && "Expected already captured ValueHandle");
|
||||||
|
assert(std::get<0>(it)->getType() == std::get<1>(it).getType() &&
|
||||||
|
"Expected the same type for capture and operand");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueHandle mlir::edsc::intrinsics::BR(BlockHandle *bh,
|
||||||
|
ArrayRef<ValueHandle *> captures,
|
||||||
|
ArrayRef<ValueHandle> operands) {
|
||||||
|
assert(!*bh && "Unexpected already captured BlockHandle");
|
||||||
|
enforceEmptyCapturesMatchOperands(captures, operands);
|
||||||
|
{ // Clone the scope explicitly to avoid modifying the insertion point in the
|
||||||
|
// current scope which result in surprising usage.
|
||||||
|
auto *currentB = ScopedContext::getBuilder();
|
||||||
|
FuncBuilder b(currentB->getInsertionBlock(), currentB->getInsertionPoint());
|
||||||
|
Location loc = ScopedContext::getLocation();
|
||||||
|
ScopedContext scope(b, loc);
|
||||||
|
BlockBuilder(bh, captures)({/* no body */});
|
||||||
|
} // Release before adding the branch to the eagerly created block.
|
||||||
|
SmallVector<Value *, 4> ops(operands.begin(), operands.end());
|
||||||
|
return ValueHandle::create<BranchOp>(bh->getBlock(), ops);
|
||||||
|
}
|
||||||
|
|
||||||
ValueHandle
|
ValueHandle
|
||||||
mlir::edsc::intrinsics::COND_BR(ValueHandle cond, BlockHandle trueBranch,
|
mlir::edsc::intrinsics::COND_BR(ValueHandle cond, BlockHandle trueBranch,
|
||||||
|
@ -43,9 +73,33 @@ mlir::edsc::intrinsics::COND_BR(ValueHandle cond, BlockHandle trueBranch,
|
||||||
falseBranch.getBlock(), falseOps);
|
falseBranch.getBlock(), falseOps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValueHandle mlir::edsc::intrinsics::COND_BR(
|
||||||
|
ValueHandle cond, BlockHandle *trueBranch,
|
||||||
|
ArrayRef<ValueHandle *> trueCaptures, ArrayRef<ValueHandle> trueOperands,
|
||||||
|
BlockHandle *falseBranch, ArrayRef<ValueHandle *> falseCaptures,
|
||||||
|
ArrayRef<ValueHandle> falseOperands) {
|
||||||
|
assert(!*trueBranch && "Unexpected already captured BlockHandle");
|
||||||
|
assert(!*falseBranch && "Unexpected already captured BlockHandle");
|
||||||
|
enforceEmptyCapturesMatchOperands(trueCaptures, trueOperands);
|
||||||
|
enforceEmptyCapturesMatchOperands(falseCaptures, falseOperands);
|
||||||
|
{ // Clone the scope explicitly.
|
||||||
|
auto *currentB = ScopedContext::getBuilder();
|
||||||
|
FuncBuilder b(currentB->getInsertionBlock(), currentB->getInsertionPoint());
|
||||||
|
Location loc = ScopedContext::getLocation();
|
||||||
|
ScopedContext scope(b, loc);
|
||||||
|
BlockBuilder(trueBranch, trueCaptures)({/* no body */});
|
||||||
|
BlockBuilder(falseBranch, falseCaptures)({/* no body */});
|
||||||
|
} // Release before adding the branch to the eagerly created block.
|
||||||
|
SmallVector<Value *, 4> trueOps(trueOperands.begin(), trueOperands.end());
|
||||||
|
SmallVector<Value *, 4> falseOps(falseOperands.begin(), falseOperands.end());
|
||||||
|
return ValueHandle::create<CondBranchOp>(
|
||||||
|
cond, trueBranch->getBlock(), trueOps, falseBranch->getBlock(), falseOps);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// TODO(ntv): Intrinsics below this line should be TableGen'd.
|
// TODO(ntv): Intrinsics below this line should be TableGen'd.
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ValueHandle mlir::edsc::intrinsics::RETURN(ArrayRef<ValueHandle> operands) {
|
ValueHandle mlir::edsc::intrinsics::RETURN(ArrayRef<ValueHandle> operands) {
|
||||||
SmallVector<Value *, 4> ops(operands.begin(), operands.end());
|
SmallVector<Value *, 4> ops(operands.begin(), operands.end());
|
||||||
return ValueHandle::create<ReturnOp>(ops);
|
return ValueHandle::create<ReturnOp>(ops);
|
||||||
|
|
|
@ -197,6 +197,50 @@ TEST_FUNC(builder_blocks) {
|
||||||
f->print(llvm::outs());
|
f->print(llvm::outs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_FUNC(builder_blocks_eager) {
|
||||||
|
using namespace edsc;
|
||||||
|
using namespace edsc::intrinsics;
|
||||||
|
using namespace edsc::op;
|
||||||
|
auto f = makeFunction("builder_blocks_eager");
|
||||||
|
|
||||||
|
ScopedContext scope(f.get());
|
||||||
|
ValueHandle c1(ValueHandle::create<ConstantIntOp>(42, 32)),
|
||||||
|
c2(ValueHandle::create<ConstantIntOp>(1234, 32));
|
||||||
|
ValueHandle arg1(c1.getType()), arg2(c1.getType()), arg3(c1.getType()),
|
||||||
|
arg4(c1.getType()), r(c1.getType());
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
BlockHandle b1, b2;
|
||||||
|
{ // Toplevel function scope.
|
||||||
|
BR(&b1, {&arg1, &arg2}, {c1, c2}); // eagerly builds a new block for b1
|
||||||
|
// We cannot construct b2 eagerly with a `BR(&b2, ...)` call from within b1
|
||||||
|
// because it would result in b2 being nested under b1 which is not what we
|
||||||
|
// want in this test.
|
||||||
|
BlockBuilder(&b2, {&arg3, &arg4})({
|
||||||
|
// Instead, construct explicitly
|
||||||
|
BR(b1, {arg3, arg4}),
|
||||||
|
});
|
||||||
|
/// And come back to append into b1 once b2 exists.
|
||||||
|
BlockBuilder(b1, Append())({
|
||||||
|
r = arg1 + arg2,
|
||||||
|
BR(b2, {arg1, r}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @builder_blocks_eager
|
||||||
|
// CHECK: %c42_i32 = constant 42 : i32
|
||||||
|
// CHECK-NEXT: %c1234_i32 = constant 1234 : i32
|
||||||
|
// CHECK-NEXT: br ^bb1(%c42_i32, %c1234_i32 : i32, i32)
|
||||||
|
// CHECK-NEXT: ^bb1(%0: i32, %1: i32): // 2 preds: ^bb0, ^bb2
|
||||||
|
// CHECK-NEXT: %2 = addi %0, %1 : i32
|
||||||
|
// CHECK-NEXT: br ^bb2(%0, %2 : i32, i32)
|
||||||
|
// CHECK-NEXT: ^bb2(%3: i32, %4: i32): // pred: ^bb1
|
||||||
|
// CHECK-NEXT: br ^bb1(%3, %4 : i32, i32)
|
||||||
|
// CHECK-NEXT: }
|
||||||
|
// clang-format on
|
||||||
|
f->print(llvm::outs());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_FUNC(builder_cond_branch) {
|
TEST_FUNC(builder_cond_branch) {
|
||||||
using namespace edsc;
|
using namespace edsc;
|
||||||
using namespace edsc::intrinsics;
|
using namespace edsc::intrinsics;
|
||||||
|
@ -211,7 +255,6 @@ TEST_FUNC(builder_cond_branch) {
|
||||||
ValueHandle arg1(c32.getType()), arg2(c64.getType()), arg3(c32.getType());
|
ValueHandle arg1(c32.getType()), arg2(c64.getType()), arg3(c32.getType());
|
||||||
|
|
||||||
BlockHandle b1, b2, functionBlock(&f->front());
|
BlockHandle b1, b2, functionBlock(&f->front());
|
||||||
;
|
|
||||||
BlockBuilder(&b1, {&arg1})({
|
BlockBuilder(&b1, {&arg1})({
|
||||||
RETURN({}),
|
RETURN({}),
|
||||||
});
|
});
|
||||||
|
@ -237,6 +280,43 @@ TEST_FUNC(builder_cond_branch) {
|
||||||
f->print(llvm::outs());
|
f->print(llvm::outs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_FUNC(builder_cond_branch_eager) {
|
||||||
|
using namespace edsc;
|
||||||
|
using namespace edsc::intrinsics;
|
||||||
|
using namespace edsc::op;
|
||||||
|
auto f = makeFunction("builder_cond_branch_eager", {},
|
||||||
|
{IntegerType::get(1, &globalContext())});
|
||||||
|
|
||||||
|
ScopedContext scope(f.get());
|
||||||
|
ValueHandle funcArg(f->getArgument(0));
|
||||||
|
ValueHandle c32(ValueHandle::create<ConstantIntOp>(32, 32)),
|
||||||
|
c64(ValueHandle::create<ConstantIntOp>(64, 64)),
|
||||||
|
c42(ValueHandle::create<ConstantIntOp>(42, 32));
|
||||||
|
ValueHandle arg1(c32.getType()), arg2(c64.getType()), arg3(c32.getType());
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
BlockHandle b1, b2;
|
||||||
|
COND_BR(funcArg, &b1, {&arg1}, {c32}, &b2, {&arg2, &arg3}, {c64, c42});
|
||||||
|
BlockBuilder(b1, Append())({
|
||||||
|
RETURN({}),
|
||||||
|
});
|
||||||
|
BlockBuilder(b2, Append())({
|
||||||
|
RETURN({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHECK-LABEL: @builder_cond_branch_eager
|
||||||
|
// CHECK: %c32_i32 = constant 32 : i32
|
||||||
|
// CHECK-NEXT: %c64_i64 = constant 64 : i64
|
||||||
|
// CHECK-NEXT: %c42_i32 = constant 42 : i32
|
||||||
|
// CHECK-NEXT: cond_br %arg0, ^bb1(%c32_i32 : i32), ^bb2(%c64_i64, %c42_i32 : i64, i32)
|
||||||
|
// CHECK-NEXT: ^bb1(%0: i32): // pred: ^bb0
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
// CHECK-NEXT: ^bb2(%1: i64, %2: i32): // pred: ^bb0
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
// clang-format on
|
||||||
|
f->print(llvm::outs());
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
RUN_TESTS();
|
RUN_TESTS();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue