CIRCT 22.0.0git
Loading...
Searching...
No Matches
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
11#include "mlir/IR/PatternMatch.h"
12#include "mlir/Support/LogicalResult.h"
13
14using namespace circt;
15using namespace arc;
16using namespace mlir;
17
18//===----------------------------------------------------------------------===//
19// Helpers
20//===----------------------------------------------------------------------===//
21
22static 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
28static 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
42LogicalResult 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
84LogicalResult 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
99LogicalResult MemoryWriteOp::fold(FoldAdaptor adaptor,
100 SmallVectorImpl<OpFoldResult> &results) {
101 if (isAlways(adaptor.getEnable(), true))
102 return getEnableMutable().clear(), success();
103 return failure();
104}
105
106LogicalResult 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
117LogicalResult StorageGetOp::canonicalize(StorageGetOp op,
118 PatternRewriter &rewriter) {
119 if (auto pred = op.getStorage().getDefiningOp<StorageGetOp>()) {
120 rewriter.modifyOpInPlace(op, [&] {
121 op.getStorageMutable().assign(pred.getStorage());
122 op.setOffset(op.getOffset() + pred.getOffset());
123 });
124 return success();
125 }
126 return failure();
127}
128
129//===----------------------------------------------------------------------===//
130// ClockDomainOp
131//===----------------------------------------------------------------------===//
132
133static bool removeUnusedClockDomainInputs(ClockDomainOp op,
134 PatternRewriter &rewriter) {
135 BitVector toDelete(op.getBodyBlock().getNumArguments());
136 for (auto arg : llvm::reverse(op.getBodyBlock().getArguments())) {
137 if (arg.use_empty()) {
138 auto i = arg.getArgNumber();
139 toDelete.set(i);
140 rewriter.modifyOpInPlace(op, [&] { op.getInputsMutable().erase(i); });
141 }
142 }
143 if (toDelete.any()) {
144 rewriter.modifyOpInPlace(
145 op, [&] { op.getBodyBlock().eraseArguments(toDelete); });
146 return true;
147 }
148 return false;
149}
150
151static bool removeUnusedClockDomainOutputs(ClockDomainOp op,
152 PatternRewriter &rewriter) {
153 SmallVector<Type> resultTypes;
154 for (auto res : llvm::reverse(op->getResults())) {
155 if (res.use_empty()) {
156 auto *terminator = op.getBodyBlock().getTerminator();
157 rewriter.modifyOpInPlace(
158 terminator, [&] { terminator->eraseOperand(res.getResultNumber()); });
159 } else {
160 resultTypes.push_back(res.getType());
161 }
162 }
163
164 // Nothing is changed.
165 if (resultTypes.size() == op->getNumResults())
166 return false;
167
168 rewriter.setInsertionPoint(op);
169
170 auto newDomain = ClockDomainOp::create(rewriter, op.getLoc(), resultTypes,
171 op.getInputs(), op.getClock());
172 rewriter.inlineRegionBefore(op.getBody(), newDomain.getBody(),
173 newDomain->getRegion(0).begin());
174
175 unsigned currIdx = 0;
176 for (auto result : op.getOutputs()) {
177 if (!result.use_empty())
178 rewriter.replaceAllUsesWith(result, newDomain->getResult(currIdx++));
179 }
180
181 rewriter.eraseOp(op);
182 return true;
183}
184
185LogicalResult ClockDomainOp::canonicalize(ClockDomainOp op,
186 PatternRewriter &rewriter) {
187 rewriter.setInsertionPointToStart(&op.getBodyBlock());
188
189 // Canonicalize inputs
190 DenseMap<Value, unsigned> seenArgs;
191 for (auto arg :
192 llvm::make_early_inc_range(op.getBodyBlock().getArguments())) {
193 auto i = arg.getArgNumber();
194 auto inputVal = op.getInputs()[i];
195
196 if (arg.use_empty())
197 continue;
198
199 // Remove duplicate inputs
200 if (seenArgs.count(inputVal)) {
201 rewriter.replaceAllUsesWith(
202 arg, op.getBodyBlock().getArgument(seenArgs[inputVal]));
203 continue;
204 }
205
206 // Pull in memories that are only used in this clock domain and clone
207 // constants into the clock domain.
208 if (auto *inputOp = inputVal.getDefiningOp()) {
209 bool isConstant = inputOp->hasTrait<OpTrait::ConstantLike>();
210 bool hasOneUse = inputVal.hasOneUse();
211 if (isConstant || (isa<MemoryOp>(inputOp) && hasOneUse)) {
212 auto resultNumber = cast<OpResult>(inputVal).getResultNumber();
213 auto *clone = rewriter.clone(*inputOp);
214 rewriter.replaceAllUsesWith(arg, clone->getResult(resultNumber));
215 if (hasOneUse && inputOp->getNumResults() == 1) {
216 inputVal.dropAllUses();
217 rewriter.eraseOp(inputOp);
218 }
219 continue;
220 }
221 }
222
223 seenArgs[op.getInputs()[i]] = i;
224 }
225
226 auto didCanonicalizeInput = removeUnusedClockDomainInputs(op, rewriter);
227
228 // Canonicalize outputs
229 for (auto [result, terminatorOperand] : llvm::zip(
230 op.getOutputs(), op.getBodyBlock().getTerminator()->getOperands())) {
231 // Replace results which are just passed-through inputs with the input
232 // directly. This makes this result unused and is thus removed later on.
233 if (isa<BlockArgument>(terminatorOperand))
234 rewriter.replaceAllUsesWith(
235 result, op.getInputs()[cast<BlockArgument>(terminatorOperand)
236 .getArgNumber()]);
237
238 // Outputs that are just constant operations can be replaced by a clone of
239 // the constant outside of the clock domain. This makes the result unused
240 // and is thus removed later on.
241 // TODO: we could also push out all operations that are not clocked/don't
242 // have side-effects. If there are long chains of such operations this can
243 // lead to long canonicalizer runtimes though, so we need to be careful here
244 // and maybe do it as a separate pass (or make sure that such chains are
245 // never pulled into the clock domain in the first place).
246 if (auto *defOp = terminatorOperand.getDefiningOp();
247 defOp && defOp->hasTrait<OpTrait::ConstantLike>() &&
248 !result.use_empty()) {
249 rewriter.setInsertionPointAfter(op);
250 unsigned resultIdx = cast<OpResult>(terminatorOperand).getResultNumber();
251 auto *clone = rewriter.clone(*defOp);
252 if (defOp->hasOneUse()) {
253 defOp->dropAllUses();
254 rewriter.eraseOp(defOp);
255 }
256 rewriter.replaceAllUsesWith(result, clone->getResult(resultIdx));
257 }
258 }
259
260 auto didCanoncalizeOutput = removeUnusedClockDomainOutputs(op, rewriter);
261
262 return success(didCanonicalizeInput || didCanoncalizeOutput);
263}
static bool isAlways(Attribute attr, bool expected)
Definition ArcFolds.cpp:22
static bool removeUnusedClockDomainInputs(ClockDomainOp op, PatternRewriter &rewriter)
Definition ArcFolds.cpp:133
static bool removeUnusedClockDomainOutputs(ClockDomainOp op, PatternRewriter &rewriter)
Definition ArcFolds.cpp:151
static Block * getBodyBlock(FModuleLike mod)
Definition arc.py:1
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.