CIRCT 23.0.0git
Loading...
Searching...
No Matches
DCOps.cpp
Go to the documentation of this file.
1//===- DCOps.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 "mlir/IR/Builders.h"
11#include "mlir/IR/Diagnostics.h"
12#include "mlir/IR/OpImplementation.h"
13#include "mlir/IR/PatternMatch.h"
14#include "mlir/Interfaces/FunctionImplementation.h"
15#include "mlir/Interfaces/SideEffectInterfaces.h"
16
17using namespace circt;
18using namespace dc;
19using namespace mlir;
20
22 auto vt = dyn_cast<ValueType>(t);
23 if (!vt)
24 return false;
25 auto innerWidth = vt.getInnerType().getIntOrFloatBitWidth();
26 return innerWidth == 1;
27}
28
29namespace circt {
30namespace dc {
31
32// =============================================================================
33// JoinOp
34// =============================================================================
35
36OpFoldResult JoinOp::fold(FoldAdaptor adaptor) {
37 // Fold simple joins (joins with 1 input).
38 if (auto tokens = getTokens(); tokens.size() == 1)
39 return tokens.front();
40
41 return {};
42}
43
44struct JoinOnBranchPattern : public OpRewritePattern<JoinOp> {
46 LogicalResult matchAndRewrite(JoinOp op,
47 PatternRewriter &rewriter) const override {
48
49 struct BranchOperandInfo {
50 // Unique operands from the branch op, in case we have the same operand
51 // from the branch op multiple times.
52 SetVector<Value> uniqueOperands;
53 // Indices which the operands are at in the join op.
54 BitVector indices;
55 };
56
57 DenseMap<BranchOp, BranchOperandInfo> branchOperands;
58 for (auto &opOperand : op->getOpOperands()) {
59 auto branch = opOperand.get().getDefiningOp<BranchOp>();
60 if (!branch)
61 continue;
62
63 BranchOperandInfo &info = branchOperands[branch];
64 info.uniqueOperands.insert(opOperand.get());
65 info.indices.resize(op->getNumOperands());
66 info.indices.set(opOperand.getOperandNumber());
67 }
68
69 if (branchOperands.empty())
70 return failure();
71
72 // Do we have both operands from any given branch op?
73 for (auto &it : branchOperands) {
74 auto branch = it.first;
75 auto &operandInfo = it.second;
76 if (operandInfo.uniqueOperands.size() != 2) {
77 // We don't have both operands from the branch op.
78 continue;
79 }
80
81 // We have both operands from the branch op. Replace the join op with the
82 // branch op's data operand.
83
84 // Unpack the !dc.value<i1> input to the branch op
85 auto unpacked =
86 UnpackOp::create(rewriter, op.getLoc(), branch.getCondition());
87 rewriter.modifyOpInPlace(op, [&]() {
88 op->eraseOperands(operandInfo.indices);
89 op.getTokensMutable().append({unpacked.getToken()});
90 });
91
92 // Only attempt a single branch at a time - else we'd have to maintain
93 // OpOperand indices during the loop... too complicated, let recursive
94 // pattern application handle this.
95 return success();
96 }
97
98 return failure();
99 }
100};
103 LogicalResult matchAndRewrite(JoinOp op,
104 PatternRewriter &rewriter) const override {
105 for (OpOperand &operand : llvm::make_early_inc_range(op->getOpOperands())) {
106 auto otherJoin = operand.get().getDefiningOp<dc::JoinOp>();
107 if (!otherJoin) {
108 // Operand does not originate from a join so it's a valid join input.
109 continue;
110 }
111
112 // Operand originates from a join. Erase the current join operand and
113 // add all of the otherJoin op's inputs to this join.
114 // DCE will take care of otherJoin in case it's no longer used.
115 rewriter.modifyOpInPlace(op, [&]() {
116 op.getTokensMutable().erase(operand.getOperandNumber());
117 op.getTokensMutable().append(otherJoin.getTokens());
118 });
119 return success();
120 }
121 return failure();
122 }
123};
124
127 LogicalResult matchAndRewrite(JoinOp op,
128 PatternRewriter &rewriter) const override {
129 for (OpOperand &operand : llvm::make_early_inc_range(op->getOpOperands())) {
130 if (auto source = operand.get().getDefiningOp<dc::SourceOp>()) {
131 rewriter.modifyOpInPlace(
132 op, [&]() { op->eraseOperand(operand.getOperandNumber()); });
133 return success();
134 }
135 }
136 return failure();
137 }
138};
139
142 LogicalResult matchAndRewrite(JoinOp op,
143 PatternRewriter &rewriter) const override {
144 llvm::DenseSet<Value> uniqueOperands;
145 for (OpOperand &operand : llvm::make_early_inc_range(op->getOpOperands())) {
146 if (!uniqueOperands.insert(operand.get()).second) {
147 rewriter.modifyOpInPlace(
148 op, [&]() { op->eraseOperand(operand.getOperandNumber()); });
149 return success();
150 }
151 }
152 return failure();
153 }
154};
155
156void JoinOp::getCanonicalizationPatterns(RewritePatternSet &results,
157 MLIRContext *context) {
160}
161
162// =============================================================================
163// ForkOp
164// =============================================================================
165
166template <typename TInt>
167static ParseResult parseIntInSquareBrackets(OpAsmParser &parser, TInt &v) {
168 if (parser.parseLSquare() || parser.parseInteger(v) || parser.parseRSquare())
169 return failure();
170 return success();
171}
172
173ParseResult ForkOp::parse(OpAsmParser &parser, OperationState &result) {
174 OpAsmParser::UnresolvedOperand operand;
175 size_t size = 0;
176 if (parseIntInSquareBrackets(parser, size))
177 return failure();
178
179 if (size == 0)
180 return parser.emitError(parser.getNameLoc(),
181 "fork size must be greater than 0");
182
183 if (parser.parseOperand(operand) ||
184 parser.parseOptionalAttrDict(result.attributes))
185 return failure();
186
187 auto tt = dc::TokenType::get(parser.getContext());
188 llvm::SmallVector<Type> operandTypes{tt};
189 SmallVector<Type> resultTypes{size, tt};
190 result.addTypes(resultTypes);
191 if (parser.resolveOperand(operand, tt, result.operands))
192 return failure();
193 return success();
194}
195
196void ForkOp::print(OpAsmPrinter &p) {
197 p << " [" << getNumResults() << "] ";
198 p << getOperand() << " ";
199 auto attrs = (*this)->getAttrs();
200 if (!attrs.empty()) {
201 p << " ";
202 p.printOptionalAttrDict(attrs);
203 }
204}
205
207 // Canonicalization of forks where the output is fed into another fork.
208public:
210 LogicalResult matchAndRewrite(ForkOp fork,
211 PatternRewriter &rewriter) const override {
212 for (auto output : fork.getOutputs()) {
213 for (auto *user : output.getUsers()) {
214 auto userFork = dyn_cast<ForkOp>(user);
215 if (!userFork)
216 continue;
217
218 // We have a fork feeding into another fork. Replace the output fork by
219 // adding more outputs to the current fork.
220 size_t totalForks = fork.getNumResults() + userFork.getNumResults();
221
222 auto newFork = dc::ForkOp::create(rewriter, fork.getLoc(),
223 fork.getToken(), totalForks);
224 rewriter.replaceOp(
225 fork, newFork.getResults().take_front(fork.getNumResults()));
226 rewriter.replaceOp(
227 userFork, newFork.getResults().take_back(userFork.getNumResults()));
228
229 // Just stop the pattern here instead of trying to do more - let the
230 // canonicalizer recurse if another run of the canonicalization applies.
231 return success();
232 }
233 }
234 return failure();
235 }
236};
237
239 // Canonicalizes away forks on source ops, in favor of individual source
240 // operations. Having standalone sources are a better alternative, since other
241 // operations can canonicalize on it (e.g. joins) as well as being very cheap
242 // to implement in hardware, if they do remain.
243public:
245 LogicalResult matchAndRewrite(ForkOp fork,
246 PatternRewriter &rewriter) const override {
247 auto source = fork.getToken().getDefiningOp<SourceOp>();
248 if (!source)
249 return failure();
250
251 // We have a source feeding into a fork. Replace the fork by a source for
252 // each output.
253 llvm::SmallVector<Value> sources;
254 for (size_t i = 0; i < fork.getNumResults(); ++i)
255 sources.push_back(dc::SourceOp::create(rewriter, fork.getLoc()));
256
257 rewriter.replaceOp(fork, sources);
258 return success();
259 }
260};
261
264
265 LogicalResult matchAndRewrite(ForkOp op,
266 PatternRewriter &rewriter) const override {
267 std::set<unsigned> unusedIndexes;
268
269 for (auto res : llvm::enumerate(op.getResults()))
270 if (res.value().use_empty())
271 unusedIndexes.insert(res.index());
272
273 if (unusedIndexes.empty())
274 return failure();
275
276 // Create a new fork op, dropping the unused results.
277 rewriter.setInsertionPoint(op);
278 auto operand = op.getOperand();
279 auto newFork = ForkOp::create(rewriter, op.getLoc(), operand,
280 op.getNumResults() - unusedIndexes.size());
281 unsigned i = 0;
282 for (auto oldRes : llvm::enumerate(op.getResults()))
283 if (unusedIndexes.count(oldRes.index()) == 0)
284 rewriter.replaceAllUsesWith(oldRes.value(), newFork.getResults()[i++]);
285 rewriter.eraseOp(op);
286 return success();
287 }
288};
289
290void ForkOp::getCanonicalizationPatterns(RewritePatternSet &results,
291 MLIRContext *context) {
294}
295
296LogicalResult ForkOp::fold(FoldAdaptor adaptor,
297 SmallVectorImpl<OpFoldResult> &results) {
298 // Fold simple forks (forks with 1 output).
299 if (getOutputs().size() == 1) {
300 results.push_back(getToken());
301 return success();
302 }
303
304 return failure();
305}
306
307// =============================================================================
308// UnpackOp
309// =============================================================================
310
312 // Eliminates unpack(pack(token, data)) by replacing the unpack results with
313 // the pack inputs directly. This is done as a canonicalization pattern
314 // (rather than a fold) so that the dead pack can be erased in the same step.
316 LogicalResult matchAndRewrite(UnpackOp unpack,
317 PatternRewriter &rewriter) const override {
318 auto pack = unpack.getInput().getDefiningOp<PackOp>();
319 if (!pack)
320 return failure();
321
322 // Replace unpack(pack(token, data)) -> (token, data).
323 rewriter.replaceOp(unpack, {pack.getToken(), pack.getInput()});
324
325 // Erase the pack in case it no longer has users.
326 if (pack->use_empty())
327 rewriter.eraseOp(pack);
328
329 return success();
330 }
331};
332
333void UnpackOp::getCanonicalizationPatterns(RewritePatternSet &results,
334 MLIRContext *context) {
336}
337
338LogicalResult UnpackOp::inferReturnTypes(
339 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
340 DictionaryAttr attrs, mlir::PropertyRef properties,
341 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
342 auto inputType = cast<ValueType>(operands.front().getType());
343 results.push_back(dc::TokenType::get(context));
344 results.push_back(inputType.getInnerType());
345 return success();
346}
347
348// =============================================================================
349// PackOp
350// =============================================================================
351
353 // Eliminates pack(unpack(v).token, unpack(v).data) by replacing the pack
354 // result with v directly.
356 LogicalResult matchAndRewrite(PackOp pack,
357 PatternRewriter &rewriter) const override {
358 auto unpack = pack.getToken().getDefiningOp<UnpackOp>();
359 if (!unpack || unpack.getOutput() != pack.getInput())
360 return failure();
361
362 rewriter.replaceOp(pack, unpack.getInput());
363
364 // Erase the now-dead unpack.
365 if (unpack.getToken().use_empty() && unpack.getOutput().use_empty())
366 rewriter.eraseOp(unpack);
367
368 return success();
369 }
370};
371
372void PackOp::getCanonicalizationPatterns(RewritePatternSet &results,
373 MLIRContext *context) {
374 results.insert<EliminatePackOfUnpackPattern>(context);
375}
376
377LogicalResult PackOp::inferReturnTypes(
378 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
379 DictionaryAttr attrs, mlir::PropertyRef properties,
380 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
381 llvm::SmallVector<Type> inputTypes;
382 Type inputType = operands.back().getType();
383 auto valueType = dc::ValueType::get(context, inputType);
384 results.push_back(valueType);
385 return success();
386}
387
388// =============================================================================
389// SelectOp
390// =============================================================================
391
393 // Canonicalize away a select that is fed only by a single branch
394 // example:
395 // %true, %false = dc.branch %sel1 %token
396 // %0 = dc.select %sel2, %true, %false
397 // ->
398 // %0 = dc.join %sel1, %sel2, %token
399
400public:
402 LogicalResult matchAndRewrite(SelectOp select,
403 PatternRewriter &rewriter) const override {
404 // Do all the inputs come from a branch?
405 BranchOp branchInput;
406 for (auto operand : {select.getTrueToken(), select.getFalseToken()}) {
407 auto br = operand.getDefiningOp<BranchOp>();
408 if (!br)
409 return failure();
410
411 if (!branchInput)
412 branchInput = br;
413 else if (branchInput != br)
414 return failure();
415 }
416
417 // Replace the select with a join (unpack the select conditions).
418 rewriter.replaceOpWithNewOp<JoinOp>(
419 select,
420 llvm::SmallVector<Value>{
421 UnpackOp::create(rewriter, select.getLoc(), select.getCondition())
422 .getToken(),
423 UnpackOp::create(rewriter, branchInput.getLoc(),
424 branchInput.getCondition())
425 .getToken()});
426
427 return success();
428 }
429};
430
431void SelectOp::getCanonicalizationPatterns(RewritePatternSet &results,
432 MLIRContext *context) {
434}
435
436// =============================================================================
437// BufferOp
438// =============================================================================
439
440FailureOr<SmallVector<int64_t>> BufferOp::getInitValueArray() {
441 assert(getInitValues() && "initValues attribute not set");
442 SmallVector<int64_t> values;
443 for (auto value : getInitValuesAttr()) {
444 if (auto iValue = dyn_cast<IntegerAttr>(value)) {
445 values.push_back(iValue.getValue().getSExtValue());
446 } else {
447 return emitError() << "initValues attribute must be an array of integers";
448 }
449 }
450 return values;
451}
452
453LogicalResult BufferOp::verify() {
454 // Verify that exactly 'size' number of initial values have been provided, if
455 // an initializer list have been provided.
456 if (auto initVals = getInitValuesAttr()) {
457 auto nInits = initVals.size();
458 if (nInits != getSize())
459 return emitOpError() << "expected " << getSize()
460 << " init values but got " << nInits << ".";
461 }
462
463 return success();
464}
465
466// =============================================================================
467// ToESIOp
468// =============================================================================
469
470LogicalResult ToESIOp::inferReturnTypes(
471 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
472 DictionaryAttr attrs, mlir::PropertyRef properties,
473 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
474 Type channelEltType;
475 if (auto valueType = dyn_cast<ValueType>(operands.front().getType()))
476 channelEltType = valueType.getInnerType();
477 else {
478 // dc.token => esi.channel<i0>
479 channelEltType = IntegerType::get(context, 0);
480 }
481
482 results.push_back(esi::ChannelType::get(context, channelEltType));
483 return success();
484}
485
486// =============================================================================
487// FromESIOp
488// =============================================================================
489
490LogicalResult FromESIOp::inferReturnTypes(
491 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
492 DictionaryAttr attrs, mlir::PropertyRef properties,
493 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
494 auto innerType =
495 cast<esi::ChannelType>(operands.front().getType()).getInner();
496 if (auto intType = dyn_cast<IntegerType>(innerType); intType.getWidth() == 0)
497 results.push_back(dc::TokenType::get(context));
498 else
499 results.push_back(dc::ValueType::get(context, innerType));
500
501 return success();
502}
503
504} // namespace dc
505} // namespace circt
506
507#define GET_OP_CLASSES
508#include "circt/Dialect/DC/DC.cpp.inc"
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
LogicalResult matchAndRewrite(SelectOp select, PatternRewriter &rewriter) const override
Definition DCOps.cpp:402
LogicalResult matchAndRewrite(ForkOp fork, PatternRewriter &rewriter) const override
Definition DCOps.cpp:245
LogicalResult matchAndRewrite(ForkOp fork, PatternRewriter &rewriter) const override
Definition DCOps.cpp:210
static ParseResult parseIntInSquareBrackets(OpAsmParser &parser, TInt &v)
Definition DCOps.cpp:167
bool isI1ValueType(Type t)
Definition DCOps.cpp:21
mlir::Type innerType(mlir::Type type)
Definition ESITypes.cpp:422
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
LogicalResult matchAndRewrite(PackOp pack, PatternRewriter &rewriter) const override
Definition DCOps.cpp:356
LogicalResult matchAndRewrite(UnpackOp unpack, PatternRewriter &rewriter) const override
Definition DCOps.cpp:316
LogicalResult matchAndRewrite(ForkOp op, PatternRewriter &rewriter) const override
Definition DCOps.cpp:265
LogicalResult matchAndRewrite(JoinOp op, PatternRewriter &rewriter) const override
Definition DCOps.cpp:46
LogicalResult matchAndRewrite(JoinOp op, PatternRewriter &rewriter) const override
Definition DCOps.cpp:142
LogicalResult matchAndRewrite(JoinOp op, PatternRewriter &rewriter) const override
Definition DCOps.cpp:127
LogicalResult matchAndRewrite(JoinOp op, PatternRewriter &rewriter) const override
Definition DCOps.cpp:103