CIRCT  19.0.0git
ArcFolds.cpp
Go to the documentation of this file.
1 //===- ArcFolds.cpp -------------------------------------------------------===//
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 
10 #include "circt/Dialect/HW/HWOps.h"
11 #include "mlir/IR/PatternMatch.h"
12 #include "mlir/Support/LogicalResult.h"
13 
14 using namespace circt;
15 using namespace arc;
16 using namespace mlir;
17 
18 //===----------------------------------------------------------------------===//
19 // Helpers
20 //===----------------------------------------------------------------------===//
21 
22 static bool isAlways(Attribute attr, bool expected) {
23  if (auto enable = dyn_cast_or_null<IntegerAttr>(attr))
24  return enable.getValue().getBoolValue() == expected;
25  return false;
26 }
27 
28 static bool isAlways(Value value, bool expected) {
29  if (!value)
30  return false;
31 
32  if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
33  return constOp.getValue().getBoolValue() == expected;
34 
35  return false;
36 }
37 
38 //===----------------------------------------------------------------------===//
39 // StateOp
40 //===----------------------------------------------------------------------===//
41 
42 LogicalResult StateOp::fold(FoldAdaptor adaptor,
43  SmallVectorImpl<OpFoldResult> &results) {
44  if ((isAlways(adaptor.getEnable(), false) ||
45  isAlways(adaptor.getReset(), true)) &&
46  !getOperation()->hasAttr("name") && !getOperation()->hasAttr("names")) {
47  // We can fold to zero here because the states are zero-initialized and
48  // don't ever change.
49  for (auto resTy : getResultTypes())
50  results.push_back(IntegerAttr::get(resTy, 0));
51  return success();
52  }
53 
54  // Remove operand when input is default value.
55  if (isAlways(adaptor.getReset(), false))
56  return getResetMutable().clear(), success();
57 
58  // Remove operand when input is default value.
59  if (isAlways(adaptor.getEnable(), true))
60  return getEnableMutable().clear(), success();
61 
62  return failure();
63 }
64 
65 LogicalResult StateOp::canonicalize(StateOp op, PatternRewriter &rewriter) {
66  // When there are no names attached, the state is not externaly observable.
67  // When there are also no internal users, we can remove it.
68  if (op->use_empty() && !op->hasAttr("name") && !op->hasAttr("names")) {
69  rewriter.eraseOp(op);
70  return success();
71  }
72 
73  return failure();
74 }
75 
76 //===----------------------------------------------------------------------===//
77 // MemoryWriteOp
78 //===----------------------------------------------------------------------===//
79 
80 LogicalResult MemoryWriteOp::fold(FoldAdaptor adaptor,
81  SmallVectorImpl<OpFoldResult> &results) {
82  if (isAlways(adaptor.getEnable(), true))
83  return getEnableMutable().clear(), success();
84  return failure();
85 }
86 
87 LogicalResult MemoryWriteOp::canonicalize(MemoryWriteOp op,
88  PatternRewriter &rewriter) {
89  if (isAlways(op.getEnable(), false))
90  return rewriter.eraseOp(op), success();
91  return failure();
92 }
93 
94 //===----------------------------------------------------------------------===//
95 // StorageGetOp
96 //===----------------------------------------------------------------------===//
97 
98 LogicalResult StorageGetOp::canonicalize(StorageGetOp op,
99  PatternRewriter &rewriter) {
100  if (auto pred = op.getStorage().getDefiningOp<StorageGetOp>()) {
101  op.getStorageMutable().assign(pred.getStorage());
102  op.setOffset(op.getOffset() + pred.getOffset());
103  return success();
104  }
105  return failure();
106 }
107 
108 //===----------------------------------------------------------------------===//
109 // ClockDomainOp
110 //===----------------------------------------------------------------------===//
111 
112 static bool removeUnusedClockDomainInputs(ClockDomainOp op,
113  PatternRewriter &rewriter) {
114  BitVector toDelete(op.getBodyBlock().getNumArguments());
115  for (auto arg : llvm::reverse(op.getBodyBlock().getArguments())) {
116  if (arg.use_empty()) {
117  auto i = arg.getArgNumber();
118  toDelete.set(i);
119  op.getInputsMutable().erase(i);
120  }
121  }
122  op.getBodyBlock().eraseArguments(toDelete);
123  return toDelete.any();
124 }
125 
126 static bool removeUnusedClockDomainOutputs(ClockDomainOp op,
127  PatternRewriter &rewriter) {
128  SmallVector<Type> resultTypes;
129  for (auto res : llvm::reverse(op->getResults())) {
130  if (res.use_empty())
131  op.getBodyBlock().getTerminator()->eraseOperand(res.getResultNumber());
132  else
133  resultTypes.push_back(res.getType());
134  }
135 
136  // Nothing is changed.
137  if (resultTypes.size() == op->getNumResults())
138  return false;
139 
140  rewriter.setInsertionPoint(op);
141 
142  auto newDomain = rewriter.create<ClockDomainOp>(
143  op.getLoc(), resultTypes, op.getInputs(), op.getClock());
144  rewriter.inlineRegionBefore(op.getBody(), newDomain.getBody(),
145  newDomain->getRegion(0).begin());
146 
147  unsigned currIdx = 0;
148  for (auto result : op.getOutputs()) {
149  if (!result.use_empty())
150  rewriter.replaceAllUsesWith(result, newDomain->getResult(currIdx++));
151  }
152 
153  rewriter.eraseOp(op);
154  return true;
155 }
156 
157 LogicalResult ClockDomainOp::canonicalize(ClockDomainOp op,
158  PatternRewriter &rewriter) {
159  rewriter.setInsertionPointToStart(&op.getBodyBlock());
160 
161  // Canonicalize inputs
162  DenseMap<Value, unsigned> seenArgs;
163  for (auto arg :
164  llvm::make_early_inc_range(op.getBodyBlock().getArguments())) {
165  auto i = arg.getArgNumber();
166  auto inputVal = op.getInputs()[i];
167 
168  if (arg.use_empty())
169  continue;
170 
171  // Remove duplicate inputs
172  if (seenArgs.count(inputVal)) {
173  rewriter.replaceAllUsesWith(
174  arg, op.getBodyBlock().getArgument(seenArgs[inputVal]));
175  continue;
176  }
177 
178  // Pull in memories that are only used in this clock domain and clone
179  // constants into the clock domain.
180  if (auto *inputOp = inputVal.getDefiningOp()) {
181  bool isConstant = inputOp->hasTrait<OpTrait::ConstantLike>();
182  bool hasOneUse = inputVal.hasOneUse();
183  if (isConstant || (isa<MemoryOp>(inputOp) && hasOneUse)) {
184  auto resultNumber = cast<OpResult>(inputVal).getResultNumber();
185  auto *clone = rewriter.clone(*inputOp);
186  rewriter.replaceAllUsesWith(arg, clone->getResult(resultNumber));
187  if (hasOneUse && inputOp->getNumResults() == 1) {
188  inputVal.dropAllUses();
189  rewriter.eraseOp(inputOp);
190  }
191  continue;
192  }
193  }
194 
195  seenArgs[op.getInputs()[i]] = i;
196  }
197 
198  auto didCanonicalizeInput = removeUnusedClockDomainInputs(op, rewriter);
199 
200  // Canonicalize outputs
201  for (auto [result, terminatorOperand] : llvm::zip(
202  op.getOutputs(), op.getBodyBlock().getTerminator()->getOperands())) {
203  // Replace results which are just passed-through inputs with the input
204  // directly. This makes this result unused and is thus removed later on.
205  if (isa<BlockArgument>(terminatorOperand))
206  rewriter.replaceAllUsesWith(
207  result, op.getInputs()[cast<BlockArgument>(terminatorOperand)
208  .getArgNumber()]);
209 
210  // Outputs that are just constant operations can be replaced by a clone of
211  // the constant outside of the clock domain. This makes the result unused
212  // and is thus removed later on.
213  // TODO: we could also push out all operations that are not clocked/don't
214  // have side-effects. If there are long chains of such operations this can
215  // lead to long canonicalizer runtimes though, so we need to be careful here
216  // and maybe do it as a separate pass (or make sure that such chains are
217  // never pulled into the clock domain in the first place).
218  if (auto *defOp = terminatorOperand.getDefiningOp();
219  defOp && defOp->hasTrait<OpTrait::ConstantLike>() &&
220  !result.use_empty()) {
221  rewriter.setInsertionPointAfter(op);
222  unsigned resultIdx = cast<OpResult>(terminatorOperand).getResultNumber();
223  auto *clone = rewriter.clone(*defOp);
224  if (defOp->hasOneUse()) {
225  defOp->dropAllUses();
226  rewriter.eraseOp(defOp);
227  }
228  rewriter.replaceAllUsesWith(result, clone->getResult(resultIdx));
229  }
230  }
231 
232  auto didCanoncalizeOutput = removeUnusedClockDomainOutputs(op, rewriter);
233 
234  return success(didCanonicalizeInput || didCanoncalizeOutput);
235 }
static bool isAlways(Attribute attr, bool expected)
Definition: ArcFolds.cpp:22
static bool removeUnusedClockDomainInputs(ClockDomainOp op, PatternRewriter &rewriter)
Definition: ArcFolds.cpp:112
static bool removeUnusedClockDomainOutputs(ClockDomainOp op, PatternRewriter &rewriter)
Definition: ArcFolds.cpp:126
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
bool isConstant(Operation *op)
Return true if the specified operation has a constant value.
Definition: FIRRTLOps.cpp:4494
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21