CIRCT  19.0.0git
Buffers.cpp
Go to the documentation of this file.
1 //===- Buffers.cpp - buffer materialization passes --------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Contains the definitions of buffer materialization passes.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
16 #include "mlir/IR/PatternMatch.h"
17 #include "mlir/Rewrite/FrozenRewritePatternSet.h"
18 #include "mlir/Transforms/DialectConversion.h"
19 
20 using namespace circt;
21 using namespace handshake;
22 using namespace mlir;
23 
24 namespace {
25 
26 struct RemoveHandshakeBuffers : public OpRewritePattern<handshake::BufferOp> {
27  using OpRewritePattern::OpRewritePattern;
28 
29  LogicalResult matchAndRewrite(handshake::BufferOp bufferOp,
30  PatternRewriter &rewriter) const override {
31  rewriter.replaceOp(bufferOp, bufferOp.getOperand());
32  return success();
33  }
34 };
35 
36 struct HandshakeRemoveBuffersPass
37  : public HandshakeRemoveBuffersBase<HandshakeRemoveBuffersPass> {
38  void runOnOperation() override {
39  handshake::FuncOp op = getOperation();
40  ConversionTarget target(getContext());
41  target.addIllegalOp<handshake::BufferOp>();
42  RewritePatternSet patterns(&getContext());
43  patterns.insert<RemoveHandshakeBuffers>(&getContext());
44 
45  if (failed(applyPartialConversion(op, target, std::move(patterns))))
46  signalPassFailure();
47  };
48 };
49 } // namespace
50 // Returns true if a block argument should have buffers added to its uses.
51 static bool shouldBufferArgument(BlockArgument arg) {
52  // At the moment, buffers only make sense on arguments which we know
53  // will lower down to a handshake bundle.
54  return arg.getType().isIntOrFloat() || arg.getType().isa<NoneType>();
55 }
56 
57 static bool isUnbufferedChannel(Operation *definingOp, Operation *usingOp) {
58  return !isa_and_nonnull<BufferOp>(definingOp) && !isa<BufferOp>(usingOp);
59 }
60 
61 static void insertBuffer(Location loc, Value operand, OpBuilder &builder,
62  unsigned numSlots, BufferTypeEnum bufferType) {
63  auto ip = builder.saveInsertionPoint();
64  builder.setInsertionPointAfterValue(operand);
65  auto bufferOp =
66  builder.create<handshake::BufferOp>(loc, operand, numSlots, bufferType);
67  operand.replaceUsesWithIf(
68  bufferOp, function_ref<bool(OpOperand &)>([](OpOperand &operand) -> bool {
69  return !isa<handshake::BufferOp>(operand.getOwner());
70  }));
71  builder.restoreInsertionPoint(ip);
72 }
73 
74 // Inserts buffers at all results of an operation
75 static void bufferResults(OpBuilder &builder, Operation *op, unsigned numSlots,
76  BufferTypeEnum bufferType) {
77  for (auto res : op->getResults()) {
78  Operation *user = *res.getUsers().begin();
79  if (isa<handshake::BufferOp>(user))
80  continue;
81  insertBuffer(op->getLoc(), res, builder, numSlots, bufferType);
82  }
83 }
84 
85 // Add a buffer to any un-buffered channel.
86 static void bufferAllStrategy(Region &r, OpBuilder &builder, unsigned numSlots,
87  BufferTypeEnum bufferType = BufferTypeEnum::seq) {
88 
89  for (auto &arg : r.getArguments()) {
90  if (!shouldBufferArgument(arg))
91  continue;
92  insertBuffer(arg.getLoc(), arg, builder, numSlots, bufferType);
93  }
94 
95  for (auto &defOp : r.getOps()) {
96  for (auto res : defOp.getResults()) {
97  for (auto *useOp : res.getUsers()) {
98  if (!isUnbufferedChannel(&defOp, useOp))
99  continue;
100  insertBuffer(res.getLoc(), res, builder, numSlots, bufferType);
101  }
102  }
103  }
104 }
105 
106 // Returns true if 'src' is within a cycle. 'breaksCycle' is a function which
107 // determines whether an operation breaks a cycle.
108 static bool inCycle(Operation *src,
109  llvm::function_ref<bool(Operation *)> breaksCycle) {
110  SetVector<Operation *> visited;
111  SmallVector<Operation *> stack = {src};
112 
113  while (!stack.empty()) {
114  Operation *curr = stack.pop_back_val();
115 
116  if (visited.contains(curr))
117  continue;
118  visited.insert(curr);
119 
120  if (breaksCycle(curr))
121  continue;
122 
123  for (auto *user : curr->getUsers()) {
124  // If visiting the source node, then we're in a cycle.
125  if (src == user)
126  return true;
127 
128  stack.push_back(user);
129  }
130  }
131  return false;
132 }
133 
134 // Perform a depth first search and insert buffers when cycles are detected.
135 static void
136 bufferCyclesStrategy(Region &r, OpBuilder &builder, unsigned numSlots,
137  BufferTypeEnum /*bufferType*/ = BufferTypeEnum::seq) {
138  // Cycles can only occur at merge-like operations so those are our buffering
139  // targets. Placing the buffer at the output of the merge-like op,
140  // as opposed to naivly placing buffers *whenever* cycles are detected
141  // ensures that we don't place a bunch of buffers on each input of the
142  // merge-like op.
143  auto isSeqBuffer = [](auto op) {
144  auto bufferOp = dyn_cast<handshake::BufferOp>(op);
145  return bufferOp && bufferOp.isSequential();
146  };
147 
148  for (auto mergeOp : r.getOps<MergeLikeOpInterface>()) {
149  // We insert a sequential buffer whenever the op is determined to be
150  // within a cycle (to break combinational cycles). Else, place a FIFO
151  // buffer.
152  bool sequential = inCycle(mergeOp, isSeqBuffer);
153  bufferResults(builder, mergeOp, numSlots,
154  sequential ? BufferTypeEnum::seq : BufferTypeEnum::fifo);
155  }
156 }
157 
158 // Combination of bufferCyclesStrategy and bufferAllStrategy, where we add a
159 // sequential buffer on graph cycles, and add FIFO buffers on all other
160 // connections.
161 static void bufferAllFIFOStrategy(Region &r, OpBuilder &builder,
162  unsigned numSlots) {
163  // First, buffer cycles with sequential buffers
164  bufferCyclesStrategy(r, builder, /*numSlots=*/numSlots,
165  /*bufferType=*/BufferTypeEnum::seq);
166  // Then, buffer remaining channels with transparent FIFO buffers
167  bufferAllStrategy(r, builder, numSlots,
168  /*bufferType=*/BufferTypeEnum::fifo);
169 }
170 
171 LogicalResult circt::handshake::bufferRegion(Region &r, OpBuilder &builder,
172  StringRef strategy,
173  unsigned bufferSize) {
174  if (strategy == "cycles")
175  bufferCyclesStrategy(r, builder, bufferSize);
176  else if (strategy == "all")
177  bufferAllStrategy(r, builder, bufferSize);
178  else if (strategy == "allFIFO")
179  bufferAllFIFOStrategy(r, builder, bufferSize);
180  else
181  return r.getParentOp()->emitOpError()
182  << "Unknown buffer strategy: " << strategy;
183 
184  return success();
185 }
186 
187 namespace {
188 struct HandshakeInsertBuffersPass
189  : public HandshakeInsertBuffersBase<HandshakeInsertBuffersPass> {
190  HandshakeInsertBuffersPass(const std::string &strategy, unsigned bufferSize) {
191  this->strategy = strategy;
192  this->bufferSize = bufferSize;
193  }
194 
195  void runOnOperation() override {
196  auto f = getOperation();
197  if (f.isExternal())
198  return;
199 
200  OpBuilder builder(f.getContext());
201 
202  if (failed(bufferRegion(f.getBody(), builder, strategy, bufferSize)))
203  signalPassFailure();
204  }
205 };
206 
207 } // namespace
208 
209 std::unique_ptr<mlir::Pass>
211  return std::make_unique<HandshakeRemoveBuffersPass>();
212 }
213 
214 std::unique_ptr<mlir::OperationPass<handshake::FuncOp>>
216  unsigned bufferSize) {
217  return std::make_unique<HandshakeInsertBuffersPass>(strategy, bufferSize);
218 }
static void bufferResults(OpBuilder &builder, Operation *op, unsigned numSlots, BufferTypeEnum bufferType)
Definition: Buffers.cpp:75
static void bufferAllFIFOStrategy(Region &r, OpBuilder &builder, unsigned numSlots)
Definition: Buffers.cpp:161
static bool isUnbufferedChannel(Operation *definingOp, Operation *usingOp)
Definition: Buffers.cpp:57
static void insertBuffer(Location loc, Value operand, OpBuilder &builder, unsigned numSlots, BufferTypeEnum bufferType)
Definition: Buffers.cpp:61
static void bufferAllStrategy(Region &r, OpBuilder &builder, unsigned numSlots, BufferTypeEnum bufferType=BufferTypeEnum::seq)
Definition: Buffers.cpp:86
static void bufferCyclesStrategy(Region &r, OpBuilder &builder, unsigned numSlots, BufferTypeEnum=BufferTypeEnum::seq)
Definition: Buffers.cpp:136
static bool shouldBufferArgument(BlockArgument arg)
Definition: Buffers.cpp:51
static bool inCycle(Operation *src, llvm::function_ref< bool(Operation *)> breaksCycle)
Definition: Buffers.cpp:108
Builder builder
std::unique_ptr< mlir::OperationPass< handshake::FuncOp > > createHandshakeInsertBuffersPass(const std::string &strategy="all", unsigned bufferSize=2)
Definition: Buffers.cpp:215
std::unique_ptr< mlir::Pass > createHandshakeRemoveBuffersPass()
Definition: Buffers.cpp:210
LogicalResult bufferRegion(Region &r, OpBuilder &rewriter, StringRef strategy, unsigned bufferSize)
Definition: Buffers.cpp:171
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21