CIRCT 20.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 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
131static 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
145static 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
176LogicalResult 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 Block * getBodyBlock(FModuleLike mod)
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.