[Handshake] Expose bufferRegion for other users (#3992)

This commit exposes a new `bufferRegion` function that is independent of
`handshake.func`. Additionally, it cleans up some of the code in the
buffer insertion.
This commit is contained in:
Christian Ulmann 2022-09-26 10:50:18 +02:00 committed by GitHub
parent 14def85c0a
commit 7eed8f45e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 160 deletions

View File

@ -59,6 +59,10 @@ void insertFork(Value result, bool isLazy, OpBuilder &rewriter);
// Adds a locking mechanism around the region.
LogicalResult lockRegion(Region &r, OpBuilder &rewriter);
// Applies the spcified buffering strategy on the region r.
LogicalResult bufferRegion(Region &r, OpBuilder &rewriter, StringRef strategy,
unsigned bufferSize);
/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/Handshake/HandshakePasses.h.inc"

View File

@ -46,7 +46,145 @@ struct HandshakeRemoveBuffersPass
signalPassFailure();
};
};
} // namespace
// Returns true if a block argument should have buffers added to its uses.
static bool shouldBufferArgument(BlockArgument arg) {
// At the moment, buffers only make sense on arguments which we know
// will lower down to a handshake bundle.
return arg.getType().isIntOrFloat() || arg.getType().isa<NoneType>();
}
static bool isUnbufferedChannel(Operation *definingOp, Operation *usingOp) {
return !isa_and_nonnull<BufferOp>(definingOp) && !isa<BufferOp>(usingOp);
}
static void insertBuffer(Location loc, Value operand, OpBuilder &builder,
unsigned numSlots, BufferTypeEnum bufferType) {
auto ip = builder.saveInsertionPoint();
builder.setInsertionPointAfterValue(operand);
auto bufferOp = builder.create<handshake::BufferOp>(
loc, operand.getType(), numSlots, operand, bufferType);
operand.replaceUsesWithIf(
bufferOp, function_ref<bool(OpOperand &)>([](OpOperand &operand) -> bool {
return !isa<handshake::BufferOp>(operand.getOwner());
}));
builder.restoreInsertionPoint(ip);
}
// Inserts buffers at all results of an operation
static void bufferResults(OpBuilder &builder, Operation *op, unsigned numSlots,
BufferTypeEnum bufferType) {
for (auto res : op->getResults()) {
Operation *user = *res.getUsers().begin();
if (isa<handshake::BufferOp>(user))
continue;
insertBuffer(op->getLoc(), res, builder, numSlots, bufferType);
}
};
// Add a buffer to any un-buffered channel.
static void bufferAllStrategy(Region &r, OpBuilder &builder, unsigned numSlots,
BufferTypeEnum bufferType = BufferTypeEnum::seq) {
for (auto &arg : r.getArguments()) {
if (!shouldBufferArgument(arg))
continue;
insertBuffer(arg.getLoc(), arg, builder, numSlots, bufferType);
}
for (auto &defOp : r.getOps()) {
for (auto res : defOp.getResults()) {
for (auto *useOp : res.getUsers()) {
if (!isUnbufferedChannel(&defOp, useOp))
continue;
insertBuffer(res.getLoc(), res, builder, numSlots, bufferType);
}
}
}
}
// Returns true if 'src' is within a cycle. 'breaksCycle' is a function which
// determines whether an operation breaks a cycle.
static bool inCycle(Operation *src,
llvm::function_ref<bool(Operation *)> breaksCycle) {
SetVector<Operation *> visited;
SmallVector<Operation *> stack = {src};
while (!stack.empty()) {
Operation *curr = stack.pop_back_val();
if (visited.contains(curr))
continue;
visited.insert(curr);
if (breaksCycle(curr))
continue;
for (auto *user : curr->getUsers()) {
// If visiting the source node, then we're in a cycle.
if (src == user)
return true;
stack.push_back(user);
}
}
return false;
}
// Perform a depth first search and insert buffers when cycles are detected.
static void
bufferCyclesStrategy(Region &r, OpBuilder &builder, unsigned numSlots,
BufferTypeEnum /*bufferType*/ = BufferTypeEnum::seq) {
// Cycles can only occur at merge-like operations so those are our buffering
// targets. Placing the buffer at the output of the merge-like op,
// as opposed to naivly placing buffers *whenever* cycles are detected
// ensures that we don't place a bunch of buffers on each input of the
// merge-like op.
auto isSeqBuffer = [](auto op) {
auto bufferOp = dyn_cast<handshake::BufferOp>(op);
return bufferOp && bufferOp.isSequential();
};
for (auto mergeOp : r.getOps<MergeLikeOpInterface>()) {
// We insert a sequential buffer whenever the op is determined to be
// within a cycle (to break combinational cycles). Else, place a FIFO
// buffer.
bool sequential = inCycle(mergeOp, isSeqBuffer);
bufferResults(builder, mergeOp, numSlots,
sequential ? BufferTypeEnum::seq : BufferTypeEnum::fifo);
}
}
// Combination of bufferCyclesStrategy and bufferAllStrategy, where we add a
// sequential buffer on graph cycles, and add FIFO buffers on all other
// connections.
static void bufferAllFIFOStrategy(Region &r, OpBuilder &builder,
unsigned numSlots) {
// First, buffer cycles with sequential buffers
bufferCyclesStrategy(r, builder, /*numSlots=*/numSlots,
/*bufferType=*/BufferTypeEnum::seq);
// Then, buffer remaining channels with transparent FIFO buffers
bufferAllStrategy(r, builder, numSlots,
/*bufferType=*/BufferTypeEnum::fifo);
}
LogicalResult circt::handshake::bufferRegion(Region &r, OpBuilder &builder,
StringRef strategy,
unsigned bufferSize) {
if (strategy == "cycles")
bufferCyclesStrategy(r, builder, bufferSize);
else if (strategy == "all")
bufferAllStrategy(r, builder, bufferSize);
else if (strategy == "allFIFO")
bufferAllFIFOStrategy(r, builder, bufferSize);
else
return r.getParentOp()->emitOpError()
<< "Unknown buffer strategy: " << strategy;
return success();
}
namespace {
struct HandshakeInsertBuffersPass
: public HandshakeInsertBuffersBase<HandshakeInsertBuffersPass> {
HandshakeInsertBuffersPass(const std::string &strategy, unsigned bufferSize) {
@ -54,169 +192,15 @@ struct HandshakeInsertBuffersPass
this->bufferSize = bufferSize;
}
// Returns true if a block argument should have buffers added to its uses.
static bool shouldBufferArgument(BlockArgument arg) {
// At the moment, buffers only make sense on arguments which we know
// will lower down to a handshake bundle.
return arg.getType().isIntOrFloat() || arg.getType().isa<NoneType>();
}
static bool isUnbufferedChannel(Operation *definingOp, Operation *usingOp) {
return !isa_and_nonnull<BufferOp>(definingOp) && !isa<BufferOp>(usingOp);
}
void insertBuffer(Location loc, Value operand, OpBuilder &builder,
unsigned numSlots, BufferTypeEnum bufferType) {
auto ip = builder.saveInsertionPoint();
builder.setInsertionPointAfterValue(operand);
auto bufferOp = builder.create<handshake::BufferOp>(
loc, operand.getType(), numSlots, operand, bufferType);
operand.replaceUsesWithIf(
bufferOp,
function_ref<bool(OpOperand &)>([](OpOperand &operand) -> bool {
return !isa<handshake::BufferOp>(operand.getOwner());
}));
builder.restoreInsertionPoint(ip);
}
// Inserts a buffer at a specific operand use.
void bufferOperand(OpOperand &use, OpBuilder &builder, size_t numSlots,
BufferTypeEnum bufferType) {
auto *usingOp = use.getOwner();
Value usingValue = use.get();
builder.setInsertionPoint(usingOp);
auto buffer = builder.create<handshake::BufferOp>(
usingValue.getLoc(), usingValue.getType(),
/*slots=*/numSlots, usingValue,
/*bufferType=*/bufferType);
usingOp->setOperand(use.getOperandNumber(), buffer);
}
// Inserts buffers at all operands of an operation.
void bufferOperands(Operation *op, OpBuilder builder, size_t numSlots,
BufferTypeEnum bufferType) {
for (auto &use : op->getOpOperands()) {
auto *srcOp = use.get().getDefiningOp();
if (isa_and_nonnull<handshake::BufferOp>(srcOp))
continue;
bufferOperand(use, builder, numSlots, bufferType);
}
}
// Inserts buffers at all results of an operation
void bufferResults(OpBuilder &builder, Operation *op, unsigned numSlots,
BufferTypeEnum bufferType) {
for (auto res : op->getResults()) {
Operation *user = *res.getUsers().begin();
if (isa<handshake::BufferOp>(user))
continue;
insertBuffer(op->getLoc(), res, builder, numSlots, bufferType);
}
};
// Add a buffer to any un-buffered channel.
void bufferAllStrategy(handshake::FuncOp f, OpBuilder &builder,
unsigned numSlots,
BufferTypeEnum bufferType = BufferTypeEnum::seq) {
for (auto &arg : f.getArguments()) {
if (!shouldBufferArgument(arg))
continue;
insertBuffer(arg.getLoc(), arg, builder, numSlots, bufferType);
}
for (auto &defOp : f.getOps()) {
for (auto res : defOp.getResults()) {
for (auto *useOp : res.getUsers()) {
if (!isUnbufferedChannel(&defOp, useOp))
continue;
insertBuffer(res.getLoc(), res, builder, numSlots, bufferType);
}
}
}
}
// Combination of bufferCyclesStrategy and bufferAllStrategy, where we add a
// sequential buffer on graph cycles, and add FIFO buffers on all other
// connections.
void bufferAllFIFOStrategy(handshake::FuncOp f, OpBuilder &builder) {
// First, buffer cycles with sequential buffers
bufferCyclesStrategy(f, builder, /*numSlots=*/2,
/*bufferType=*/BufferTypeEnum::seq);
// Then, buffer remaining channels with transparent FIFO buffers
bufferAllStrategy(f, builder, bufferSize,
/*bufferType=*/BufferTypeEnum::fifo);
}
// Perform a depth first search and insert buffers when cycles are detected.
void
bufferCyclesStrategy(handshake::FuncOp f, OpBuilder &builder,
unsigned numSlots,
BufferTypeEnum /*bufferType*/ = BufferTypeEnum::seq) {
// Cycles can only occur at merge-like operations so those are our buffering
// targets. Placing the buffer at the output of the merge-like op,
// as opposed to naivly placing buffers *whenever* cycles are detected
// ensures that we don't place a bunch of buffers on each input of the
// merge-like op.
auto isSeqBuffer = [](auto op) {
auto bufferOp = dyn_cast<handshake::BufferOp>(op);
return bufferOp && bufferOp.isSequential();
};
for (auto mergeOp : f.getOps<MergeLikeOpInterface>()) {
// We insert a sequential buffer whenever the op is determined to be
// within a cycle (to break combinational cycles). Else, place a FIFO
// buffer.
bool sequential = inCycle(mergeOp, isSeqBuffer);
bufferResults(builder, mergeOp, numSlots,
sequential ? BufferTypeEnum::seq : BufferTypeEnum::fifo);
}
}
// Returns true if 'src' is within a cycle. 'breaksCycle' is a function which
// determines whether an operation breaks a cycle.
bool inCycle(Operation *src,
llvm::function_ref<bool(Operation *)> breaksCycle) {
SetVector<Operation *> visited;
SmallVector<Operation *> stack = {src};
while (!stack.empty()) {
Operation *curr = stack.pop_back_val();
if (visited.contains(curr))
continue;
visited.insert(curr);
if (breaksCycle(curr))
continue;
for (auto *user : curr->getUsers()) {
// If visiting the source node, then we're in a cycle.
if (src == user)
return true;
stack.push_back(user);
}
}
return false;
}
void runOnOperation() override {
auto f = getOperation();
auto builder = OpBuilder(f.getContext());
if (strategy == "cycles")
bufferCyclesStrategy(f, builder, bufferSize);
else if (strategy == "all")
bufferAllStrategy(f, builder, bufferSize);
else if (strategy == "allFIFO")
bufferAllFIFOStrategy(f, builder);
else {
getOperation().emitOpError() << "Unknown buffer strategy: " << strategy;
signalPassFailure();
if (f.isExternal())
return;
}
OpBuilder builder(f.getContext());
if (failed(bufferRegion(f.getBody(), builder, strategy, bufferSize)))
signalPassFailure();
}
};

View File

@ -24,3 +24,9 @@ handshake.func @foo(%arg0 : i32, %ctrl : none) -> (i32, none) {
%2:2 = fork [2] %1 : i32
return %2#1, %ctrl : i32, none
}
// -----
// CHECK-LABEL: handshake.func @external(
// CHECK-SAME: i32, none, ...) -> none
handshake.func @external(%arg0: i32, %ctrl: none, ...) -> none