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