mirror of https://github.com/llvm/circt.git
[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:
parent
14def85c0a
commit
7eed8f45e6
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue