CIRCT 24.0.0git
Loading...
Searching...
No Matches
FIRRTLFolds.cpp
Go to the documentation of this file.
1//===- FIRRTLFolds.cpp - Implement folds and canonicalizations for ops ----===//
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//
9// This file implement the folding and canonicalizations for FIRRTL ops.
10//
11//===----------------------------------------------------------------------===//
12
17#include "circt/Support/APInt.h"
18#include "circt/Support/LLVM.h"
20#include "mlir/IR/Matchers.h"
21#include "mlir/IR/PatternMatch.h"
22#include "llvm/ADT/APSInt.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/SmallPtrSet.h"
25#include "llvm/ADT/StringExtras.h"
26#include "llvm/ADT/TypeSwitch.h"
27
28using namespace circt;
29using namespace firrtl;
30
31// Drop writes to old and pass through passthrough to make patterns easier to
32// write.
33static Value dropWrite(PatternRewriter &rewriter, OpResult old,
34 Value passthrough) {
35 SmallPtrSet<Operation *, 8> users;
36 for (auto *user : old.getUsers())
37 users.insert(user);
38 for (Operation *user : users)
39 if (auto connect = dyn_cast<FConnectLike>(user))
40 if (connect.getDest() == old)
41 rewriter.eraseOp(user);
42 return passthrough;
43}
44
45// Return true if it is OK to propagate the name to the operation.
46// Non-pure operations such as instances, registers, and memories are not
47// allowed to update names for name stabilities and LEC reasons.
48static bool isOkToPropagateName(Operation *op) {
49 // Conservatively disallow operations with regions to prevent performance
50 // regression due to recursive calls to sub-regions in mlir::isPure.
51 if (op->getNumRegions() != 0)
52 return false;
53 return mlir::isPure(op) || isa<NodeOp, WireOp>(op);
54}
55
56// Move a name hint from a soon to be deleted operation to a new operation.
57// Pass through the new operation to make patterns easier to write. This cannot
58// move a name to a port (block argument), doing so would require rewriting all
59// instance sites as well as the module.
60static Value moveNameHint(OpResult old, Value passthrough) {
61 Operation *op = passthrough.getDefiningOp();
62 // This should handle ports, but it isn't clear we can change those in
63 // canonicalizers.
64 assert(op && "passthrough must be an operation");
65 Operation *oldOp = old.getOwner();
66 auto name = oldOp->getAttrOfType<StringAttr>("name");
67 if (name && !name.getValue().empty() && isOkToPropagateName(op))
68 op->setAttr("name", name);
69 return passthrough;
70}
71
72// Declarative canonicalization patterns
73namespace circt {
74namespace firrtl {
75namespace patterns {
76#include "circt/Dialect/FIRRTL/FIRRTLCanonicalization.h.inc"
77} // namespace patterns
78} // namespace firrtl
79} // namespace circt
80
81/// Return true if this operation's operands and results all have a known width.
82/// This only works for integer types.
83static bool hasKnownWidthIntTypes(Operation *op) {
84 auto resultType = type_cast<IntType>(op->getResult(0).getType());
85 if (!resultType.hasWidth())
86 return false;
87 for (Value operand : op->getOperands())
88 if (!type_cast<IntType>(operand.getType()).hasWidth())
89 return false;
90 return true;
91}
92
93/// Return true if this value is 1 bit UInt.
94static bool isUInt1(Type type) {
95 auto t = type_dyn_cast<UIntType>(type);
96 if (!t || !t.hasWidth() || t.getWidth() != 1)
97 return false;
98 return true;
99}
100
101/// Set the name of an op based on the best of two names: The current name, and
102/// the name passed in.
103static void updateName(PatternRewriter &rewriter, Operation *op,
104 StringAttr name) {
105 // Should never rename InstanceOp
106 if (!name || name.getValue().empty() || !isOkToPropagateName(op))
107 return;
108 assert((!isa<InstanceOp, RegOp, RegResetOp>(op)) && "Should never rename");
109 auto newName = name.getValue(); // old name is interesting
110 auto newOpName = op->getAttrOfType<StringAttr>("name");
111 // new name might not be interesting
112 if (newOpName)
113 newName = chooseName(newOpName.getValue(), name.getValue());
114 // Only update if needed
115 if (!newOpName || newOpName.getValue() != newName)
116 rewriter.modifyOpInPlace(
117 op, [&] { op->setAttr("name", rewriter.getStringAttr(newName)); });
118}
119
120/// A wrapper of `PatternRewriter::replaceOp` to propagate "name" attribute.
121/// If a replaced op has a "name" attribute, this function propagates the name
122/// to the new value.
123static void replaceOpAndCopyName(PatternRewriter &rewriter, Operation *op,
124 Value newValue) {
125 if (auto *newOp = newValue.getDefiningOp()) {
126 auto name = op->getAttrOfType<StringAttr>("name");
127 updateName(rewriter, newOp, name);
128 }
129 rewriter.replaceOp(op, newValue);
130}
131
132/// A wrapper of `PatternRewriter::replaceOpWithNewOp` to propagate "name"
133/// attribute. If a replaced op has a "name" attribute, this function propagates
134/// the name to the new value.
135template <typename OpTy, typename... Args>
136static OpTy replaceOpWithNewOpAndCopyName(PatternRewriter &rewriter,
137 Operation *op, Args &&...args) {
138 auto name = op->getAttrOfType<StringAttr>("name");
139 auto newOp =
140 rewriter.replaceOpWithNewOp<OpTy>(op, std::forward<Args>(args)...);
141 updateName(rewriter, newOp, name);
142 return newOp;
143}
144
145/// Return true if the name is droppable. Note that this is different from
146/// `isUselessName` because non-useless names may be also droppable.
148 if (auto namableOp = dyn_cast<firrtl::FNamableOp>(op))
149 return namableOp.hasDroppableName();
150 return false;
151}
152
153/// Implicitly replace the operand to a constant folding operation with a const
154/// 0 in case the operand is non-constant but has a bit width 0, or if the
155/// operand is an invalid value.
156///
157/// This makes constant folding significantly easier, as we can simply pass the
158/// operands to an operation through this function to appropriately replace any
159/// zero-width dynamic values or invalid values with a constant of value 0.
160static std::optional<APSInt>
161getExtendedConstant(Value operand, Attribute constant, int32_t destWidth) {
162 assert(type_cast<IntType>(operand.getType()) &&
163 "getExtendedConstant is limited to integer types");
164
165 // We never support constant folding to unknown width values.
166 if (destWidth < 0)
167 return {};
168
169 // Extension signedness follows the operand sign.
170 if (IntegerAttr result = dyn_cast_or_null<IntegerAttr>(constant))
171 return extOrTruncZeroWidth(result.getAPSInt(), destWidth);
172
173 // If the operand is zero bits, then we can return a zero of the result
174 // type.
175 if (type_cast<IntType>(operand.getType()).getWidth() == 0)
176 return APSInt(destWidth,
177 type_cast<IntType>(operand.getType()).isUnsigned());
178 return {};
179}
180
181/// Determine the value of a constant operand for the sake of constant folding.
182static std::optional<APSInt> getConstant(Attribute operand) {
183 if (!operand)
184 return {};
185 if (auto attr = dyn_cast<BoolAttr>(operand))
186 return APSInt(APInt(1, attr.getValue()));
187 if (auto attr = dyn_cast<IntegerAttr>(operand))
188 return attr.getAPSInt();
189 return {};
190}
191
192/// Determine whether a constant operand is a zero value for the sake of
193/// constant folding. This considers `invalidvalue` to be zero.
194static bool isConstantZero(Attribute operand) {
195 if (auto cst = getConstant(operand))
196 return cst->isZero();
197 return false;
198}
199
200/// Determine whether a constant operand is a one value for the sake of constant
201/// folding.
202static bool isConstantOne(Attribute operand) {
203 if (auto cst = getConstant(operand))
204 return cst->isOne();
205 return false;
206}
207
208/// This is the policy for folding, which depends on the sort of operator we're
209/// processing.
215
216/// Applies the constant folding function `calculate` to the given operands.
217///
218/// Sign or zero extends the operands appropriately to the bitwidth of the
219/// result type if \p useDstWidth is true, else to the larger of the two operand
220/// bit widths and depending on whether the operation is to be performed on
221/// signed or unsigned operands.
222static Attribute constFoldFIRRTLBinaryOp(
223 Operation *op, ArrayRef<Attribute> operands, BinOpKind opKind,
224 const function_ref<APInt(const APSInt &, const APSInt &)> &calculate) {
225 assert(operands.size() == 2 && "binary op takes two operands");
226
227 // We cannot fold something to an unknown width.
228 auto resultType = type_cast<IntType>(op->getResult(0).getType());
229 if (resultType.getWidthOrSentinel() < 0)
230 return {};
231
232 // Any binary op returning i0 is 0.
233 if (resultType.getWidthOrSentinel() == 0)
234 return getIntAttr(resultType, APInt(0, 0, resultType.isSigned()));
235
236 // Determine the operand widths. This is either dictated by the operand type,
237 // or if that type is an unsized integer, by the actual bits necessary to
238 // represent the constant value.
239 auto lhsWidth =
240 type_cast<IntType>(op->getOperand(0).getType()).getWidthOrSentinel();
241 auto rhsWidth =
242 type_cast<IntType>(op->getOperand(1).getType()).getWidthOrSentinel();
243 if (auto lhs = dyn_cast_or_null<IntegerAttr>(operands[0]))
244 lhsWidth = std::max<int32_t>(lhsWidth, lhs.getValue().getBitWidth());
245 if (auto rhs = dyn_cast_or_null<IntegerAttr>(operands[1]))
246 rhsWidth = std::max<int32_t>(rhsWidth, rhs.getValue().getBitWidth());
247
248 // Compares extend the operands to the widest of the operand types, not to the
249 // result type.
250 int32_t operandWidth;
251 switch (opKind) {
253 operandWidth = resultType.getWidthOrSentinel();
254 break;
256 // Compares compute with the widest operand, not at the destination type
257 // (which is always i1).
258 operandWidth = std::max(1, std::max(lhsWidth, rhsWidth));
259 break;
261 operandWidth =
262 std::max(std::max(lhsWidth, rhsWidth), resultType.getWidthOrSentinel());
263 break;
264 }
265
266 auto lhs = getExtendedConstant(op->getOperand(0), operands[0], operandWidth);
267 if (!lhs)
268 return {};
269 auto rhs = getExtendedConstant(op->getOperand(1), operands[1], operandWidth);
270 if (!rhs)
271 return {};
272
273 APInt resultValue = calculate(*lhs, *rhs);
274
275 // If the result type is smaller than the computation then we need to
276 // narrow the constant after the calculation.
277 if (opKind == BinOpKind::DivideOrShift)
278 resultValue = resultValue.trunc(resultType.getWidthOrSentinel());
279
280 assert((unsigned)resultType.getWidthOrSentinel() ==
281 resultValue.getBitWidth());
282 return getIntAttr(resultType, resultValue);
283}
284
285/// Applies the canonicalization function `canonicalize` to the given operation.
286///
287/// Determines which (if any) of the operation's operands are constants, and
288/// provides them as arguments to the callback function. Any `invalidvalue` in
289/// the input is mapped to a constant zero. The value returned from the callback
290/// is used as the replacement for `op`, and an additional pad operation is
291/// inserted if necessary. Does nothing if the result of `op` is of unknown
292/// width, in which case the necessity of a pad cannot be determined.
293static LogicalResult canonicalizePrimOp(
294 Operation *op, PatternRewriter &rewriter,
295 const function_ref<OpFoldResult(ArrayRef<Attribute>)> &canonicalize) {
296 // Can only operate on FIRRTL primitive operations.
297 if (op->getNumResults() != 1)
298 return failure();
299 auto type = type_dyn_cast<FIRRTLBaseType>(op->getResult(0).getType());
300 if (!type)
301 return failure();
302
303 // Can only operate on operations with a known result width.
304 auto width = type.getBitWidthOrSentinel();
305 if (width < 0)
306 return failure();
307
308 // Determine which of the operands are constants.
309 SmallVector<Attribute, 3> constOperands;
310 constOperands.reserve(op->getNumOperands());
311 for (auto operand : op->getOperands()) {
312 Attribute attr;
313 if (auto *defOp = operand.getDefiningOp())
314 TypeSwitch<Operation *>(defOp).Case<ConstantOp, SpecialConstantOp>(
315 [&](auto op) { attr = op.getValueAttr(); });
316 constOperands.push_back(attr);
317 }
318
319 // Perform the canonicalization and materialize the result if it is a
320 // constant.
321 auto result = canonicalize(constOperands);
322 if (!result)
323 return failure();
324 Value resultValue;
325 if (auto cst = dyn_cast<Attribute>(result))
326 resultValue = op->getDialect()
327 ->materializeConstant(rewriter, cst, type, op->getLoc())
328 ->getResult(0);
329 else
330 resultValue = cast<Value>(result);
331
332 // Insert a pad if the type widths disagree.
333 if (width !=
334 type_cast<FIRRTLBaseType>(resultValue.getType()).getBitWidthOrSentinel())
335 resultValue = PadPrimOp::create(rewriter, op->getLoc(), resultValue, width);
336
337 // Insert a cast if this is a uint vs. sint or vice versa.
338 if (type_isa<SIntType>(type) && type_isa<UIntType>(resultValue.getType()))
339 resultValue = AsSIntPrimOp::create(rewriter, op->getLoc(), resultValue);
340 else if (type_isa<UIntType>(type) &&
341 type_isa<SIntType>(resultValue.getType()))
342 resultValue = AsUIntPrimOp::create(rewriter, op->getLoc(), resultValue);
343
344 assert(type == resultValue.getType() && "canonicalization changed type");
345 replaceOpAndCopyName(rewriter, op, resultValue);
346 return success();
347}
348
349/// Get the largest unsigned value of a given bit width. Returns a 1-bit zero
350/// value if `bitWidth` is 0.
351static APInt getMaxUnsignedValue(unsigned bitWidth) {
352 return bitWidth > 0 ? APInt::getMaxValue(bitWidth) : APInt();
353}
354
355/// Get the smallest signed value of a given bit width. Returns a 1-bit zero
356/// value if `bitWidth` is 0.
357static APInt getMinSignedValue(unsigned bitWidth) {
358 return bitWidth > 0 ? APInt::getSignedMinValue(bitWidth) : APInt();
359}
360
361/// Get the largest signed value of a given bit width. Returns a 1-bit zero
362/// value if `bitWidth` is 0.
363static APInt getMaxSignedValue(unsigned bitWidth) {
364 return bitWidth > 0 ? APInt::getSignedMaxValue(bitWidth) : APInt();
365}
366
367//===----------------------------------------------------------------------===//
368// Fold Hooks
369//===----------------------------------------------------------------------===//
370
371OpFoldResult ConstantOp::fold(FoldAdaptor adaptor) {
372 assert(adaptor.getOperands().empty() && "constant has no operands");
373 return getValueAttr();
374}
375
376OpFoldResult SpecialConstantOp::fold(FoldAdaptor adaptor) {
377 assert(adaptor.getOperands().empty() && "constant has no operands");
378 return getValueAttr();
379}
380
381OpFoldResult AggregateConstantOp::fold(FoldAdaptor adaptor) {
382 assert(adaptor.getOperands().empty() && "constant has no operands");
383 return getFieldsAttr();
384}
385
386OpFoldResult StringConstantOp::fold(FoldAdaptor adaptor) {
387 assert(adaptor.getOperands().empty() && "constant has no operands");
388 return getValueAttr();
389}
390
391OpFoldResult FIntegerConstantOp::fold(FoldAdaptor adaptor) {
392 assert(adaptor.getOperands().empty() && "constant has no operands");
393 return getValueAttr();
394}
395
396OpFoldResult BoolConstantOp::fold(FoldAdaptor adaptor) {
397 assert(adaptor.getOperands().empty() && "constant has no operands");
398 return getValueAttr();
399}
400
401OpFoldResult DoubleConstantOp::fold(FoldAdaptor adaptor) {
402 assert(adaptor.getOperands().empty() && "constant has no operands");
403 return getValueAttr();
404}
405
406//===----------------------------------------------------------------------===//
407// Binary Operators
408//===----------------------------------------------------------------------===//
409
410OpFoldResult AddPrimOp::fold(FoldAdaptor adaptor) {
412 *this, adaptor.getOperands(), BinOpKind::Normal,
413 [=](const APSInt &a, const APSInt &b) { return a + b; });
414}
415
416void AddPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
417 MLIRContext *context) {
418 results.insert<patterns::moveConstAdd, patterns::AddOfZero,
419 patterns::AddOfSelf, patterns::AddOfPad>(context);
420}
421
422OpFoldResult SubPrimOp::fold(FoldAdaptor adaptor) {
424 *this, adaptor.getOperands(), BinOpKind::Normal,
425 [=](const APSInt &a, const APSInt &b) { return a - b; });
426}
427
428void SubPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
429 MLIRContext *context) {
430 results.insert<patterns::SubOfZero, patterns::SubFromZeroSigned,
431 patterns::SubFromZeroUnsigned, patterns::SubOfSelf,
432 patterns::SubOfPadL, patterns::SubOfPadR>(context);
433}
434
435OpFoldResult MulPrimOp::fold(FoldAdaptor adaptor) {
436 // mul(x, 0) -> 0
437 //
438 // This is legal because it aligns with the Scala FIRRTL Compiler
439 // interpretation of lowering invalid to constant zero before constant
440 // propagation. Note: the Scala FIRRTL Compiler does NOT currently optimize
441 // multiplication this way and will emit "x * 0".
442 if (isConstantZero(adaptor.getRhs()) || isConstantZero(adaptor.getLhs()))
443 return getIntZerosAttr(getType());
444
446 *this, adaptor.getOperands(), BinOpKind::Normal,
447 [=](const APSInt &a, const APSInt &b) { return a * b; });
448}
449
450OpFoldResult DivPrimOp::fold(FoldAdaptor adaptor) {
451 /// div(x, x) -> 1
452 ///
453 /// Division by zero is undefined in the FIRRTL specification. This fold
454 /// exploits that fact to optimize self division to one. Note: this should
455 /// supersede any division with invalid or zero. Division of invalid by
456 /// invalid should be one.
457 if (getLhs() == getRhs()) {
458 auto width = getType().base().getWidthOrSentinel();
459 if (width == -1)
460 width = 2;
461 // Only fold if we have at least 1 bit of width to represent the `1` value.
462 if (width != 0)
463 return getIntAttr(getType(), APInt(width, 1));
464 }
465
466 // div(0, x) -> 0
467 //
468 // This is legal because it aligns with the Scala FIRRTL Compiler
469 // interpretation of lowering invalid to constant zero before constant
470 // propagation. Note: the Scala FIRRTL Compiler does NOT currently optimize
471 // division this way and will emit "0 / x".
472 if (isConstantZero(adaptor.getLhs()) && !isConstantZero(adaptor.getRhs()))
473 return getIntZerosAttr(getType());
474
475 /// div(x, 1) -> x : (uint, uint) -> uint
476 ///
477 /// UInt division by one returns the numerator. SInt division can't
478 /// be folded here because it increases the return type bitwidth by
479 /// one and requires sign extension (a new op).
480 if (auto rhsCst = dyn_cast_or_null<IntegerAttr>(adaptor.getRhs()))
481 if (rhsCst.getValue().isOne() && getLhs().getType() == getType())
482 return getLhs();
483
485 *this, adaptor.getOperands(), BinOpKind::DivideOrShift,
486 [=](const APSInt &a, const APSInt &b) -> APInt {
487 if (!!b)
488 return a / b;
489 return APInt(a.getBitWidth(), 0);
490 });
491}
492
493OpFoldResult RemPrimOp::fold(FoldAdaptor adaptor) {
494 // rem(x, x) -> 0
495 //
496 // Division by zero is undefined in the FIRRTL specification. This fold
497 // exploits that fact to optimize self division remainder to zero. Note:
498 // this should supersede any division with invalid or zero. Remainder of
499 // division of invalid by invalid should be zero.
500 if (getLhs() == getRhs())
501 return getIntZerosAttr(getType());
502
503 // rem(0, x) -> 0
504 //
505 // This is legal because it aligns with the Scala FIRRTL Compiler
506 // interpretation of lowering invalid to constant zero before constant
507 // propagation. Note: the Scala FIRRTL Compiler does NOT currently optimize
508 // division this way and will emit "0 % x".
509 if (isConstantZero(adaptor.getLhs()))
510 return getIntZerosAttr(getType());
511
513 *this, adaptor.getOperands(), BinOpKind::DivideOrShift,
514 [=](const APSInt &a, const APSInt &b) -> APInt {
515 if (!!b)
516 return a % b;
517 return APInt(a.getBitWidth(), 0);
518 });
519}
520
521OpFoldResult DShlPrimOp::fold(FoldAdaptor adaptor) {
523 *this, adaptor.getOperands(), BinOpKind::DivideOrShift,
524 [=](const APSInt &a, const APSInt &b) -> APInt { return a.shl(b); });
525}
526
527OpFoldResult DShlwPrimOp::fold(FoldAdaptor adaptor) {
529 *this, adaptor.getOperands(), BinOpKind::DivideOrShift,
530 [=](const APSInt &a, const APSInt &b) -> APInt { return a.shl(b); });
531}
532
533OpFoldResult DShrPrimOp::fold(FoldAdaptor adaptor) {
535 *this, adaptor.getOperands(), BinOpKind::DivideOrShift,
536 [=](const APSInt &a, const APSInt &b) -> APInt {
537 return getType().base().isUnsigned() || !a.getBitWidth() ? a.lshr(b)
538 : a.ashr(b);
539 });
540}
541
542// TODO: Move to DRR.
543OpFoldResult AndPrimOp::fold(FoldAdaptor adaptor) {
544 if (auto rhsCst = getConstant(adaptor.getRhs())) {
545 /// and(x, 0) -> 0, 0 is largest or is implicit zero extended
546 if (rhsCst->isZero())
547 return getIntZerosAttr(getType());
548
549 /// and(x, -1) -> x
550 if (rhsCst->isAllOnes() && getLhs().getType() == getType() &&
551 getRhs().getType() == getType())
552 return getLhs();
553 }
554
555 if (auto lhsCst = getConstant(adaptor.getLhs())) {
556 /// and(0, x) -> 0, 0 is largest or is implicit zero extended
557 if (lhsCst->isZero())
558 return getIntZerosAttr(getType());
559
560 /// and(-1, x) -> x
561 if (lhsCst->isAllOnes() && getLhs().getType() == getType() &&
562 getRhs().getType() == getType())
563 return getRhs();
564 }
565
566 /// and(x, x) -> x
567 if (getLhs() == getRhs() && getRhs().getType() == getType())
568 return getRhs();
569
571 *this, adaptor.getOperands(), BinOpKind::Normal,
572 [](const APSInt &a, const APSInt &b) -> APInt { return a & b; });
573}
574
575void AndPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
576 MLIRContext *context) {
577 results
578 .insert<patterns::extendAnd, patterns::moveConstAnd, patterns::AndOfZero,
579 patterns::AndOfAllOne, patterns::AndOfSelf, patterns::AndOfPad,
580 patterns::AndOfAsSIntL, patterns::AndOfAsSIntR>(context);
581}
582
583OpFoldResult OrPrimOp::fold(FoldAdaptor adaptor) {
584 if (auto rhsCst = getConstant(adaptor.getRhs())) {
585 /// or(x, 0) -> x
586 if (rhsCst->isZero() && getLhs().getType() == getType())
587 return getLhs();
588
589 /// or(x, -1) -> -1
590 if (rhsCst->isAllOnes() && getRhs().getType() == getType() &&
591 getLhs().getType() == getType())
592 return getRhs();
593 }
594
595 if (auto lhsCst = getConstant(adaptor.getLhs())) {
596 /// or(0, x) -> x
597 if (lhsCst->isZero() && getRhs().getType() == getType())
598 return getRhs();
599
600 /// or(-1, x) -> -1
601 if (lhsCst->isAllOnes() && getLhs().getType() == getType() &&
602 getRhs().getType() == getType())
603 return getLhs();
604 }
605
606 /// or(x, x) -> x
607 if (getLhs() == getRhs() && getRhs().getType() == getType())
608 return getRhs();
609
611 *this, adaptor.getOperands(), BinOpKind::Normal,
612 [](const APSInt &a, const APSInt &b) -> APInt { return a | b; });
613}
614
615void OrPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
616 MLIRContext *context) {
617 results.insert<patterns::extendOr, patterns::moveConstOr, patterns::OrOfZero,
618 patterns::OrOfAllOne, patterns::OrOfSelf, patterns::OrOfPad,
619 patterns::OrOrr>(context);
620}
621
622OpFoldResult XorPrimOp::fold(FoldAdaptor adaptor) {
623 /// xor(x, 0) -> x
624 if (auto rhsCst = getConstant(adaptor.getRhs()))
625 if (rhsCst->isZero() &&
626 firrtl::areAnonymousTypesEquivalent(getLhs().getType(), getType()))
627 return getLhs();
628
629 /// xor(x, 0) -> x
630 if (auto lhsCst = getConstant(adaptor.getLhs()))
631 if (lhsCst->isZero() &&
632 firrtl::areAnonymousTypesEquivalent(getRhs().getType(), getType()))
633 return getRhs();
634
635 /// xor(x, x) -> 0
636 if (getLhs() == getRhs())
637 return getIntAttr(
638 getType(),
639 APInt(std::max(getType().base().getWidthOrSentinel(), 0), 0));
640
642 *this, adaptor.getOperands(), BinOpKind::Normal,
643 [](const APSInt &a, const APSInt &b) -> APInt { return a ^ b; });
644}
645
646void XorPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
647 MLIRContext *context) {
648 results.insert<patterns::extendXor, patterns::moveConstXor,
649 patterns::XorOfZero, patterns::XorOfSelf, patterns::XorOfPad>(
650 context);
651}
652
653void LEQPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
654 MLIRContext *context) {
655 results.insert<patterns::LEQWithConstLHS>(context);
656}
657
658OpFoldResult LEQPrimOp::fold(FoldAdaptor adaptor) {
659 bool isUnsigned = getLhs().getType().base().isUnsigned();
660
661 // leq(x, x) -> 1
662 if (getLhs() == getRhs())
663 return getIntAttr(getType(), APInt(1, 1));
664
665 // Comparison against constant outside type bounds.
666 if (auto width = getLhs().getType().base().getWidth()) {
667 if (auto rhsCst = getConstant(adaptor.getRhs())) {
668 auto commonWidth = std::max<int32_t>(*width, rhsCst->getBitWidth());
669 commonWidth = std::max(commonWidth, 1);
670
671 // leq(x, const) -> 0 where const < minValue of the unsigned type of x
672 // This can never occur since const is unsigned and cannot be less than 0.
673
674 // leq(x, const) -> 0 where const < minValue of the signed type of x
675 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
676 .slt(getMinSignedValue(*width).sext(commonWidth)))
677 return getIntAttr(getType(), APInt(1, 0));
678
679 // leq(x, const) -> 1 where const >= maxValue of the unsigned type of x
680 if (isUnsigned && rhsCst->zext(commonWidth)
681 .uge(getMaxUnsignedValue(*width).zext(commonWidth)))
682 return getIntAttr(getType(), APInt(1, 1));
683
684 // leq(x, const) -> 1 where const >= maxValue of the signed type of x
685 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
686 .sge(getMaxSignedValue(*width).sext(commonWidth)))
687 return getIntAttr(getType(), APInt(1, 1));
688 }
689 }
690
692 *this, adaptor.getOperands(), BinOpKind::Compare,
693 [=](const APSInt &a, const APSInt &b) -> APInt {
694 return APInt(1, a <= b);
695 });
696}
697
698void LTPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
699 MLIRContext *context) {
700 results.insert<patterns::LTWithConstLHS>(context);
701}
702
703OpFoldResult LTPrimOp::fold(FoldAdaptor adaptor) {
704 IntType lhsType = getLhs().getType();
705 bool isUnsigned = lhsType.isUnsigned();
706
707 // lt(x, x) -> 0
708 if (getLhs() == getRhs())
709 return getIntAttr(getType(), APInt(1, 0));
710
711 // lt(x, 0) -> 0 when x is unsigned
712 if (auto rhsCst = getConstant(adaptor.getRhs())) {
713 if (rhsCst->isZero() && lhsType.isUnsigned())
714 return getIntAttr(getType(), APInt(1, 0));
715 }
716
717 // Comparison against constant outside type bounds.
718 if (auto width = lhsType.getWidth()) {
719 if (auto rhsCst = getConstant(adaptor.getRhs())) {
720 auto commonWidth = std::max<int32_t>(*width, rhsCst->getBitWidth());
721 commonWidth = std::max(commonWidth, 1);
722
723 // lt(x, const) -> 0 where const <= minValue of the unsigned type of x
724 // Handled explicitly above.
725
726 // lt(x, const) -> 0 where const <= minValue of the signed type of x
727 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
728 .sle(getMinSignedValue(*width).sext(commonWidth)))
729 return getIntAttr(getType(), APInt(1, 0));
730
731 // lt(x, const) -> 1 where const > maxValue of the unsigned type of x
732 if (isUnsigned && rhsCst->zext(commonWidth)
733 .ugt(getMaxUnsignedValue(*width).zext(commonWidth)))
734 return getIntAttr(getType(), APInt(1, 1));
735
736 // lt(x, const) -> 1 where const > maxValue of the signed type of x
737 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
738 .sgt(getMaxSignedValue(*width).sext(commonWidth)))
739 return getIntAttr(getType(), APInt(1, 1));
740 }
741 }
742
744 *this, adaptor.getOperands(), BinOpKind::Compare,
745 [=](const APSInt &a, const APSInt &b) -> APInt {
746 return APInt(1, a < b);
747 });
748}
749
750void GEQPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
751 MLIRContext *context) {
752 results.insert<patterns::GEQWithConstLHS>(context);
753}
754
755OpFoldResult GEQPrimOp::fold(FoldAdaptor adaptor) {
756 IntType lhsType = getLhs().getType();
757 bool isUnsigned = lhsType.isUnsigned();
758
759 // geq(x, x) -> 1
760 if (getLhs() == getRhs())
761 return getIntAttr(getType(), APInt(1, 1));
762
763 // geq(x, 0) -> 1 when x is unsigned
764 if (auto rhsCst = getConstant(adaptor.getRhs())) {
765 if (rhsCst->isZero() && isUnsigned)
766 return getIntAttr(getType(), APInt(1, 1));
767 }
768
769 // Comparison against constant outside type bounds.
770 if (auto width = lhsType.getWidth()) {
771 if (auto rhsCst = getConstant(adaptor.getRhs())) {
772 auto commonWidth = std::max<int32_t>(*width, rhsCst->getBitWidth());
773 commonWidth = std::max(commonWidth, 1);
774
775 // geq(x, const) -> 0 where const > maxValue of the unsigned type of x
776 if (isUnsigned && rhsCst->zext(commonWidth)
777 .ugt(getMaxUnsignedValue(*width).zext(commonWidth)))
778 return getIntAttr(getType(), APInt(1, 0));
779
780 // geq(x, const) -> 0 where const > maxValue of the signed type of x
781 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
782 .sgt(getMaxSignedValue(*width).sext(commonWidth)))
783 return getIntAttr(getType(), APInt(1, 0));
784
785 // geq(x, const) -> 1 where const <= minValue of the unsigned type of x
786 // Handled explicitly above.
787
788 // geq(x, const) -> 1 where const <= minValue of the signed type of x
789 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
790 .sle(getMinSignedValue(*width).sext(commonWidth)))
791 return getIntAttr(getType(), APInt(1, 1));
792 }
793 }
794
796 *this, adaptor.getOperands(), BinOpKind::Compare,
797 [=](const APSInt &a, const APSInt &b) -> APInt {
798 return APInt(1, a >= b);
799 });
800}
801
802void GTPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
803 MLIRContext *context) {
804 results.insert<patterns::GTWithConstLHS>(context);
805}
806
807OpFoldResult GTPrimOp::fold(FoldAdaptor adaptor) {
808 IntType lhsType = getLhs().getType();
809 bool isUnsigned = lhsType.isUnsigned();
810
811 // gt(x, x) -> 0
812 if (getLhs() == getRhs())
813 return getIntAttr(getType(), APInt(1, 0));
814
815 // Comparison against constant outside type bounds.
816 if (auto width = lhsType.getWidth()) {
817 if (auto rhsCst = getConstant(adaptor.getRhs())) {
818 auto commonWidth = std::max<int32_t>(*width, rhsCst->getBitWidth());
819 commonWidth = std::max(commonWidth, 1);
820
821 // gt(x, const) -> 0 where const >= maxValue of the unsigned type of x
822 if (isUnsigned && rhsCst->zext(commonWidth)
823 .uge(getMaxUnsignedValue(*width).zext(commonWidth)))
824 return getIntAttr(getType(), APInt(1, 0));
825
826 // gt(x, const) -> 0 where const >= maxValue of the signed type of x
827 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
828 .sge(getMaxSignedValue(*width).sext(commonWidth)))
829 return getIntAttr(getType(), APInt(1, 0));
830
831 // gt(x, const) -> 1 where const < minValue of the unsigned type of x
832 // This can never occur since const is unsigned and cannot be less than 0.
833
834 // gt(x, const) -> 1 where const < minValue of the signed type of x
835 if (!isUnsigned && sextZeroWidth(*rhsCst, commonWidth)
836 .slt(getMinSignedValue(*width).sext(commonWidth)))
837 return getIntAttr(getType(), APInt(1, 1));
838 }
839 }
840
842 *this, adaptor.getOperands(), BinOpKind::Compare,
843 [=](const APSInt &a, const APSInt &b) -> APInt {
844 return APInt(1, a > b);
845 });
846}
847
848OpFoldResult EQPrimOp::fold(FoldAdaptor adaptor) {
849 // eq(x, x) -> 1
850 if (getLhs() == getRhs())
851 return getIntAttr(getType(), APInt(1, 1));
852
853 if (auto rhsCst = getConstant(adaptor.getRhs())) {
854 /// eq(x, 1) -> x when x is 1 bit.
855 /// TODO: Support SInt<1> on the LHS etc.
856 if (rhsCst->isAllOnes() && getLhs().getType() == getType() &&
857 getRhs().getType() == getType())
858 return getLhs();
859 }
860
862 *this, adaptor.getOperands(), BinOpKind::Compare,
863 [=](const APSInt &a, const APSInt &b) -> APInt {
864 return APInt(1, a == b);
865 });
866}
867
868LogicalResult EQPrimOp::canonicalize(EQPrimOp op, PatternRewriter &rewriter) {
869 return canonicalizePrimOp(
870 op, rewriter, [&](ArrayRef<Attribute> operands) -> OpFoldResult {
871 if (auto rhsCst = getConstant(operands[1])) {
872 auto width = op.getLhs().getType().getBitWidthOrSentinel();
873
874 // eq(x, 0) -> not(x) when x is 1 bit.
875 if (rhsCst->isZero() && op.getLhs().getType() == op.getType() &&
876 op.getRhs().getType() == op.getType()) {
877 return NotPrimOp::create(rewriter, op.getLoc(), op.getLhs())
878 .getResult();
879 }
880
881 // eq(x, 0) -> not(orr(x)) when x is >1 bit
882 if (rhsCst->isZero() && width > 1) {
883 auto orrOp = OrRPrimOp::create(rewriter, op.getLoc(), op.getLhs());
884 return NotPrimOp::create(rewriter, op.getLoc(), orrOp).getResult();
885 }
886
887 // eq(x, ~0) -> andr(x) when x is >1 bit
888 if (rhsCst->isAllOnes() && width > 1 &&
889 op.getLhs().getType() == op.getRhs().getType()) {
890 return AndRPrimOp::create(rewriter, op.getLoc(), op.getLhs())
891 .getResult();
892 }
893 }
894 return {};
895 });
896}
897
898OpFoldResult NEQPrimOp::fold(FoldAdaptor adaptor) {
899 // neq(x, x) -> 0
900 if (getLhs() == getRhs())
901 return getIntAttr(getType(), APInt(1, 0));
902
903 if (auto rhsCst = getConstant(adaptor.getRhs())) {
904 /// neq(x, 0) -> x when x is 1 bit.
905 /// TODO: Support SInt<1> on the LHS etc.
906 if (rhsCst->isZero() && getLhs().getType() == getType() &&
907 getRhs().getType() == getType())
908 return getLhs();
909 }
910
912 *this, adaptor.getOperands(), BinOpKind::Compare,
913 [=](const APSInt &a, const APSInt &b) -> APInt {
914 return APInt(1, a != b);
915 });
916}
917
918LogicalResult NEQPrimOp::canonicalize(NEQPrimOp op, PatternRewriter &rewriter) {
919 return canonicalizePrimOp(
920 op, rewriter, [&](ArrayRef<Attribute> operands) -> OpFoldResult {
921 if (auto rhsCst = getConstant(operands[1])) {
922 auto width = op.getLhs().getType().getBitWidthOrSentinel();
923
924 // neq(x, 1) -> not(x) when x is 1 bit
925 if (rhsCst->isAllOnes() && op.getLhs().getType() == op.getType() &&
926 op.getRhs().getType() == op.getType()) {
927 return NotPrimOp::create(rewriter, op.getLoc(), op.getLhs())
928 .getResult();
929 }
930
931 // neq(x, 0) -> orr(x) when x is >1 bit
932 if (rhsCst->isZero() && width > 1) {
933 return OrRPrimOp::create(rewriter, op.getLoc(), op.getLhs())
934 .getResult();
935 }
936
937 // neq(x, ~0) -> not(andr(x))) when x is >1 bit
938 if (rhsCst->isAllOnes() && width > 1 &&
939 op.getLhs().getType() == op.getRhs().getType()) {
940 auto andrOp =
941 AndRPrimOp::create(rewriter, op.getLoc(), op.getLhs());
942 return NotPrimOp::create(rewriter, op.getLoc(), andrOp).getResult();
943 }
944 }
945
946 return {};
947 });
948}
949
950OpFoldResult IntegerAddOp::fold(FoldAdaptor adaptor) {
951 // TODO: implement constant folding, etc.
952 // Tracked in https://github.com/llvm/circt/issues/6696.
953 return {};
954}
955
956OpFoldResult IntegerMulOp::fold(FoldAdaptor adaptor) {
957 // TODO: implement constant folding, etc.
958 // Tracked in https://github.com/llvm/circt/issues/6724.
959 return {};
960}
961
962OpFoldResult IntegerShrOp::fold(FoldAdaptor adaptor) {
963 if (auto rhsCst = getConstant(adaptor.getRhs())) {
964 if (auto lhsCst = getConstant(adaptor.getLhs())) {
965
966 return IntegerAttr::get(IntegerType::get(getContext(),
967 lhsCst->getBitWidth(),
968 IntegerType::Signed),
969 lhsCst->ashr(*rhsCst));
970 }
971
972 if (rhsCst->isZero())
973 return getLhs();
974 }
975
976 return {};
977}
978
979OpFoldResult IntegerShlOp::fold(FoldAdaptor adaptor) {
980 if (auto rhsCst = getConstant(adaptor.getRhs())) {
981 // Constant folding
982 if (auto lhsCst = getConstant(adaptor.getLhs()))
983
984 return IntegerAttr::get(IntegerType::get(getContext(),
985 lhsCst->getBitWidth(),
986 IntegerType::Signed),
987 lhsCst->shl(*rhsCst));
988
989 // integer.shl(x, 0) -> x
990 if (rhsCst->isZero())
991 return getLhs();
992 }
993
994 return {};
995}
996
997/// Fold bitwise operations on FIRRTL arbitrary precision signed integer
998/// properties. Integer constants may use different storage widths; sign-extend
999/// both operands to the wider storage width so the operation has signed
1000/// two's-complement semantics without truncating its result.
1001static IntegerAttr foldFIntegerBinaryBitwise(
1002 MLIRContext *context, Attribute lhsAttr, Attribute rhsAttr,
1003 const function_ref<APInt(const APInt &, const APInt &)> &evaluate) {
1004 auto lhs = dyn_cast_or_null<IntegerAttr>(lhsAttr);
1005 auto rhs = dyn_cast_or_null<IntegerAttr>(rhsAttr);
1006 if (!lhs || !rhs || !lhs.getType().isSignedInteger() ||
1007 !rhs.getType().isSignedInteger())
1008 return {};
1009
1010 APSInt lhsValue = lhs.getAPSInt();
1011 APSInt rhsValue = rhs.getAPSInt();
1012 unsigned width = std::max(lhsValue.getBitWidth(), rhsValue.getBitWidth());
1013 lhsValue = lhsValue.extend(width);
1014 rhsValue = rhsValue.extend(width);
1015
1016 APSInt result(evaluate(lhsValue, rhsValue), /*isUnsigned=*/false);
1017 auto resultType = IntegerType::get(context, width, IntegerType::Signed);
1018 return IntegerAttr::get(resultType, result);
1019}
1020
1021OpFoldResult IntegerAndOp::fold(FoldAdaptor adaptor) {
1023 getContext(), adaptor.getLhs(), adaptor.getRhs(),
1024 [](const APInt &lhs, const APInt &rhs) { return lhs & rhs; });
1025}
1026
1027OpFoldResult IntegerOrOp::fold(FoldAdaptor adaptor) {
1029 getContext(), adaptor.getLhs(), adaptor.getRhs(),
1030 [](const APInt &lhs, const APInt &rhs) { return lhs | rhs; });
1031}
1032
1033OpFoldResult IntegerNotOp::fold(FoldAdaptor adaptor) {
1034 auto input = dyn_cast_or_null<IntegerAttr>(adaptor.getInput());
1035 if (!input || !input.getType().isSignedInteger())
1036 return {};
1037
1038 APSInt result(~input.getAPSInt(), /*isUnsigned=*/false);
1039 auto resultType =
1040 IntegerType::get(getContext(), result.getBitWidth(), IntegerType::Signed);
1041 return IntegerAttr::get(resultType, result);
1042}
1043
1044//===----------------------------------------------------------------------===//
1045// Unary Operators
1046//===----------------------------------------------------------------------===//
1047
1048OpFoldResult SizeOfIntrinsicOp::fold(FoldAdaptor) {
1049 auto base = getInput().getType();
1050 auto w = getBitWidth(base);
1051 if (w)
1052 return getIntAttr(getType(), APInt(32, *w));
1053 return {};
1054}
1055
1056OpFoldResult IsXIntrinsicOp::fold(FoldAdaptor adaptor) {
1057 // No constant can be 'x' by definition.
1058 if (auto cst = getConstant(adaptor.getArg()))
1059 return getIntAttr(getType(), APInt(1, 0));
1060 return {};
1061}
1062
1063OpFoldResult AsSIntPrimOp::fold(FoldAdaptor adaptor) {
1064 // No effect.
1065 if (areAnonymousTypesEquivalent(getInput().getType(), getType()))
1066 return getInput();
1067
1068 // Be careful to only fold the cast into the constant if the size is known.
1069 // Otherwise width inference may produce differently-sized constants if the
1070 // sign changes.
1071 if (getType().base().hasWidth())
1072 if (auto cst = getConstant(adaptor.getInput()))
1073 return getIntAttr(getType(), *cst);
1074
1075 return {};
1076}
1077
1078void AsSIntPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1079 MLIRContext *context) {
1080 results.insert<patterns::StoUtoS>(context);
1081}
1082
1083OpFoldResult AsUIntPrimOp::fold(FoldAdaptor adaptor) {
1084 // No effect.
1085 if (areAnonymousTypesEquivalent(getInput().getType(), getType()))
1086 return getInput();
1087
1088 // Be careful to only fold the cast into the constant if the size is known.
1089 // Otherwise width inference may produce differently-sized constants if the
1090 // sign changes.
1091 if (getType().base().hasWidth())
1092 if (auto cst = getConstant(adaptor.getInput()))
1093 return getIntAttr(getType(), *cst);
1094
1095 return {};
1096}
1097
1098void AsUIntPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1099 MLIRContext *context) {
1100 results.insert<patterns::UtoStoU>(context);
1101}
1102
1103OpFoldResult AsAsyncResetPrimOp::fold(FoldAdaptor adaptor) {
1104 // No effect.
1105 if (getInput().getType() == getType())
1106 return getInput();
1107
1108 // Constant fold.
1109 if (auto cst = getConstant(adaptor.getInput()))
1110 return BoolAttr::get(getContext(), cst->getBoolValue());
1111
1112 return {};
1113}
1114
1115OpFoldResult AsResetPrimOp::fold(FoldAdaptor adaptor) {
1116 if (auto cst = getConstant(adaptor.getInput()))
1117 return BoolAttr::get(getContext(), cst->getBoolValue());
1118 return {};
1119}
1120
1121OpFoldResult AsClockPrimOp::fold(FoldAdaptor adaptor) {
1122 // No effect.
1123 if (getInput().getType() == getType())
1124 return getInput();
1125
1126 // Constant fold.
1127 if (auto cst = getConstant(adaptor.getInput()))
1128 return BoolAttr::get(getContext(), cst->getBoolValue());
1129
1130 return {};
1131}
1132
1133OpFoldResult CvtPrimOp::fold(FoldAdaptor adaptor) {
1134 if (!hasKnownWidthIntTypes(*this))
1135 return {};
1136
1137 // Signed to signed is a noop, unsigned operands prepend a zero bit.
1138 if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(),
1139 getType().base().getWidthOrSentinel()))
1140 return getIntAttr(getType(), *cst);
1141
1142 return {};
1143}
1144
1145void CvtPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1146 MLIRContext *context) {
1147 results.insert<patterns::CVTSigned, patterns::CVTUnSigned>(context);
1148}
1149
1150OpFoldResult NegPrimOp::fold(FoldAdaptor adaptor) {
1151 if (!hasKnownWidthIntTypes(*this))
1152 return {};
1153
1154 // FIRRTL negate always adds a bit.
1155 // -x ---> 0-sext(x) or 0-zext(x)
1156 if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(),
1157 getType().base().getWidthOrSentinel()))
1158 return getIntAttr(getType(), APInt((*cst).getBitWidth(), 0) - *cst);
1159
1160 return {};
1161}
1162
1163OpFoldResult NotPrimOp::fold(FoldAdaptor adaptor) {
1164 if (!hasKnownWidthIntTypes(*this))
1165 return {};
1166
1167 if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(),
1168 getType().base().getWidthOrSentinel()))
1169 return getIntAttr(getType(), ~*cst);
1170
1171 return {};
1172}
1173
1174void NotPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1175 MLIRContext *context) {
1176 results.insert<patterns::NotNot, patterns::NotEq, patterns::NotNeq,
1177 patterns::NotLeq, patterns::NotLt, patterns::NotGeq,
1178 patterns::NotGt>(context);
1179}
1180
1181class ReductionCat : public mlir::RewritePattern {
1182public:
1183 ReductionCat(MLIRContext *context, llvm::StringLiteral opName)
1184 : RewritePattern(opName, 0, context) {}
1185
1186 /// Handle a constant operand in the cat operation.
1187 /// Returns true if the entire reduction can be replaced with a constant.
1188 /// May add non-zero constants to the remaining operands list.
1189 virtual bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op,
1190 ConstantOp constantOp,
1191 SmallVectorImpl<Value> &remaining) const = 0;
1192
1193 /// Return the unit value for this reduction operation:
1194 virtual bool getIdentityValue() const = 0;
1195
1196 LogicalResult
1197 matchAndRewrite(Operation *op,
1198 mlir::PatternRewriter &rewriter) const override {
1199 // Check if the operand is a cat operation
1200 auto catOp = op->getOperand(0).getDefiningOp<CatPrimOp>();
1201 if (!catOp)
1202 return failure();
1203
1204 SmallVector<Value> nonConstantOperands;
1205
1206 // Process each operand of the cat operation
1207 for (auto operand : catOp.getInputs()) {
1208 if (auto constantOp = operand.getDefiningOp<ConstantOp>()) {
1209 // Handle constant operands - may short-circuit the entire operation
1210 if (handleConstant(rewriter, op, constantOp, nonConstantOperands))
1211 return success();
1212 } else {
1213 // Keep non-constant operands for further processing
1214 nonConstantOperands.push_back(operand);
1215 }
1216 }
1217
1218 // If no operands remain, replace with identity value
1219 if (nonConstantOperands.empty()) {
1221 rewriter, op, cast<IntType>(op->getResult(0).getType()),
1222 APInt(1, getIdentityValue()));
1223 return success();
1224 }
1225
1226 // If only one operand remains, apply reduction directly to it
1227 if (nonConstantOperands.size() == 1) {
1228 rewriter.modifyOpInPlace(
1229 op, [&] { op->setOperand(0, nonConstantOperands.front()); });
1230 return success();
1231 }
1232
1233 // Multiple operands remain - optimize only when cat has a single use.
1234 if (catOp->hasOneUse() &&
1235 nonConstantOperands.size() < catOp->getNumOperands()) {
1237 nonConstantOperands);
1238 return success();
1239 }
1240 return failure();
1241 }
1242};
1243
1244class OrRCat : public ReductionCat {
1245public:
1246 OrRCat(MLIRContext *context)
1247 : ReductionCat(context, OrRPrimOp::getOperationName()) {}
1248 bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op,
1249 ConstantOp value,
1250 SmallVectorImpl<Value> &remaining) const override {
1251 if (value.getValue().isZero())
1252 return false;
1253
1255 rewriter, op, cast<IntType>(op->getResult(0).getType()),
1256 llvm::APInt(1, 1));
1257 return true;
1258 }
1259 bool getIdentityValue() const override { return false; }
1260};
1261
1262class AndRCat : public ReductionCat {
1263public:
1264 AndRCat(MLIRContext *context)
1265 : ReductionCat(context, AndRPrimOp::getOperationName()) {}
1266 bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op,
1267 ConstantOp value,
1268 SmallVectorImpl<Value> &remaining) const override {
1269 if (value.getValue().isAllOnes())
1270 return false;
1271
1273 rewriter, op, cast<IntType>(op->getResult(0).getType()),
1274 llvm::APInt(1, 0));
1275 return true;
1276 }
1277 bool getIdentityValue() const override { return true; }
1278};
1279
1280class XorRCat : public ReductionCat {
1281public:
1282 XorRCat(MLIRContext *context)
1283 : ReductionCat(context, XorRPrimOp::getOperationName()) {}
1284 bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op,
1285 ConstantOp value,
1286 SmallVectorImpl<Value> &remaining) const override {
1287 if (value.getValue().isZero())
1288 return false;
1289 remaining.push_back(value);
1290 return false;
1291 }
1292 bool getIdentityValue() const override { return false; }
1293};
1294
1295OpFoldResult AndRPrimOp::fold(FoldAdaptor adaptor) {
1296 if (!hasKnownWidthIntTypes(*this))
1297 return {};
1298
1299 if (getInput().getType().getBitWidthOrSentinel() == 0)
1300 return getIntAttr(getType(), APInt(1, 1));
1301
1302 // x == -1
1303 if (auto cst = getConstant(adaptor.getInput()))
1304 return getIntAttr(getType(), APInt(1, cst->isAllOnes()));
1305
1306 // one bit is identity. Only applies to UInt since we can't make a cast
1307 // here.
1308 if (isUInt1(getInput().getType()))
1309 return getInput();
1310
1311 return {};
1312}
1313
1314void AndRPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1315 MLIRContext *context) {
1316 results.insert<patterns::AndRasSInt, patterns::AndRasUInt, patterns::AndRPadU,
1317 patterns::AndRPadS, patterns::AndRCatAndR_left,
1318 patterns::AndRCatAndR_right, AndRCat>(context);
1319}
1320
1321OpFoldResult OrRPrimOp::fold(FoldAdaptor adaptor) {
1322 if (!hasKnownWidthIntTypes(*this))
1323 return {};
1324
1325 if (getInput().getType().getBitWidthOrSentinel() == 0)
1326 return getIntAttr(getType(), APInt(1, 0));
1327
1328 // x != 0
1329 if (auto cst = getConstant(adaptor.getInput()))
1330 return getIntAttr(getType(), APInt(1, !cst->isZero()));
1331
1332 // one bit is identity. Only applies to UInt since we can't make a cast
1333 // here.
1334 if (isUInt1(getInput().getType()))
1335 return getInput();
1336
1337 return {};
1338}
1339
1340void OrRPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1341 MLIRContext *context) {
1342 results.insert<patterns::OrRasSInt, patterns::OrRasUInt, patterns::OrRPadU,
1343 patterns::OrRCatOrR_left, patterns::OrRCatOrR_right, OrRCat>(
1344 context);
1345}
1346
1347OpFoldResult XorRPrimOp::fold(FoldAdaptor adaptor) {
1348 if (!hasKnownWidthIntTypes(*this))
1349 return {};
1350
1351 if (getInput().getType().getBitWidthOrSentinel() == 0)
1352 return getIntAttr(getType(), APInt(1, 0));
1353
1354 // popcount(x) & 1
1355 if (auto cst = getConstant(adaptor.getInput()))
1356 return getIntAttr(getType(), APInt(1, cst->popcount() & 1));
1357
1358 // one bit is identity. Only applies to UInt since we can't make a cast here.
1359 if (isUInt1(getInput().getType()))
1360 return getInput();
1361
1362 return {};
1363}
1364
1365void XorRPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1366 MLIRContext *context) {
1367 results
1368 .insert<patterns::XorRasSInt, patterns::XorRasUInt, patterns::XorRPadU,
1369 patterns::XorRCatXorR_left, patterns::XorRCatXorR_right, XorRCat>(
1370 context);
1371}
1372
1373//===----------------------------------------------------------------------===//
1374// Other Operators
1375//===----------------------------------------------------------------------===//
1376
1377OpFoldResult CatPrimOp::fold(FoldAdaptor adaptor) {
1378 auto inputs = getInputs();
1379 auto inputAdaptors = adaptor.getInputs();
1380
1381 // If no inputs, return 0-bit value
1382 if (inputs.empty())
1383 return getIntZerosAttr(getType());
1384
1385 // If single input and same type, return it
1386 if (inputs.size() == 1 && inputs[0].getType() == getType())
1387 return inputs[0];
1388
1389 // Make sure it's safe to fold.
1390 if (!hasKnownWidthIntTypes(*this))
1391 return {};
1392
1393 // Filter out zero-width operands
1394 SmallVector<Value> nonZeroInputs;
1395 SmallVector<Attribute> nonZeroAttributes;
1396 bool allConstant = true;
1397 for (auto [input, attr] : llvm::zip(inputs, inputAdaptors)) {
1398 auto inputType = type_cast<IntType>(input.getType());
1399 if (inputType.getBitWidthOrSentinel() != 0) {
1400 nonZeroInputs.push_back(input);
1401 if (!attr)
1402 allConstant = false;
1403 if (nonZeroInputs.size() > 1 && !allConstant)
1404 return {};
1405 }
1406 }
1407
1408 // If all inputs were zero-width, return 0-bit value
1409 if (nonZeroInputs.empty())
1410 return getIntZerosAttr(getType());
1411
1412 // If only one non-zero input and it has the same type as result, return it
1413 if (nonZeroInputs.size() == 1 && nonZeroInputs[0].getType() == getType())
1414 return nonZeroInputs[0];
1415
1416 if (!hasKnownWidthIntTypes(*this))
1417 return {};
1418
1419 // Constant fold cat - concatenate all constant operands
1420 SmallVector<APInt> constants;
1421 for (auto inputAdaptor : inputAdaptors) {
1422 if (auto cst = getConstant(inputAdaptor))
1423 constants.push_back(*cst);
1424 else
1425 return {}; // Not all operands are constant
1426 }
1427
1428 assert(!constants.empty());
1429 // Concatenate all constants from left to right
1430 APInt result = constants[0];
1431 for (size_t i = 1; i < constants.size(); ++i)
1432 result = result.concat(constants[i]);
1433
1434 return getIntAttr(getType(), result);
1435}
1436
1437void DShlPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1438 MLIRContext *context) {
1439 results.insert<patterns::DShlOfConstant>(context);
1440}
1441
1442void DShrPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1443 MLIRContext *context) {
1444 results.insert<patterns::DShrOfConstant>(context);
1445}
1446
1447namespace {
1448
1449/// Canonicalize a cat tree into single cat operation.
1450class FlattenCat : public mlir::OpRewritePattern<CatPrimOp> {
1451public:
1452 using OpRewritePattern::OpRewritePattern;
1453
1454 LogicalResult
1455 matchAndRewrite(CatPrimOp cat,
1456 mlir::PatternRewriter &rewriter) const override {
1457 if (!hasKnownWidthIntTypes(cat) ||
1458 cat.getType().getBitWidthOrSentinel() == 0)
1459 return failure();
1460
1461 // If this is not a root of concat tree, skip.
1462 if (cat->hasOneUse() && isa<CatPrimOp>(*cat->getUsers().begin()))
1463 return failure();
1464
1465 // Try flattening the cat tree.
1466 SmallVector<Value> operands;
1467 SmallVector<Value> worklist;
1468 auto pushOperands = [&worklist](CatPrimOp op) {
1469 for (auto operand : llvm::reverse(op.getInputs()))
1470 worklist.push_back(operand);
1471 };
1472 pushOperands(cat);
1473 bool hasSigned = false, hasUnsigned = false;
1474 while (!worklist.empty()) {
1475 auto value = worklist.pop_back_val();
1476 auto catOp = value.getDefiningOp<CatPrimOp>();
1477 if (!catOp) {
1478 operands.push_back(value);
1479 (type_isa<UIntType>(value.getType()) ? hasUnsigned : hasSigned) = true;
1480 continue;
1481 }
1482
1483 pushOperands(catOp);
1484 }
1485
1486 // Helper function to cast signed values to unsigned. CatPrimOp converts
1487 // signed values to unsigned so we need to do it explicitly here.
1488 auto castToUIntIfSigned = [&](Value value) -> Value {
1489 if (type_isa<UIntType>(value.getType()))
1490 return value;
1491 return AsUIntPrimOp::create(rewriter, value.getLoc(), value);
1492 };
1493
1494 assert(operands.size() >= 1 && "zero width cast must be rejected");
1495
1496 if (operands.size() == 1) {
1497 rewriter.replaceOp(cat, castToUIntIfSigned(operands[0]));
1498 return success();
1499 }
1500
1501 if (operands.size() == cat->getNumOperands())
1502 return failure();
1503
1504 // If types are mixed, cast all operands to unsigned.
1505 if (hasSigned && hasUnsigned)
1506 for (auto &operand : operands)
1507 operand = castToUIntIfSigned(operand);
1508
1509 replaceOpWithNewOpAndCopyName<CatPrimOp>(rewriter, cat, cat.getType(),
1510 operands);
1511 return success();
1512 }
1513};
1514
1515// Fold successive constants cat(x, y, 1, 10, z) -> cat(x, y, 110, z)
1516class CatOfConstant : public mlir::OpRewritePattern<CatPrimOp> {
1517public:
1518 using OpRewritePattern::OpRewritePattern;
1519
1520 LogicalResult
1521 matchAndRewrite(CatPrimOp cat,
1522 mlir::PatternRewriter &rewriter) const override {
1523 if (!hasKnownWidthIntTypes(cat))
1524 return failure();
1525
1526 SmallVector<Value> operands;
1527
1528 for (size_t i = 0; i < cat->getNumOperands(); ++i) {
1529 auto cst = cat.getInputs()[i].getDefiningOp<ConstantOp>();
1530 if (!cst) {
1531 operands.push_back(cat.getInputs()[i]);
1532 continue;
1533 }
1534 APSInt value = cst.getValue();
1535 size_t j = i + 1;
1536 for (; j < cat->getNumOperands(); ++j) {
1537 auto nextCst = cat.getInputs()[j].getDefiningOp<ConstantOp>();
1538 if (!nextCst)
1539 break;
1540 value = value.concat(nextCst.getValue());
1541 }
1542
1543 if (j == i + 1) {
1544 // Not folded.
1545 operands.push_back(cst);
1546 } else {
1547 // Folded.
1548 operands.push_back(ConstantOp::create(rewriter, cat.getLoc(), value));
1549 }
1550
1551 i = j - 1;
1552 }
1553
1554 if (operands.size() == cat->getNumOperands())
1555 return failure();
1556
1557 replaceOpWithNewOpAndCopyName<CatPrimOp>(rewriter, cat, cat.getType(),
1558 operands);
1559
1560 return success();
1561 }
1562};
1563
1564} // namespace
1565
1566void CatPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1567 MLIRContext *context) {
1568 results.insert<patterns::CatBitsBits, patterns::CatDoubleConst,
1569 patterns::CatCast, FlattenCat, CatOfConstant>(context);
1570}
1571
1572//===----------------------------------------------------------------------===//
1573// StringConcatOp
1574//===----------------------------------------------------------------------===//
1575
1576OpFoldResult StringConcatOp::fold(FoldAdaptor adaptor) {
1577 // Fold single-operand concat to just the operand.
1578 if (getInputs().size() == 1)
1579 return getInputs()[0];
1580
1581 // Check if all operands are constant strings before accumulating.
1582 if (!llvm::all_of(adaptor.getInputs(), [](Attribute operand) {
1583 return isa_and_nonnull<StringAttr>(operand);
1584 }))
1585 return {};
1586
1587 // All operands are constant strings, concatenate them.
1588 SmallString<64> result;
1589 for (auto operand : adaptor.getInputs())
1590 result += cast<StringAttr>(operand).getValue();
1591
1592 return StringAttr::get(getContext(), result);
1593}
1594
1595namespace {
1596/// Flatten nested string.concat operations into a single concat.
1597/// string.concat(a, string.concat(b, c), d) -> string.concat(a, b, c, d)
1598class FlattenStringConcat : public mlir::OpRewritePattern<StringConcatOp> {
1599public:
1600 using OpRewritePattern::OpRewritePattern;
1601
1602 LogicalResult
1603 matchAndRewrite(StringConcatOp concat,
1604 mlir::PatternRewriter &rewriter) const override {
1605
1606 // Check if any operands are nested concats with a single use. Only inline
1607 // single-use nested concats to avoid fighting with DCE.
1608 bool hasNestedConcat = llvm::any_of(concat.getInputs(), [](Value operand) {
1609 auto nestedConcat = operand.getDefiningOp<StringConcatOp>();
1610 return nestedConcat && operand.hasOneUse();
1611 });
1612
1613 if (!hasNestedConcat)
1614 return failure();
1615
1616 // Flatten nested concats that have a single use.
1617 SmallVector<Value> flatOperands;
1618 for (auto input : concat.getInputs()) {
1619 if (auto nestedConcat = input.getDefiningOp<StringConcatOp>();
1620 nestedConcat && input.hasOneUse())
1621 llvm::append_range(flatOperands, nestedConcat.getInputs());
1622 else
1623 flatOperands.push_back(input);
1624 }
1625
1626 rewriter.modifyOpInPlace(concat,
1627 [&]() { concat->setOperands(flatOperands); });
1628 return success();
1629 }
1630};
1631
1632/// Merge consecutive constant strings in a concat and remove empty strings.
1633/// string.concat("a", "b", x, "", "c", "d") -> string.concat("ab", x, "cd")
1634class MergeAdjacentStringConstants
1635 : public mlir::OpRewritePattern<StringConcatOp> {
1636public:
1637 using OpRewritePattern::OpRewritePattern;
1638
1639 LogicalResult
1640 matchAndRewrite(StringConcatOp concat,
1641 mlir::PatternRewriter &rewriter) const override {
1642
1643 SmallVector<Value> newOperands;
1644 SmallString<64> accumulatedLit;
1645 SmallVector<StringConstantOp> accumulatedOps;
1646 bool changed = false;
1647
1648 auto flushLiterals = [&]() {
1649 if (accumulatedOps.empty())
1650 return;
1651
1652 // If only one literal, reuse it.
1653 if (accumulatedOps.size() == 1) {
1654 newOperands.push_back(accumulatedOps[0]);
1655 } else {
1656 // Multiple literals - merge them.
1657 auto newLit = rewriter.createOrFold<StringConstantOp>(
1658 concat.getLoc(), StringAttr::get(getContext(), accumulatedLit));
1659 newOperands.push_back(newLit);
1660 changed = true;
1661 }
1662 accumulatedLit.clear();
1663 accumulatedOps.clear();
1664 };
1665
1666 for (auto operand : concat.getInputs()) {
1667 if (auto litOp = operand.getDefiningOp<StringConstantOp>()) {
1668 // Skip empty strings.
1669 if (litOp.getValue().empty()) {
1670 changed = true;
1671 continue;
1672 }
1673 accumulatedLit += litOp.getValue();
1674 accumulatedOps.push_back(litOp);
1675 } else {
1676 flushLiterals();
1677 newOperands.push_back(operand);
1678 }
1679 }
1680
1681 // Flush any remaining literals.
1682 flushLiterals();
1683
1684 if (!changed)
1685 return failure();
1686
1687 // If no operands remain, replace with empty string.
1688 if (newOperands.empty())
1689 return rewriter.replaceOpWithNewOp<StringConstantOp>(
1690 concat, StringAttr::get(getContext(), "")),
1691 success();
1692
1693 // Single-operand case is handled by the folder.
1694 rewriter.modifyOpInPlace(concat,
1695 [&]() { concat->setOperands(newOperands); });
1696 return success();
1697 }
1698};
1699
1700} // namespace
1701
1702void StringConcatOp::getCanonicalizationPatterns(RewritePatternSet &results,
1703 MLIRContext *context) {
1704 results.insert<FlattenStringConcat, MergeAdjacentStringConstants>(context);
1705}
1706
1707//===----------------------------------------------------------------------===//
1708// PropEqOp
1709//===----------------------------------------------------------------------===//
1710
1711OpFoldResult PropEqOp::fold(FoldAdaptor adaptor) {
1712 auto lhsAttr = adaptor.getLhs();
1713 auto rhsAttr = adaptor.getRhs();
1714 if (!lhsAttr || !rhsAttr)
1715 return {};
1716
1717 return BoolAttr::get(getContext(), lhsAttr == rhsAttr);
1718}
1719
1720//===----------------------------------------------------------------------===//
1721// Boolean Property Ops
1722//===----------------------------------------------------------------------===//
1723
1724// Helper to extract the bool value from a BoolAttr.
1725static std::optional<bool> getBoolValue(Attribute attr) {
1726 if (auto boolAttr = dyn_cast_or_null<BoolAttr>(attr))
1727 return boolAttr.getValue();
1728 return std::nullopt;
1729}
1730
1731OpFoldResult BoolAndOp::fold(FoldAdaptor adaptor) {
1732 auto lhs = getBoolValue(adaptor.getLhs());
1733 auto rhs = getBoolValue(adaptor.getRhs());
1734 if (lhs && rhs)
1735 return BoolAttr::get(getContext(), *lhs && *rhs);
1736 // AND with false is always false.
1737 if ((lhs && !*lhs) || (rhs && !*rhs))
1738 return BoolAttr::get(getContext(), false);
1739 // AND with true is identity.
1740 if (lhs && *lhs)
1741 return getRhs();
1742 if (rhs && *rhs)
1743 return getLhs();
1744 return {};
1745}
1746
1747OpFoldResult BoolOrOp::fold(FoldAdaptor adaptor) {
1748 auto lhs = getBoolValue(adaptor.getLhs());
1749 auto rhs = getBoolValue(adaptor.getRhs());
1750 if (lhs && rhs)
1751 return BoolAttr::get(getContext(), *lhs || *rhs);
1752 // OR with true is always true.
1753 if ((lhs && *lhs) || (rhs && *rhs))
1754 return BoolAttr::get(getContext(), true);
1755 // OR with false is identity.
1756 if (lhs && !*lhs)
1757 return getRhs();
1758 if (rhs && !*rhs)
1759 return getLhs();
1760 return {};
1761}
1762
1763OpFoldResult BoolXorOp::fold(FoldAdaptor adaptor) {
1764 auto lhs = getBoolValue(adaptor.getLhs());
1765 auto rhs = getBoolValue(adaptor.getRhs());
1766 if (lhs && rhs)
1767 return BoolAttr::get(getContext(), *lhs ^ *rhs);
1768 // XOR with false is identity.
1769 if (lhs && !*lhs)
1770 return getRhs();
1771 if (rhs && !*rhs)
1772 return getLhs();
1773 return {};
1774}
1775
1776OpFoldResult BitCastOp::fold(FoldAdaptor adaptor) {
1777 auto op = (*this);
1778 // BitCast is redundant if input and result types are same.
1779 if (op.getType() == op.getInput().getType())
1780 return op.getInput();
1781
1782 // Two consecutive BitCasts are redundant if first bitcast type is same as the
1783 // final result type.
1784 if (BitCastOp in = dyn_cast_or_null<BitCastOp>(op.getInput().getDefiningOp()))
1785 if (op.getType() == in.getInput().getType())
1786 return in.getInput();
1787
1788 return {};
1789}
1790
1791OpFoldResult BitsPrimOp::fold(FoldAdaptor adaptor) {
1792 IntType inputType = getInput().getType();
1793 IntType resultType = getType();
1794 // If we are extracting the entire input, then return it.
1795 if (inputType == getType() && resultType.hasWidth())
1796 return getInput();
1797
1798 // Constant fold.
1799 if (hasKnownWidthIntTypes(*this))
1800 if (auto cst = getConstant(adaptor.getInput()))
1801 return getIntAttr(resultType,
1802 cst->extractBits(getHi() - getLo() + 1, getLo()));
1803
1804 return {};
1805}
1806
1807struct BitsOfCat : public mlir::OpRewritePattern<BitsPrimOp> {
1808 using OpRewritePattern::OpRewritePattern;
1809
1810 LogicalResult
1811 matchAndRewrite(BitsPrimOp bits,
1812 mlir::PatternRewriter &rewriter) const override {
1813 auto cat = bits.getInput().getDefiningOp<CatPrimOp>();
1814 if (!cat)
1815 return failure();
1816 int32_t bitPos = bits.getLo();
1817 auto resultWidth = type_cast<UIntType>(bits.getType()).getWidthOrSentinel();
1818 if (resultWidth < 0)
1819 return failure();
1820 for (auto operand : llvm::reverse(cat.getInputs())) {
1821 auto operandWidth =
1822 type_cast<IntType>(operand.getType()).getWidthOrSentinel();
1823 if (operandWidth < 0)
1824 return failure();
1825 if (bitPos < operandWidth) {
1826 if (bitPos + resultWidth <= operandWidth) {
1827 auto newBits = rewriter.createOrFold<BitsPrimOp>(
1828 bits.getLoc(), operand, bitPos + resultWidth - 1, bitPos);
1829 replaceOpAndCopyName(rewriter, bits, newBits);
1830 return success();
1831 }
1832 return failure();
1833 }
1834 bitPos -= operandWidth;
1835 }
1836 return failure();
1837 }
1838};
1839
1840void BitsPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1841 MLIRContext *context) {
1842 results
1843 .insert<patterns::BitsOfBits, patterns::BitsOfMux, patterns::BitsOfAsUInt,
1844 patterns::BitsOfAnd, patterns::BitsOfPad, BitsOfCat>(context);
1845}
1846
1847/// Replace the specified operation with a 'bits' op from the specified hi/lo
1848/// bits. Insert a cast to handle the case where the original operation
1849/// returned a signed integer.
1850static void replaceWithBits(Operation *op, Value value, unsigned hiBit,
1851 unsigned loBit, PatternRewriter &rewriter) {
1852 auto resType = type_cast<IntType>(op->getResult(0).getType());
1853 if (type_cast<IntType>(value.getType()).getWidth() != resType.getWidth())
1854 value = BitsPrimOp::create(rewriter, op->getLoc(), value, hiBit, loBit);
1855
1856 if (resType.isSigned() && !type_cast<IntType>(value.getType()).isSigned()) {
1857 value = rewriter.createOrFold<AsSIntPrimOp>(op->getLoc(), resType, value);
1858 } else if (resType.isUnsigned() &&
1859 !type_cast<IntType>(value.getType()).isUnsigned()) {
1860 value = rewriter.createOrFold<AsUIntPrimOp>(op->getLoc(), resType, value);
1861 }
1862 rewriter.replaceOp(op, value);
1863}
1864
1865template <typename OpTy>
1866static OpFoldResult foldMux(OpTy op, typename OpTy::FoldAdaptor adaptor) {
1867 // mux : UInt<0> -> 0
1868 if (op.getType().getBitWidthOrSentinel() == 0)
1869 return getIntAttr(op.getType(),
1870 APInt(0, 0, op.getType().isSignedInteger()));
1871
1872 // mux(cond, x, x) -> x
1873 if (op.getHigh() == op.getLow() && op.getHigh().getType() == op.getType())
1874 return op.getHigh();
1875
1876 // The following folds require that the result has a known width. Otherwise
1877 // the mux requires an additional padding operation to be inserted, which is
1878 // not possible in a fold.
1879 if (op.getType().hasUninferredWidth())
1880 return {};
1881
1882 // mux(0/1, x, y) -> x or y
1883 if (auto cond = getConstant(adaptor.getSel())) {
1884 if (cond->isZero() && op.getLow().getType() == op.getType())
1885 return op.getLow();
1886 if (!cond->isZero() && op.getHigh().getType() == op.getType())
1887 return op.getHigh();
1888 }
1889
1890 // mux(cond, x, cst)
1891 if (auto lowCst = getConstant(adaptor.getLow())) {
1892 // mux(cond, c1, c2)
1893 if (auto highCst = getConstant(adaptor.getHigh())) {
1894 // mux(cond, cst, cst) -> cst
1895 if (highCst->getBitWidth() == lowCst->getBitWidth() &&
1896 *highCst == *lowCst)
1897 // Ensure that this has an integer representation. This specifically
1898 // avoids problems with clocks.
1899 if (auto intType = type_dyn_cast<IntType>(op.getType()))
1900 if (intType.hasWidth() &&
1901 (unsigned)intType.getWidthOrSentinel() == highCst->getBitWidth())
1902 return getIntAttr(op.getType(), *highCst);
1903 // mux(cond, 1, 0) -> cond
1904 if (highCst->isOne() && lowCst->isZero() &&
1905 op.getType() == op.getSel().getType())
1906 return op.getSel();
1907
1908 // TODO: x ? ~0 : 0 -> sext(x)
1909 // TODO: "x ? c1 : c2" -> many tricks
1910 }
1911 // TODO: "x ? a : 0" -> sext(x) & a
1912 }
1913
1914 // TODO: "x ? c1 : y" -> "~x ? y : c1"
1915 return {};
1916}
1917
1918OpFoldResult MuxPrimOp::fold(FoldAdaptor adaptor) {
1919 return foldMux(*this, adaptor);
1920}
1921
1922OpFoldResult Mux2CellIntrinsicOp::fold(FoldAdaptor adaptor) {
1923 return foldMux(*this, adaptor);
1924}
1925
1926OpFoldResult Mux4CellIntrinsicOp::fold(FoldAdaptor adaptor) { return {}; }
1927
1928namespace {
1929
1930// If the mux has a known output width, pad the operands up to this width.
1931// Most folds on mux require that folded operands are of the same width as
1932// the mux itself.
1933class MuxPad : public mlir::OpRewritePattern<MuxPrimOp> {
1934public:
1935 using OpRewritePattern::OpRewritePattern;
1936
1937 LogicalResult
1938 matchAndRewrite(MuxPrimOp mux,
1939 mlir::PatternRewriter &rewriter) const override {
1940 auto width = mux.getType().getBitWidthOrSentinel();
1941 if (width < 0)
1942 return failure();
1943
1944 auto pad = [&](Value input) -> Value {
1945 auto inputWidth =
1946 type_cast<FIRRTLBaseType>(input.getType()).getBitWidthOrSentinel();
1947 if (inputWidth < 0 || width == inputWidth)
1948 return input;
1949 return PadPrimOp::create(rewriter, mux.getLoc(), mux.getType(), input,
1950 width)
1951 .getResult();
1952 };
1953
1954 auto newHigh = pad(mux.getHigh());
1955 auto newLow = pad(mux.getLow());
1956 if (newHigh == mux.getHigh() && newLow == mux.getLow())
1957 return failure();
1958
1960 rewriter, mux, mux.getType(), ValueRange{mux.getSel(), newHigh, newLow},
1961 mux->getAttrs());
1962 return success();
1963 }
1964};
1965
1966// Find muxes which have conditions dominated by other muxes with the same
1967// condition.
1968class MuxSharedCond : public mlir::OpRewritePattern<MuxPrimOp> {
1969public:
1970 using OpRewritePattern::OpRewritePattern;
1971
1972 static const int depthLimit = 5;
1973
1974 Value updateOrClone(MuxPrimOp mux, Value high, Value low,
1975 mlir::PatternRewriter &rewriter,
1976 bool updateInPlace) const {
1977 if (updateInPlace) {
1978 rewriter.modifyOpInPlace(mux, [&] {
1979 mux.setOperand(1, high);
1980 mux.setOperand(2, low);
1981 });
1982 return {};
1983 }
1984 rewriter.setInsertionPointAfter(mux);
1985 return MuxPrimOp::create(rewriter, mux.getLoc(), mux.getType(),
1986 ValueRange{mux.getSel(), high, low})
1987 .getResult();
1988 }
1989
1990 // Walk a dependent mux tree assuming the condition cond is true.
1991 Value tryCondTrue(Value op, Value cond, mlir::PatternRewriter &rewriter,
1992 bool updateInPlace, int limit) const {
1993 MuxPrimOp mux = op.getDefiningOp<MuxPrimOp>();
1994 if (!mux)
1995 return {};
1996 if (mux.getSel() == cond)
1997 return mux.getHigh();
1998 if (limit > depthLimit)
1999 return {};
2000 updateInPlace &= mux->hasOneUse();
2001
2002 if (Value v = tryCondTrue(mux.getHigh(), cond, rewriter, updateInPlace,
2003 limit + 1))
2004 return updateOrClone(mux, v, mux.getLow(), rewriter, updateInPlace);
2005
2006 if (Value v =
2007 tryCondTrue(mux.getLow(), cond, rewriter, updateInPlace, limit + 1))
2008 return updateOrClone(mux, mux.getHigh(), v, rewriter, updateInPlace);
2009 return {};
2010 }
2011
2012 // Walk a dependent mux tree assuming the condition cond is false.
2013 Value tryCondFalse(Value op, Value cond, mlir::PatternRewriter &rewriter,
2014 bool updateInPlace, int limit) const {
2015 MuxPrimOp mux = op.getDefiningOp<MuxPrimOp>();
2016 if (!mux)
2017 return {};
2018 if (mux.getSel() == cond)
2019 return mux.getLow();
2020 if (limit > depthLimit)
2021 return {};
2022 updateInPlace &= mux->hasOneUse();
2023
2024 if (Value v = tryCondFalse(mux.getHigh(), cond, rewriter, updateInPlace,
2025 limit + 1))
2026 return updateOrClone(mux, v, mux.getLow(), rewriter, updateInPlace);
2027
2028 if (Value v = tryCondFalse(mux.getLow(), cond, rewriter, updateInPlace,
2029 limit + 1))
2030 return updateOrClone(mux, mux.getHigh(), v, rewriter, updateInPlace);
2031
2032 return {};
2033 }
2034
2035 LogicalResult
2036 matchAndRewrite(MuxPrimOp mux,
2037 mlir::PatternRewriter &rewriter) const override {
2038 auto width = mux.getType().getBitWidthOrSentinel();
2039 if (width < 0)
2040 return failure();
2041
2042 if (Value v = tryCondTrue(mux.getHigh(), mux.getSel(), rewriter, true, 0)) {
2043 rewriter.modifyOpInPlace(mux, [&] { mux.setOperand(1, v); });
2044 return success();
2045 }
2046
2047 if (Value v = tryCondFalse(mux.getLow(), mux.getSel(), rewriter, true, 0)) {
2048 rewriter.modifyOpInPlace(mux, [&] { mux.setOperand(2, v); });
2049 return success();
2050 }
2051
2052 return failure();
2053 }
2054};
2055} // namespace
2056
2057void MuxPrimOp::getCanonicalizationPatterns(RewritePatternSet &results,
2058 MLIRContext *context) {
2059 results
2060 .add<MuxPad, MuxSharedCond, patterns::MuxEQOperands,
2061 patterns::MuxEQOperandsSwapped, patterns::MuxNEQ, patterns::MuxNot,
2062 patterns::MuxSameTrue, patterns::MuxSameFalse,
2063 patterns::NarrowMuxLHS, patterns::NarrowMuxRHS, patterns::MuxPadSel>(
2064 context);
2065}
2066
2067void Mux2CellIntrinsicOp::getCanonicalizationPatterns(
2068 RewritePatternSet &results, MLIRContext *context) {
2069 results.add<patterns::Mux2PadSel>(context);
2070}
2071
2072void Mux4CellIntrinsicOp::getCanonicalizationPatterns(
2073 RewritePatternSet &results, MLIRContext *context) {
2074 results.add<patterns::Mux4PadSel>(context);
2075}
2076
2077OpFoldResult PadPrimOp::fold(FoldAdaptor adaptor) {
2078 auto input = this->getInput();
2079
2080 // pad(x) -> x if the width doesn't change.
2081 if (input.getType() == getType())
2082 return input;
2083
2084 // Need to know the input width.
2085 auto inputType = input.getType().base();
2086 int32_t width = inputType.getWidthOrSentinel();
2087 if (width == -1)
2088 return {};
2089
2090 // Constant fold.
2091 if (auto cst = getConstant(adaptor.getInput())) {
2092 auto destWidth = getType().base().getWidthOrSentinel();
2093 if (destWidth == -1)
2094 return {};
2095
2096 if (inputType.isSigned() && cst->getBitWidth())
2097 return getIntAttr(getType(), cst->sext(destWidth));
2098 return getIntAttr(getType(), cst->zext(destWidth));
2099 }
2100
2101 return {};
2102}
2103
2104OpFoldResult ShlPrimOp::fold(FoldAdaptor adaptor) {
2105 auto input = this->getInput();
2106 IntType inputType = input.getType();
2107 int shiftAmount = getAmount();
2108
2109 // shl(x, 0) -> x
2110 if (shiftAmount == 0)
2111 return input;
2112
2113 // Constant fold.
2114 if (auto cst = getConstant(adaptor.getInput())) {
2115 auto inputWidth = inputType.getWidthOrSentinel();
2116 if (inputWidth != -1) {
2117 auto resultWidth = inputWidth + shiftAmount;
2118 shiftAmount = std::min(shiftAmount, resultWidth);
2119 return getIntAttr(getType(), cst->zext(resultWidth).shl(shiftAmount));
2120 }
2121 }
2122 return {};
2123}
2124
2125OpFoldResult ShrPrimOp::fold(FoldAdaptor adaptor) {
2126 auto input = this->getInput();
2127 IntType inputType = input.getType();
2128 int shiftAmount = getAmount();
2129 auto inputWidth = inputType.getWidthOrSentinel();
2130
2131 // shr(x, 0) -> x
2132 // Once the shr width changes, do this: shiftAmount == 0 &&
2133 // (!inputType.isSigned() || inputWidth > 0)
2134 if (shiftAmount == 0 && inputWidth > 0)
2135 return input;
2136
2137 if (inputWidth == -1)
2138 return {};
2139 if (inputWidth == 0)
2140 return getIntZerosAttr(getType());
2141
2142 // shr(x, cst) where cst is all of x's bits and x is unsigned is 0.
2143 // If x is signed, it is the sign bit.
2144 if (shiftAmount >= inputWidth && inputType.isUnsigned())
2145 return getIntAttr(getType(), APInt(0, 0, false));
2146
2147 // Constant fold.
2148 if (auto cst = getConstant(adaptor.getInput())) {
2149 APInt value;
2150 if (inputType.isSigned())
2151 value = cst->ashr(std::min(shiftAmount, inputWidth - 1));
2152 else
2153 value = cst->lshr(std::min(shiftAmount, inputWidth));
2154 auto resultWidth = std::max(inputWidth - shiftAmount, 1);
2155 return getIntAttr(getType(), value.trunc(resultWidth));
2156 }
2157 return {};
2158}
2159
2160LogicalResult ShrPrimOp::canonicalize(ShrPrimOp op, PatternRewriter &rewriter) {
2161 auto inputWidth = op.getInput().getType().base().getWidthOrSentinel();
2162 if (inputWidth <= 0)
2163 return failure();
2164
2165 // If we know the input width, we can canonicalize this into a BitsPrimOp.
2166 unsigned shiftAmount = op.getAmount();
2167 if (int(shiftAmount) >= inputWidth) {
2168 // shift(x, 32) => 0 when x has 32 bits. This is handled by fold().
2169 if (op.getType().base().isUnsigned())
2170 return failure();
2171
2172 // Shifting a signed value by the full width is actually taking the
2173 // sign bit. If the shift amount is greater than the input width, it
2174 // is equivalent to shifting by the input width.
2175 shiftAmount = inputWidth - 1;
2176 }
2177
2178 replaceWithBits(op, op.getInput(), inputWidth - 1, shiftAmount, rewriter);
2179 return success();
2180}
2181
2182LogicalResult HeadPrimOp::canonicalize(HeadPrimOp op,
2183 PatternRewriter &rewriter) {
2184 auto inputWidth = op.getInput().getType().base().getWidthOrSentinel();
2185 if (inputWidth <= 0)
2186 return failure();
2187
2188 // If we know the input width, we can canonicalize this into a BitsPrimOp.
2189 unsigned keepAmount = op.getAmount();
2190 if (keepAmount)
2191 replaceWithBits(op, op.getInput(), inputWidth - 1, inputWidth - keepAmount,
2192 rewriter);
2193 return success();
2194}
2195
2196OpFoldResult HeadPrimOp::fold(FoldAdaptor adaptor) {
2197 if (hasKnownWidthIntTypes(*this))
2198 if (auto cst = getConstant(adaptor.getInput())) {
2199 int shiftAmount =
2200 getInput().getType().base().getWidthOrSentinel() - getAmount();
2201 return getIntAttr(getType(), cst->lshr(shiftAmount).trunc(getAmount()));
2202 }
2203
2204 return {};
2205}
2206
2207OpFoldResult TailPrimOp::fold(FoldAdaptor adaptor) {
2208 if (hasKnownWidthIntTypes(*this))
2209 if (auto cst = getConstant(adaptor.getInput()))
2210 return getIntAttr(getType(),
2211 cst->trunc(getType().base().getWidthOrSentinel()));
2212 return {};
2213}
2214
2215LogicalResult TailPrimOp::canonicalize(TailPrimOp op,
2216 PatternRewriter &rewriter) {
2217 auto inputWidth = op.getInput().getType().base().getWidthOrSentinel();
2218 if (inputWidth <= 0)
2219 return failure();
2220
2221 // If we know the input width, we can canonicalize this into a BitsPrimOp.
2222 unsigned dropAmount = op.getAmount();
2223 if (dropAmount != unsigned(inputWidth))
2224 replaceWithBits(op, op.getInput(), inputWidth - dropAmount - 1, 0,
2225 rewriter);
2226 return success();
2227}
2228
2229void SubaccessOp::getCanonicalizationPatterns(RewritePatternSet &results,
2230 MLIRContext *context) {
2231 results.add<patterns::SubaccessOfConstant>(context);
2232}
2233
2234OpFoldResult MultibitMuxOp::fold(FoldAdaptor adaptor) {
2235 // If there is only one input, just return it.
2236 if (adaptor.getInputs().size() == 1)
2237 return getOperand(1);
2238
2239 if (auto constIndex = getConstant(adaptor.getIndex())) {
2240 auto index = constIndex->getZExtValue();
2241 if (index < getInputs().size())
2242 return getInputs()[getInputs().size() - 1 - index];
2243 }
2244
2245 return {};
2246}
2247
2248LogicalResult MultibitMuxOp::canonicalize(MultibitMuxOp op,
2249 PatternRewriter &rewriter) {
2250 // If all operands are equal, just canonicalize to it. We can add this
2251 // canonicalization as a folder but it costly to look through all inputs so it
2252 // is added here.
2253 if (llvm::all_of(op.getInputs().drop_front(), [&](auto input) {
2254 return input == op.getInputs().front();
2255 })) {
2256 replaceOpAndCopyName(rewriter, op, op.getInputs().front());
2257 return success();
2258 }
2259
2260 // If the index width is narrower than the size of inputs, drop front
2261 // elements.
2262 auto indexWidth = op.getIndex().getType().getBitWidthOrSentinel();
2263 uint64_t inputSize = op.getInputs().size();
2264 if (indexWidth >= 0 && indexWidth < 64 && 1ull << indexWidth < inputSize) {
2265 rewriter.modifyOpInPlace(op, [&]() {
2266 op.getInputsMutable().erase(0, inputSize - (1ull << indexWidth));
2267 });
2268 return success();
2269 }
2270
2271 // If the op is a vector indexing (e.g. `multbit_mux idx, a[n-1], a[n-2], ...,
2272 // a[0]`), we can fold the op into subaccess op `a[idx]`.
2273 if (auto lastSubindex = op.getInputs().back().getDefiningOp<SubindexOp>()) {
2274 if (llvm::all_of(llvm::enumerate(op.getInputs()), [&](auto e) {
2275 auto subindex = e.value().template getDefiningOp<SubindexOp>();
2276 return subindex && lastSubindex.getInput() == subindex.getInput() &&
2277 subindex.getIndex() + e.index() + 1 == op.getInputs().size();
2278 })) {
2280 rewriter, op, lastSubindex.getInput(), op.getIndex());
2281 return success();
2282 }
2283 }
2284
2285 // If the size is 2, canonicalize into a normal mux to introduce more folds.
2286 if (op.getInputs().size() != 2)
2287 return failure();
2288
2289 // TODO: Handle even when `index` doesn't have uint<1>.
2290 auto uintType = op.getIndex().getType();
2291 if (uintType.getBitWidthOrSentinel() != 1)
2292 return failure();
2293
2294 // multibit_mux(index, {lhs, rhs}) -> mux(index, lhs, rhs)
2296 rewriter, op, op.getIndex(), op.getInputs()[0], op.getInputs()[1]);
2297 return success();
2298}
2299
2300//===----------------------------------------------------------------------===//
2301// Declarations
2302//===----------------------------------------------------------------------===//
2303
2304/// Scan all the uses of the specified value, checking to see if there is
2305/// exactly one connect that has the value as its destination. This returns the
2306/// operation if found and if all the other users are "reads" from the value.
2307/// Returns null if there are no connects, or multiple connects to the value, or
2308/// if the value is involved in an `AttachOp`, or if the connect isn't matching.
2309///
2310/// Note that this will simply return the connect, which is located *anywhere*
2311/// after the definition of the value. Users of this function are likely
2312/// interested in the source side of the returned connect, the definition of
2313/// which does likely not dominate the original value.
2314MatchingConnectOp firrtl::getSingleConnectUserOf(Value value) {
2315 MatchingConnectOp connect;
2316 for (Operation *user : value.getUsers()) {
2317 // If we see an attach or aggregate sublements, just conservatively fail.
2318 if (isa<AttachOp, SubfieldOp, SubaccessOp, SubindexOp>(user))
2319 return {};
2320
2321 if (auto aConnect = dyn_cast<FConnectLike>(user))
2322 if (aConnect.getDest() == value) {
2323 auto matchingConnect = dyn_cast<MatchingConnectOp>(*aConnect);
2324 // If this is not a matching connect, a second matching connect or in a
2325 // different block, fail.
2326 if (!matchingConnect || (connect && connect != matchingConnect) ||
2327 matchingConnect->getBlock() != value.getParentBlock())
2328 return {};
2329 connect = matchingConnect;
2330 }
2331 }
2332 return connect;
2333}
2334
2335// Forward simple values through wire's and reg's.
2336static LogicalResult canonicalizeSingleSetConnect(MatchingConnectOp op,
2337 PatternRewriter &rewriter) {
2338 // While we can do this for nearly all wires, we currently limit it to simple
2339 // things.
2340 Operation *connectedDecl = op.getDest().getDefiningOp();
2341 if (!connectedDecl)
2342 return failure();
2343
2344 // Only support wire and reg for now.
2345 if (!isa<WireOp>(connectedDecl) && !isa<RegOp>(connectedDecl))
2346 return failure();
2347 if (hasDontTouch(connectedDecl) || !AnnotationSet(connectedDecl).empty() ||
2348 !hasDroppableName(connectedDecl) ||
2349 cast<Forceable>(connectedDecl).isForceable())
2350 return failure();
2351
2352 // Only forward if the types exactly match and there is one connect.
2353 if (getSingleConnectUserOf(op.getDest()) != op)
2354 return failure();
2355
2356 // Only forward if there is more than one use
2357 if (connectedDecl->hasOneUse())
2358 return failure();
2359
2360 // Only do this if the connectee and the declaration are in the same block.
2361 auto *declBlock = connectedDecl->getBlock();
2362 auto *srcValueOp = op.getSrc().getDefiningOp();
2363 if (!srcValueOp) {
2364 // Ports are ok for wires but not registers.
2365 if (!isa<WireOp>(connectedDecl))
2366 return failure();
2367
2368 } else {
2369 // Constants/invalids in the same block are ok to forward, even through
2370 // reg's since the clocking doesn't matter for constants.
2371 auto cnst = dyn_cast<ConstantOp>(srcValueOp);
2372 if (!cnst)
2373 return failure();
2374 if (srcValueOp->getBlock() != declBlock)
2375 return failure();
2376 // A register with a time-zero `initial` value only reaches the constant on
2377 // the first edge; until then it holds `initial`. Forwarding the constant
2378 // would change its value at time zero, so bail unless the two agree.
2379 if (auto reg = dyn_cast<RegOp>(connectedDecl))
2380 if (!preservesInitial(reg.getInitialAttr(), cnst.getValue()))
2381 return failure();
2382 }
2383
2384 // Ok, we know we are doing the transformation.
2385
2386 auto replacement = op.getSrc();
2387 // This will be replaced with the constant source. First, make sure the
2388 // constant dominates all users.
2389 if (srcValueOp && srcValueOp != &declBlock->front())
2390 srcValueOp->moveBefore(&declBlock->front());
2391
2392 // Replace all things *using* the decl with the constant/port, and
2393 // remove the declaration.
2394 replaceOpAndCopyName(rewriter, connectedDecl, replacement);
2395
2396 // Remove the connect
2397 rewriter.eraseOp(op);
2398 return success();
2399}
2400
2401void ConnectOp::getCanonicalizationPatterns(RewritePatternSet &results,
2402 MLIRContext *context) {
2403 results.insert<patterns::ConnectExtension, patterns::ConnectSameType>(
2404 context);
2405}
2406
2407LogicalResult MatchingConnectOp::canonicalize(MatchingConnectOp op,
2408 PatternRewriter &rewriter) {
2409 // TODO: Canonicalize towards explicit extensions and flips here.
2410
2411 // If there is a simple value connected to a foldable decl like a wire or reg,
2412 // see if we can eliminate the decl.
2413 if (succeeded(canonicalizeSingleSetConnect(op, rewriter)))
2414 return success();
2415 return failure();
2416}
2417
2418//===----------------------------------------------------------------------===//
2419// Statements
2420//===----------------------------------------------------------------------===//
2421
2422/// If the specified value has an AttachOp user strictly dominating by
2423/// "dominatingAttach" then return it.
2424static AttachOp getDominatingAttachUser(Value value, AttachOp dominatedAttach) {
2425 for (auto *user : value.getUsers()) {
2426 auto attach = dyn_cast<AttachOp>(user);
2427 if (!attach || attach == dominatedAttach)
2428 continue;
2429 if (attach->isBeforeInBlock(dominatedAttach))
2430 return attach;
2431 }
2432 return {};
2433}
2434
2435LogicalResult AttachOp::canonicalize(AttachOp op, PatternRewriter &rewriter) {
2436 // Single operand attaches are a noop.
2437 if (op.getNumOperands() <= 1) {
2438 rewriter.eraseOp(op);
2439 return success();
2440 }
2441
2442 for (auto operand : op.getOperands()) {
2443 // Check to see if any of our operands has other attaches to it:
2444 // attach x, y
2445 // ...
2446 // attach x, z
2447 // If so, we can merge these into "attach x, y, z".
2448 if (auto attach = getDominatingAttachUser(operand, op)) {
2449 SmallVector<Value> newOperands(op.getOperands());
2450 for (auto newOperand : attach.getOperands())
2451 if (newOperand != operand) // Don't add operand twice.
2452 newOperands.push_back(newOperand);
2453 AttachOp::create(rewriter, op->getLoc(), newOperands);
2454 rewriter.eraseOp(attach);
2455 rewriter.eraseOp(op);
2456 return success();
2457 }
2458
2459 // If this wire is *only* used by an attach then we can just delete
2460 // it.
2461 // TODO: May need to be sensitive to "don't touch" or other
2462 // annotations.
2463 if (auto wire = dyn_cast_or_null<WireOp>(operand.getDefiningOp())) {
2464 if (!hasDontTouch(wire.getOperation()) && wire->hasOneUse() &&
2465 !wire.isForceable()) {
2466 SmallVector<Value> newOperands;
2467 for (auto newOperand : op.getOperands())
2468 if (newOperand != operand) // Don't the add wire.
2469 newOperands.push_back(newOperand);
2470
2471 AttachOp::create(rewriter, op->getLoc(), newOperands);
2472 rewriter.eraseOp(op);
2473 rewriter.eraseOp(wire);
2474 return success();
2475 }
2476 }
2477 }
2478 return failure();
2479}
2480
2481/// Replaces the given op with the contents of the given single-block region.
2482static void replaceOpWithRegion(PatternRewriter &rewriter, Operation *op,
2483 Region &region) {
2484 assert(llvm::hasSingleElement(region) && "expected single-region block");
2485 rewriter.inlineBlockBefore(&region.front(), op, {});
2486}
2487
2488LogicalResult WhenOp::canonicalize(WhenOp op, PatternRewriter &rewriter) {
2489 if (auto constant = op.getCondition().getDefiningOp<firrtl::ConstantOp>()) {
2490 if (constant.getValue().isAllOnes())
2491 replaceOpWithRegion(rewriter, op, op.getThenRegion());
2492 else if (op.hasElseRegion() && !op.getElseRegion().empty())
2493 replaceOpWithRegion(rewriter, op, op.getElseRegion());
2494
2495 rewriter.eraseOp(op);
2496
2497 return success();
2498 }
2499
2500 // Erase empty if-else block.
2501 if (!op.getThenBlock().empty() && op.hasElseRegion() &&
2502 op.getElseBlock().empty()) {
2503 rewriter.eraseBlock(&op.getElseBlock());
2504 return success();
2505 }
2506
2507 // Erase empty whens.
2508
2509 // If there is stuff in the then block, leave this operation alone.
2510 if (!op.getThenBlock().empty())
2511 return failure();
2512
2513 // If not and there is no else, then this operation is just useless.
2514 if (!op.hasElseRegion() || op.getElseBlock().empty()) {
2515 rewriter.eraseOp(op);
2516 return success();
2517 }
2518 return failure();
2519}
2520
2521namespace {
2522// Remove private nodes. If they have an interesting names, move the name to
2523// the source expression.
2524struct FoldNodeName : public mlir::OpRewritePattern<NodeOp> {
2525 using OpRewritePattern::OpRewritePattern;
2526 LogicalResult matchAndRewrite(NodeOp node,
2527 PatternRewriter &rewriter) const override {
2528 auto name = node.getNameAttr();
2529 if (!node.hasDroppableName() || node.getInnerSym() ||
2530 !AnnotationSet(node).empty() || node.isForceable())
2531 return failure();
2532 auto *newOp = node.getInput().getDefiningOp();
2533 if (newOp)
2534 updateName(rewriter, newOp, name);
2535 rewriter.replaceOp(node, node.getInput());
2536 return success();
2537 }
2538};
2539
2540// Bypass nodes.
2541struct NodeBypass : public mlir::OpRewritePattern<NodeOp> {
2542 using OpRewritePattern::OpRewritePattern;
2543 LogicalResult matchAndRewrite(NodeOp node,
2544 PatternRewriter &rewriter) const override {
2545 if (node.getInnerSym() || !AnnotationSet(node).empty() ||
2546 node.use_empty() || node.isForceable())
2547 return failure();
2548 rewriter.replaceAllUsesWith(node.getResult(), node.getInput());
2549 return success();
2550 }
2551};
2552
2553} // namespace
2554
2555template <typename OpTy>
2556static LogicalResult demoteForceableIfUnused(OpTy op,
2557 PatternRewriter &rewriter) {
2558 if (!op.isForceable() || !op.getDataRef().use_empty())
2559 return failure();
2560
2561 firrtl::detail::replaceWithNewForceability(op, false, &rewriter);
2562 return success();
2563}
2564
2565// Interesting names and symbols and don't touch force nodes to stick around.
2566LogicalResult NodeOp::fold(FoldAdaptor adaptor,
2567 SmallVectorImpl<OpFoldResult> &results) {
2568 if (!hasDroppableName())
2569 return failure();
2570 if (hasDontTouch(getResult())) // handles inner symbols
2571 return failure();
2572 if (getAnnotationsAttr() && !AnnotationSet(getAnnotationsAttr()).empty())
2573 return failure();
2574 if (isForceable())
2575 return failure();
2576 if (!adaptor.getInput())
2577 return failure();
2578
2579 results.push_back(adaptor.getInput());
2580 return success();
2581}
2582
2583void NodeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2584 MLIRContext *context) {
2585 results.insert<FoldNodeName>(context);
2587}
2588
2589namespace {
2590// For a lhs, find all the writers of fields of the aggregate type. If there
2591// is one writer for each field, merge the writes
2592struct AggOneShot : public mlir::RewritePattern {
2593 AggOneShot(StringRef name, uint32_t weight, MLIRContext *context)
2594 : RewritePattern(name, 0, context) {}
2595
2596 SmallVector<Value> getCompleteWrite(Operation *lhs) const {
2597 auto lhsTy = lhs->getResult(0).getType();
2599 return {};
2600
2601 DenseMap<uint32_t, Value> fields;
2602 for (Operation *user : lhs->getResult(0).getUsers()) {
2603 if (user->getParentOp() != lhs->getParentOp())
2604 return {};
2605 if (auto aConnect = dyn_cast<MatchingConnectOp>(user)) {
2606 if (aConnect.getDest() == lhs->getResult(0))
2607 return {};
2608 } else if (auto subField = dyn_cast<SubfieldOp>(user)) {
2609 for (Operation *subuser : subField.getResult().getUsers()) {
2610 if (auto aConnect = dyn_cast<MatchingConnectOp>(subuser)) {
2611 if (aConnect.getDest() == subField) {
2612 if (subuser->getParentOp() != lhs->getParentOp())
2613 return {};
2614 if (fields.count(subField.getFieldIndex())) // duplicate write
2615 return {};
2616 fields[subField.getFieldIndex()] = aConnect.getSrc();
2617 }
2618 continue;
2619 }
2620 return {};
2621 }
2622 } else if (auto subIndex = dyn_cast<SubindexOp>(user)) {
2623 for (Operation *subuser : subIndex.getResult().getUsers()) {
2624 if (auto aConnect = dyn_cast<MatchingConnectOp>(subuser)) {
2625 if (aConnect.getDest() == subIndex) {
2626 if (subuser->getParentOp() != lhs->getParentOp())
2627 return {};
2628 if (fields.count(subIndex.getIndex())) // duplicate write
2629 return {};
2630 fields[subIndex.getIndex()] = aConnect.getSrc();
2631 }
2632 continue;
2633 }
2634 return {};
2635 }
2636 } else {
2637 return {};
2638 }
2639 }
2640
2641 SmallVector<Value> values;
2642 uint32_t total = type_isa<BundleType>(lhsTy)
2643 ? type_cast<BundleType>(lhsTy).getNumElements()
2644 : type_cast<FVectorType>(lhsTy).getNumElements();
2645 for (uint32_t i = 0; i < total; ++i) {
2646 if (!fields.count(i))
2647 return {};
2648 values.push_back(fields[i]);
2649 }
2650 return values;
2651 }
2652
2653 LogicalResult matchAndRewrite(Operation *op,
2654 PatternRewriter &rewriter) const override {
2655 auto values = getCompleteWrite(op);
2656 if (values.empty())
2657 return failure();
2658 rewriter.setInsertionPointToEnd(op->getBlock());
2659 auto dest = op->getResult(0);
2660 auto destType = dest.getType();
2661
2662 // If not passive, cannot matchingconnect.
2663 if (!type_cast<FIRRTLBaseType>(destType).isPassive())
2664 return failure();
2665
2666 Value newVal = type_isa<BundleType>(destType)
2667 ? rewriter.createOrFold<BundleCreateOp>(op->getLoc(),
2668 destType, values)
2669 : rewriter.createOrFold<VectorCreateOp>(
2670 op->getLoc(), destType, values);
2671 rewriter.createOrFold<MatchingConnectOp>(op->getLoc(), dest, newVal);
2672 for (Operation *user : dest.getUsers()) {
2673 if (auto subIndex = dyn_cast<SubindexOp>(user)) {
2674 for (Operation *subuser :
2675 llvm::make_early_inc_range(subIndex.getResult().getUsers()))
2676 if (auto aConnect = dyn_cast<MatchingConnectOp>(subuser))
2677 if (aConnect.getDest() == subIndex)
2678 rewriter.eraseOp(aConnect);
2679 } else if (auto subField = dyn_cast<SubfieldOp>(user)) {
2680 for (Operation *subuser :
2681 llvm::make_early_inc_range(subField.getResult().getUsers()))
2682 if (auto aConnect = dyn_cast<MatchingConnectOp>(subuser))
2683 if (aConnect.getDest() == subField)
2684 rewriter.eraseOp(aConnect);
2685 }
2686 }
2687 return success();
2688 }
2689};
2690
2691struct WireAggOneShot : public AggOneShot {
2692 WireAggOneShot(MLIRContext *context)
2693 : AggOneShot(WireOp::getOperationName(), 0, context) {}
2694};
2695struct SubindexAggOneShot : public AggOneShot {
2696 SubindexAggOneShot(MLIRContext *context)
2697 : AggOneShot(SubindexOp::getOperationName(), 0, context) {}
2698};
2699struct SubfieldAggOneShot : public AggOneShot {
2700 SubfieldAggOneShot(MLIRContext *context)
2701 : AggOneShot(SubfieldOp::getOperationName(), 0, context) {}
2702};
2703} // namespace
2704
2705void WireOp::getCanonicalizationPatterns(RewritePatternSet &results,
2706 MLIRContext *context) {
2707 results.insert<WireAggOneShot>(context);
2709}
2710
2711void SubindexOp::getCanonicalizationPatterns(RewritePatternSet &results,
2712 MLIRContext *context) {
2713 results.insert<SubindexAggOneShot>(context);
2714}
2715
2716OpFoldResult SubindexOp::fold(FoldAdaptor adaptor) {
2717 auto attr = dyn_cast_or_null<ArrayAttr>(adaptor.getInput());
2718 if (!attr)
2719 return {};
2720 return attr[getIndex()];
2721}
2722
2723OpFoldResult SubfieldOp::fold(FoldAdaptor adaptor) {
2724 auto attr = dyn_cast_or_null<ArrayAttr>(adaptor.getInput());
2725 if (!attr)
2726 return {};
2727 auto index = getFieldIndex();
2728 return attr[index];
2729}
2730
2731void SubfieldOp::getCanonicalizationPatterns(RewritePatternSet &results,
2732 MLIRContext *context) {
2733 results.insert<SubfieldAggOneShot>(context);
2734}
2735
2736static Attribute collectFields(MLIRContext *context,
2737 ArrayRef<Attribute> operands) {
2738 for (auto operand : operands)
2739 if (!operand)
2740 return {};
2741 return ArrayAttr::get(context, operands);
2742}
2743
2744OpFoldResult BundleCreateOp::fold(FoldAdaptor adaptor) {
2745 // bundle_create(%foo["a"], %foo["b"]) -> %foo when the type of %foo is
2746 // bundle<a:..., b:...>.
2747 if (getNumOperands() > 0)
2748 if (SubfieldOp first = getOperand(0).getDefiningOp<SubfieldOp>())
2749 if (first.getFieldIndex() == 0 &&
2750 first.getInput().getType() == getType() &&
2751 llvm::all_of(
2752 llvm::drop_begin(llvm::enumerate(getOperands())), [&](auto elem) {
2753 auto subindex =
2754 elem.value().template getDefiningOp<SubfieldOp>();
2755 return subindex && subindex.getInput() == first.getInput() &&
2756 subindex.getFieldIndex() == elem.index();
2757 }))
2758 return first.getInput();
2759
2760 return collectFields(getContext(), adaptor.getOperands());
2761}
2762
2763OpFoldResult VectorCreateOp::fold(FoldAdaptor adaptor) {
2764 // vector_create(%foo[0], %foo[1]) -> %foo when the type of %foo is
2765 // vector<..., 2>.
2766 if (getNumOperands() > 0)
2767 if (SubindexOp first = getOperand(0).getDefiningOp<SubindexOp>())
2768 if (first.getIndex() == 0 && first.getInput().getType() == getType() &&
2769 llvm::all_of(
2770 llvm::drop_begin(llvm::enumerate(getOperands())), [&](auto elem) {
2771 auto subindex =
2772 elem.value().template getDefiningOp<SubindexOp>();
2773 return subindex && subindex.getInput() == first.getInput() &&
2774 subindex.getIndex() == elem.index();
2775 }))
2776 return first.getInput();
2777
2778 return collectFields(getContext(), adaptor.getOperands());
2779}
2780
2781OpFoldResult UninferredResetCastOp::fold(FoldAdaptor adaptor) {
2782 if (getOperand().getType() == getType())
2783 return getOperand();
2784 return {};
2785}
2786
2787namespace {
2788// A register with constant reset and all connection to either itself or the
2789// same constant, must be replaced by the constant.
2790struct FoldResetMux : public mlir::OpRewritePattern<RegResetOp> {
2791 using OpRewritePattern::OpRewritePattern;
2792 LogicalResult matchAndRewrite(RegResetOp reg,
2793 PatternRewriter &rewriter) const override {
2794 auto reset =
2795 dyn_cast_or_null<ConstantOp>(reg.getResetValue().getDefiningOp());
2796 if (!reset || hasDontTouch(reg.getOperation()) ||
2797 !AnnotationSet(reg).empty() || reg.isForceable())
2798 return failure();
2799 // Find the one true connect, or bail
2800 auto con = getSingleConnectUserOf(reg.getResult());
2801 if (!con)
2802 return failure();
2803
2804 auto mux = dyn_cast_or_null<MuxPrimOp>(con.getSrc().getDefiningOp());
2805 if (!mux)
2806 return failure();
2807 auto *high = mux.getHigh().getDefiningOp();
2808 auto *low = mux.getLow().getDefiningOp();
2809 auto constOp = dyn_cast_or_null<ConstantOp>(high);
2810
2811 if (constOp && low != reg)
2812 return failure();
2813 if (dyn_cast_or_null<ConstantOp>(low) && high == reg)
2814 constOp = dyn_cast<ConstantOp>(low);
2815
2816 if (!constOp || constOp.getType() != reset.getType() ||
2817 constOp.getValue() != reset.getValue())
2818 return failure();
2819
2820 // Check all types should be typed by now
2821 auto regTy = reg.getResult().getType();
2822 if (con.getDest().getType() != regTy || con.getSrc().getType() != regTy ||
2823 mux.getHigh().getType() != regTy || mux.getLow().getType() != regTy ||
2824 regTy.getBitWidthOrSentinel() < 0)
2825 return failure();
2826
2827 // Ok, we know we are doing the transformation.
2828
2829 // Bail if replacing the register with the constant would change its
2830 // time-zero simulation value.
2831 if (!preservesInitial(reg.getInitialAttr(), constOp.getValue()))
2832 return failure();
2833
2834 // Make sure the constant dominates all users.
2835 if (constOp != &con->getBlock()->front())
2836 constOp->moveBefore(&con->getBlock()->front());
2837
2838 // Replace the register with the constant.
2839 replaceOpAndCopyName(rewriter, reg, constOp.getResult());
2840 // Remove the connect.
2841 rewriter.eraseOp(con);
2842 return success();
2843 }
2844};
2845} // namespace
2846
2847static bool isDefinedByOneConstantOp(Value v) {
2848 if (auto c = v.getDefiningOp<ConstantOp>())
2849 return c.getValue().isOne();
2850 if (auto sc = v.getDefiningOp<SpecialConstantOp>())
2851 return sc.getValue();
2852 return false;
2853}
2854
2855static LogicalResult
2856canonicalizeRegResetWithOneReset(RegResetOp reg, PatternRewriter &rewriter) {
2857 if (!isDefinedByOneConstantOp(reg.getResetSignal()))
2858 return failure();
2859
2860 auto resetValue = reg.getResetValue();
2861 if (reg.getType(0) != resetValue.getType())
2862 return failure();
2863
2864 // Bail if replacing the register with the constant would change its
2865 // time-zero simulation value.
2866 if (auto constOp = dyn_cast_or_null<ConstantOp>(resetValue.getDefiningOp())) {
2867 if (!preservesInitial(reg.getInitialAttr(), constOp.getValue()))
2868 return failure();
2869 } else if (!preservesInitial(reg.getInitialAttr())) {
2870 return failure();
2871 }
2872
2873 // Ignore 'passthrough'.
2874 (void)dropWrite(rewriter, reg->getResult(0), {});
2876 rewriter, reg, resetValue, reg.getNameAttr(), reg.getNameKind(),
2877 reg.getAnnotationsAttr(), reg.getInnerSymAttr(), reg.getForceable());
2878 return success();
2879}
2880
2881void RegResetOp::getCanonicalizationPatterns(RewritePatternSet &results,
2882 MLIRContext *context) {
2883 results.add<patterns::RegResetWithZeroReset, FoldResetMux>(context);
2886}
2887
2888// Returns the value connected to a port, if there is only one.
2889static Value getPortFieldValue(Value port, StringRef name) {
2890 auto portTy = type_cast<BundleType>(port.getType());
2891 auto fieldIndex = portTy.getElementIndex(name);
2892 assert(fieldIndex && "missing field on memory port");
2893
2894 Value value = {};
2895 for (auto *op : port.getUsers()) {
2896 auto portAccess = cast<SubfieldOp>(op);
2897 if (fieldIndex != portAccess.getFieldIndex())
2898 continue;
2899 auto conn = getSingleConnectUserOf(portAccess);
2900 if (!conn || value)
2901 return {};
2902 value = conn.getSrc();
2903 }
2904 return value;
2905}
2906
2907// Returns true if the enable field of a port is set to false.
2908static bool isPortDisabled(Value port) {
2909 auto value = getPortFieldValue(port, "en");
2910 if (!value)
2911 return false;
2912 auto portConst = value.getDefiningOp<ConstantOp>();
2913 if (!portConst)
2914 return false;
2915 return portConst.getValue().isZero();
2916}
2917
2918// Returns true if the data output is unused.
2919static bool isPortUnused(Value port, StringRef data) {
2920 auto portTy = type_cast<BundleType>(port.getType());
2921 auto fieldIndex = portTy.getElementIndex(data);
2922 assert(fieldIndex && "missing enable flag on memory port");
2923
2924 for (auto *op : port.getUsers()) {
2925 auto portAccess = cast<SubfieldOp>(op);
2926 if (fieldIndex != portAccess.getFieldIndex())
2927 continue;
2928 if (!portAccess.use_empty())
2929 return false;
2930 }
2931
2932 return true;
2933}
2934
2935// Returns the value connected to a port, if there is only one.
2936static void replacePortField(PatternRewriter &rewriter, Value port,
2937 StringRef name, Value value) {
2938 auto portTy = type_cast<BundleType>(port.getType());
2939 auto fieldIndex = portTy.getElementIndex(name);
2940 assert(fieldIndex && "missing field on memory port");
2941
2942 for (auto *op : llvm::make_early_inc_range(port.getUsers())) {
2943 auto portAccess = cast<SubfieldOp>(op);
2944 if (fieldIndex != portAccess.getFieldIndex())
2945 continue;
2946 rewriter.replaceAllUsesWith(portAccess, value);
2947 rewriter.eraseOp(portAccess);
2948 }
2949}
2950
2951// Remove accesses to a port which is used.
2952static void erasePort(PatternRewriter &rewriter, Value port) {
2953 // Helper to create a dummy 0 clock for the dummy registers.
2954 Value clock;
2955 auto getClock = [&] {
2956 if (!clock)
2957 clock = SpecialConstantOp::create(rewriter, port.getLoc(),
2958 ClockType::get(rewriter.getContext()),
2959 false);
2960 return clock;
2961 };
2962
2963 // Find the clock field of the port and determine whether the port is
2964 // accessed only through its subfields or as a whole wire. If the port
2965 // is used in its entirety, replace it with a wire. Otherwise,
2966 // eliminate individual subfields and replace with reasonable defaults.
2967 for (auto *op : port.getUsers()) {
2968 auto subfield = dyn_cast<SubfieldOp>(op);
2969 if (!subfield) {
2970 auto ty = port.getType();
2971 auto reg = RegOp::create(rewriter, port.getLoc(), ty, getClock());
2972 rewriter.replaceAllUsesWith(port, reg.getResult());
2973 return;
2974 }
2975 }
2976
2977 // Remove all connects to field accesses as they are no longer relevant.
2978 // If field values are used anywhere, which should happen solely for read
2979 // ports, a dummy register is introduced which replicates the behaviour of
2980 // memory that is never written, but might be read.
2981 for (auto *accessOp : llvm::make_early_inc_range(port.getUsers())) {
2982 auto access = cast<SubfieldOp>(accessOp);
2983 for (auto *user : llvm::make_early_inc_range(access->getUsers())) {
2984 auto connect = dyn_cast<FConnectLike>(user);
2985 if (connect && connect.getDest() == access) {
2986 rewriter.eraseOp(user);
2987 continue;
2988 }
2989 }
2990 if (access.use_empty()) {
2991 rewriter.eraseOp(access);
2992 continue;
2993 }
2994
2995 // Replace read values with a register that is never written, handing off
2996 // the canonicalization of such a register to another canonicalizer.
2997 auto ty = access.getType();
2998 auto reg = RegOp::create(rewriter, access.getLoc(), ty, getClock());
2999 rewriter.replaceOp(access, reg.getResult());
3000 }
3001 assert(port.use_empty() && "port should have no remaining uses");
3002}
3003
3004namespace {
3005// If memory has known, but zero width, eliminate it.
3006struct FoldZeroWidthMemory : public mlir::OpRewritePattern<MemOp> {
3007 using OpRewritePattern::OpRewritePattern;
3008 LogicalResult matchAndRewrite(MemOp mem,
3009 PatternRewriter &rewriter) const override {
3010 if (hasDontTouch(mem))
3011 return failure();
3012
3013 if (!firrtl::type_isa<IntType>(mem.getDataType()) ||
3014 mem.getDataType().getBitWidthOrSentinel() != 0)
3015 return failure();
3016
3017 // Make sure are users are safe to replace
3018 for (auto port : mem.getResults())
3019 for (auto *user : port.getUsers())
3020 if (!isa<SubfieldOp>(user))
3021 return failure();
3022
3023 // Annoyingly, there isn't a good replacement for the port as a whole,
3024 // since they have an outer flip type.
3025 for (auto port : mem.getResults()) {
3026 for (auto *user : llvm::make_early_inc_range(port.getUsers())) {
3027 SubfieldOp sfop = cast<SubfieldOp>(user);
3028 StringRef fieldName = sfop.getFieldName();
3030 rewriter, sfop, sfop.getResult().getType())
3031 .getResult();
3032 if (fieldName.ends_with("data")) {
3033 // Make sure to write data ports.
3034 auto zero = firrtl::ConstantOp::create(
3035 rewriter, wire.getLoc(),
3036 firrtl::type_cast<IntType>(wire.getType()), APInt::getZero(0));
3037 MatchingConnectOp::create(rewriter, wire.getLoc(), wire, zero);
3038 }
3039 }
3040 }
3041 rewriter.eraseOp(mem);
3042 return success();
3043 }
3044};
3045
3046// If memory has no write ports and no file initialization, eliminate it.
3047struct FoldReadOrWriteOnlyMemory : public mlir::OpRewritePattern<MemOp> {
3048 using OpRewritePattern::OpRewritePattern;
3049 LogicalResult matchAndRewrite(MemOp mem,
3050 PatternRewriter &rewriter) const override {
3051 if (hasDontTouch(mem))
3052 return failure();
3053 bool isRead = false, isWritten = false;
3054 for (unsigned i = 0; i < mem.getNumResults(); ++i) {
3055 switch (mem.getPortKind(i)) {
3056 case MemOp::PortKind::Read:
3057 isRead = true;
3058 if (isWritten)
3059 return failure();
3060 continue;
3061 case MemOp::PortKind::Write:
3062 isWritten = true;
3063 if (isRead)
3064 return failure();
3065 continue;
3066 case MemOp::PortKind::Debug:
3067 case MemOp::PortKind::ReadWrite:
3068 return failure();
3069 }
3070 llvm_unreachable("unknown port kind");
3071 }
3072 assert((!isWritten || !isRead) && "memory is in use");
3073
3074 // If the memory is read only, but has a file initialization, then we can't
3075 // remove it. A write only memory with file initialization is okay to
3076 // remove.
3077 if (isRead && mem.getInit())
3078 return failure();
3079
3080 for (auto port : mem.getResults())
3081 erasePort(rewriter, port);
3082
3083 rewriter.eraseOp(mem);
3084 return success();
3085 }
3086};
3087
3088// Eliminate the dead ports of memories.
3089struct FoldUnusedPorts : public mlir::OpRewritePattern<MemOp> {
3090 using OpRewritePattern::OpRewritePattern;
3091 LogicalResult matchAndRewrite(MemOp mem,
3092 PatternRewriter &rewriter) const override {
3093 if (hasDontTouch(mem))
3094 return failure();
3095 // Identify the dead and changed ports.
3096 llvm::SmallBitVector deadPorts(mem.getNumResults());
3097 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3098 // Do not simplify annotated ports.
3099 if (!mem.getPortAnnotation(i).empty())
3100 continue;
3101
3102 // Skip debug ports.
3103 auto kind = mem.getPortKind(i);
3104 if (kind == MemOp::PortKind::Debug)
3105 continue;
3106
3107 // If a port is disabled, always eliminate it.
3108 if (isPortDisabled(port)) {
3109 deadPorts.set(i);
3110 continue;
3111 }
3112 // Eliminate read ports whose outputs are not used.
3113 if (kind == MemOp::PortKind::Read && isPortUnused(port, "data")) {
3114 deadPorts.set(i);
3115 continue;
3116 }
3117 }
3118 if (deadPorts.none())
3119 return failure();
3120
3121 // Rebuild the new memory with the altered ports.
3122 SmallVector<Type> resultTypes;
3123 SmallVector<StringRef> portNames;
3124 SmallVector<Attribute> portAnnotations;
3125 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3126 if (deadPorts[i])
3127 continue;
3128 resultTypes.push_back(port.getType());
3129 portNames.push_back(mem.getPortName(i));
3130 portAnnotations.push_back(mem.getPortAnnotation(i));
3131 }
3132
3133 MemOp newOp;
3134 if (!resultTypes.empty())
3135 newOp = MemOp::create(
3136 rewriter, mem.getLoc(), resultTypes, mem.getReadLatency(),
3137 mem.getWriteLatency(), mem.getDepth(), mem.getRuw(),
3138 rewriter.getStrArrayAttr(portNames), mem.getName(), mem.getNameKind(),
3139 mem.getAnnotations(), rewriter.getArrayAttr(portAnnotations),
3140 mem.getInnerSymAttr(), mem.getInitAttr(), mem.getPrefixAttr());
3141
3142 // Replace the dead ports with dummy wires.
3143 unsigned nextPort = 0;
3144 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3145 if (deadPorts[i])
3146 erasePort(rewriter, port);
3147 else
3148 rewriter.replaceAllUsesWith(port, newOp.getResult(nextPort++));
3149 }
3150
3151 rewriter.eraseOp(mem);
3152 return success();
3153 }
3154};
3155
3156// Rewrite write-only read-write ports to write ports.
3157struct FoldReadWritePorts : public mlir::OpRewritePattern<MemOp> {
3158 using OpRewritePattern::OpRewritePattern;
3159 LogicalResult matchAndRewrite(MemOp mem,
3160 PatternRewriter &rewriter) const override {
3161 if (hasDontTouch(mem))
3162 return failure();
3163
3164 // Identify read-write ports whose read end is unused.
3165 llvm::SmallBitVector deadReads(mem.getNumResults());
3166 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3167 if (mem.getPortKind(i) != MemOp::PortKind::ReadWrite)
3168 continue;
3169 if (!mem.getPortAnnotation(i).empty())
3170 continue;
3171 if (isPortUnused(port, "rdata")) {
3172 deadReads.set(i);
3173 continue;
3174 }
3175 }
3176 if (deadReads.none())
3177 return failure();
3178
3179 SmallVector<Type> resultTypes;
3180 SmallVector<StringRef> portNames;
3181 SmallVector<Attribute> portAnnotations;
3182 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3183 if (deadReads[i])
3184 resultTypes.push_back(
3185 MemOp::getTypeForPort(mem.getDepth(), mem.getDataType(),
3186 MemOp::PortKind::Write, mem.getMaskBits()));
3187 else
3188 resultTypes.push_back(port.getType());
3189
3190 portNames.push_back(mem.getPortName(i));
3191 portAnnotations.push_back(mem.getPortAnnotation(i));
3192 }
3193
3194 auto newOp = MemOp::create(
3195 rewriter, mem.getLoc(), resultTypes, mem.getReadLatency(),
3196 mem.getWriteLatency(), mem.getDepth(), mem.getRuw(),
3197 rewriter.getStrArrayAttr(portNames), mem.getName(), mem.getNameKind(),
3198 mem.getAnnotations(), rewriter.getArrayAttr(portAnnotations),
3199 mem.getInnerSymAttr(), mem.getInitAttr(), mem.getPrefixAttr());
3200
3201 for (unsigned i = 0, n = mem.getNumResults(); i < n; ++i) {
3202 auto result = mem.getResult(i);
3203 auto newResult = newOp.getResult(i);
3204 if (deadReads[i]) {
3205 auto resultPortTy = type_cast<BundleType>(result.getType());
3206
3207 // Rewrite accesses to the old port field to accesses to a
3208 // corresponding field of the new port.
3209 auto replace = [&](StringRef toName, StringRef fromName) {
3210 auto fromFieldIndex = resultPortTy.getElementIndex(fromName);
3211 assert(fromFieldIndex && "missing enable flag on memory port");
3212
3213 auto toField = SubfieldOp::create(rewriter, newResult.getLoc(),
3214 newResult, toName);
3215 for (auto *op : llvm::make_early_inc_range(result.getUsers())) {
3216 auto fromField = cast<SubfieldOp>(op);
3217 if (fromFieldIndex != fromField.getFieldIndex())
3218 continue;
3219 rewriter.replaceOp(fromField, toField.getResult());
3220 }
3221 };
3222
3223 replace("addr", "addr");
3224 replace("en", "en");
3225 replace("clk", "clk");
3226 replace("data", "wdata");
3227 replace("mask", "wmask");
3228
3229 // Remove the wmode field, replacing it with dummy wires.
3230 auto wmodeFieldIndex = resultPortTy.getElementIndex("wmode");
3231 for (auto *op : llvm::make_early_inc_range(result.getUsers())) {
3232 auto wmodeField = cast<SubfieldOp>(op);
3233 if (wmodeFieldIndex != wmodeField.getFieldIndex())
3234 continue;
3235 rewriter.replaceOpWithNewOp<WireOp>(wmodeField, wmodeField.getType());
3236 }
3237 } else {
3238 rewriter.replaceAllUsesWith(result, newResult);
3239 }
3240 }
3241 rewriter.eraseOp(mem);
3242 return success();
3243 }
3244};
3245
3246// Eliminate the dead ports of memories.
3247struct FoldUnusedBits : public mlir::OpRewritePattern<MemOp> {
3248 using OpRewritePattern::OpRewritePattern;
3249
3250 LogicalResult matchAndRewrite(MemOp mem,
3251 PatternRewriter &rewriter) const override {
3252 if (hasDontTouch(mem))
3253 return failure();
3254
3255 // Only apply the transformation if the memory is not sequential.
3256 const auto &summary = mem.getSummary();
3257 if (summary.isMasked || summary.isSeqMem())
3258 return failure();
3259
3260 auto type = type_dyn_cast<IntType>(mem.getDataType());
3261 if (!type)
3262 return failure();
3263 auto width = type.getBitWidthOrSentinel();
3264 if (width <= 0)
3265 return failure();
3266
3267 llvm::SmallBitVector usedBits(width);
3268 DenseMap<unsigned, unsigned> mapping;
3269
3270 // Find which bits are used out of the users of a read port. This detects
3271 // ports whose data/rdata field is used only through bit select ops. The
3272 // bit selects are then used to build a bit-mask. The ops are collected.
3273 SmallVector<BitsPrimOp> readOps;
3274 auto findReadUsers = [&](Value port, StringRef field) -> LogicalResult {
3275 auto portTy = type_cast<BundleType>(port.getType());
3276 auto fieldIndex = portTy.getElementIndex(field);
3277 assert(fieldIndex && "missing data port");
3278
3279 for (auto *op : port.getUsers()) {
3280 auto portAccess = cast<SubfieldOp>(op);
3281 if (fieldIndex != portAccess.getFieldIndex())
3282 continue;
3283
3284 for (auto *user : op->getUsers()) {
3285 auto bits = dyn_cast<BitsPrimOp>(user);
3286 if (!bits)
3287 return failure();
3288
3289 usedBits.set(bits.getLo(), bits.getHi() + 1);
3290 if (usedBits.all())
3291 return failure();
3292
3293 mapping[bits.getLo()] = 0;
3294 readOps.push_back(bits);
3295 }
3296 }
3297
3298 return success();
3299 };
3300
3301 // Finds the users of write ports. This expects all the data/wdata fields
3302 // of the ports to be used solely as the destination of matching connects.
3303 // If a memory has ports with other uses, it is excluded from optimisation.
3304 SmallVector<MatchingConnectOp> writeOps;
3305 auto findWriteUsers = [&](Value port, StringRef field) -> LogicalResult {
3306 auto portTy = type_cast<BundleType>(port.getType());
3307 auto fieldIndex = portTy.getElementIndex(field);
3308 assert(fieldIndex && "missing data port");
3309
3310 for (auto *op : port.getUsers()) {
3311 auto portAccess = cast<SubfieldOp>(op);
3312 if (fieldIndex != portAccess.getFieldIndex())
3313 continue;
3314
3315 auto conn = getSingleConnectUserOf(portAccess);
3316 if (!conn)
3317 return failure();
3318
3319 writeOps.push_back(conn);
3320 }
3321 return success();
3322 };
3323
3324 // Traverse all ports and find the read and used data fields.
3325 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3326 // Do not simplify annotated ports.
3327 if (!mem.getPortAnnotation(i).empty())
3328 return failure();
3329
3330 switch (mem.getPortKind(i)) {
3331 case MemOp::PortKind::Debug:
3332 // Skip debug ports.
3333 return failure();
3334 case MemOp::PortKind::Write:
3335 if (failed(findWriteUsers(port, "data")))
3336 return failure();
3337 continue;
3338 case MemOp::PortKind::Read:
3339 if (failed(findReadUsers(port, "data")))
3340 return failure();
3341 continue;
3342 case MemOp::PortKind::ReadWrite:
3343 if (failed(findWriteUsers(port, "wdata")))
3344 return failure();
3345 if (failed(findReadUsers(port, "rdata")))
3346 return failure();
3347 continue;
3348 }
3349 llvm_unreachable("unknown port kind");
3350 }
3351
3352 // Unused memories are handled in a different canonicalizer.
3353 if (usedBits.none())
3354 return failure();
3355
3356 // Build a mapping of existing indices to compacted ones.
3357 SmallVector<std::pair<unsigned, unsigned>> ranges;
3358 unsigned newWidth = 0;
3359 for (int i = usedBits.find_first(); 0 <= i && i < width;) {
3360 int e = usedBits.find_next_unset(i);
3361 if (e < 0)
3362 e = width;
3363 for (int idx = i; idx < e; ++idx, ++newWidth) {
3364 if (auto it = mapping.find(idx); it != mapping.end()) {
3365 it->second = newWidth;
3366 }
3367 }
3368 ranges.emplace_back(i, e - 1);
3369 i = e != width ? usedBits.find_next(e) : e;
3370 }
3371
3372 // Create the new op with the new port types.
3373 auto newType = IntType::get(mem->getContext(), type.isSigned(), newWidth);
3374 SmallVector<Type> portTypes;
3375 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3376 portTypes.push_back(
3377 MemOp::getTypeForPort(mem.getDepth(), newType, mem.getPortKind(i)));
3378 }
3379 auto newMem = rewriter.replaceOpWithNewOp<MemOp>(
3380 mem, portTypes, mem.getReadLatency(), mem.getWriteLatency(),
3381 mem.getDepth(), mem.getRuw(), mem.getPortNames(), mem.getName(),
3382 mem.getNameKind(), mem.getAnnotations(), mem.getPortAnnotations(),
3383 mem.getInnerSymAttr(), mem.getInitAttr(), mem.getPrefixAttr());
3384
3385 // Rewrite bundle users to the new data type.
3386 auto rewriteSubfield = [&](Value port, StringRef field) {
3387 auto portTy = type_cast<BundleType>(port.getType());
3388 auto fieldIndex = portTy.getElementIndex(field);
3389 assert(fieldIndex && "missing data port");
3390
3391 rewriter.setInsertionPointAfter(newMem);
3392 auto newPortAccess =
3393 SubfieldOp::create(rewriter, port.getLoc(), port, field);
3394
3395 for (auto *op : llvm::make_early_inc_range(port.getUsers())) {
3396 auto portAccess = cast<SubfieldOp>(op);
3397 if (op == newPortAccess || fieldIndex != portAccess.getFieldIndex())
3398 continue;
3399 rewriter.replaceOp(portAccess, newPortAccess.getResult());
3400 }
3401 };
3402
3403 // Rewrite the field accesses.
3404 for (auto [i, port] : llvm::enumerate(newMem.getResults())) {
3405 switch (newMem.getPortKind(i)) {
3406 case MemOp::PortKind::Debug:
3407 llvm_unreachable("cannot rewrite debug port");
3408 case MemOp::PortKind::Write:
3409 rewriteSubfield(port, "data");
3410 continue;
3411 case MemOp::PortKind::Read:
3412 rewriteSubfield(port, "data");
3413 continue;
3414 case MemOp::PortKind::ReadWrite:
3415 rewriteSubfield(port, "rdata");
3416 rewriteSubfield(port, "wdata");
3417 continue;
3418 }
3419 llvm_unreachable("unknown port kind");
3420 }
3421
3422 // Rewrite the reads to the new ranges, compacting them.
3423 for (auto readOp : readOps) {
3424 rewriter.setInsertionPointAfter(readOp);
3425 auto it = mapping.find(readOp.getLo());
3426 assert(it != mapping.end() && "bit op mapping not found");
3427 // Create a new bit selection from the compressed memory. The new op may
3428 // be folded if we are selecting the entire compressed memory.
3429 auto newReadValue = rewriter.createOrFold<BitsPrimOp>(
3430 readOp.getLoc(), readOp.getInput(),
3431 readOp.getHi() - readOp.getLo() + it->second, it->second);
3432 rewriter.replaceAllUsesWith(readOp, newReadValue);
3433 rewriter.eraseOp(readOp);
3434 }
3435
3436 // Rewrite the writes into a concatenation of slices.
3437 for (auto writeOp : writeOps) {
3438 Value source = writeOp.getSrc();
3439 rewriter.setInsertionPoint(writeOp);
3440
3441 SmallVector<Value> slices;
3442 for (auto &[start, end] : llvm::reverse(ranges)) {
3443 Value slice = rewriter.createOrFold<BitsPrimOp>(writeOp.getLoc(),
3444 source, end, start);
3445 slices.push_back(slice);
3446 }
3447
3448 Value catOfSlices =
3449 rewriter.createOrFold<CatPrimOp>(writeOp.getLoc(), slices);
3450
3451 // If the original memory held a signed integer, then the compressed
3452 // memory will be signed too. Since the catOfSlices is always unsigned,
3453 // cast the data to a signed integer if needed before connecting back to
3454 // the memory.
3455 if (type.isSigned())
3456 catOfSlices =
3457 rewriter.createOrFold<AsSIntPrimOp>(writeOp.getLoc(), catOfSlices);
3458
3459 rewriter.replaceOpWithNewOp<MatchingConnectOp>(writeOp, writeOp.getDest(),
3460 catOfSlices);
3461 }
3462
3463 return success();
3464 }
3465};
3466
3467// Rewrite single-address memories to a firrtl register.
3468struct FoldRegMems : public mlir::OpRewritePattern<MemOp> {
3469 using OpRewritePattern::OpRewritePattern;
3470 LogicalResult matchAndRewrite(MemOp mem,
3471 PatternRewriter &rewriter) const override {
3472 const FirMemory &info = mem.getSummary();
3473 if (hasDontTouch(mem) || info.depth != 1)
3474 return failure();
3475
3476 auto ty = mem.getDataType();
3477 auto loc = mem.getLoc();
3478 auto *block = mem->getBlock();
3479
3480 // Find the clock of the register-to-be, all write ports should share it.
3481 Value clock;
3482 SmallPtrSet<Operation *, 8> connects;
3483 SmallVector<SubfieldOp> portAccesses;
3484 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3485 if (!mem.getPortAnnotation(i).empty())
3486 continue;
3487
3488 auto collect = [&, port = port](ArrayRef<StringRef> fields) {
3489 auto portTy = type_cast<BundleType>(port.getType());
3490 for (auto field : fields) {
3491 auto fieldIndex = portTy.getElementIndex(field);
3492 assert(fieldIndex && "missing field on memory port");
3493
3494 for (auto *op : port.getUsers()) {
3495 auto portAccess = cast<SubfieldOp>(op);
3496 if (fieldIndex != portAccess.getFieldIndex())
3497 continue;
3498 portAccesses.push_back(portAccess);
3499 for (auto *user : portAccess->getUsers()) {
3500 auto conn = dyn_cast<FConnectLike>(user);
3501 if (!conn)
3502 return failure();
3503 connects.insert(conn);
3504 }
3505 }
3506 }
3507 return success();
3508 };
3509
3510 switch (mem.getPortKind(i)) {
3511 case MemOp::PortKind::Debug:
3512 return failure();
3513 case MemOp::PortKind::Read:
3514 if (failed(collect({"clk", "en", "addr"})))
3515 return failure();
3516 continue;
3517 case MemOp::PortKind::Write:
3518 if (failed(collect({"clk", "en", "addr", "data", "mask"})))
3519 return failure();
3520 break;
3521 case MemOp::PortKind::ReadWrite:
3522 if (failed(collect({"clk", "en", "addr", "wmode", "wdata", "wmask"})))
3523 return failure();
3524 break;
3525 }
3526
3527 Value portClock = getPortFieldValue(port, "clk");
3528 if (!portClock || (clock && portClock != clock))
3529 return failure();
3530 clock = portClock;
3531 }
3532 // Create a new wire where the memory used to be. This wire will dominate
3533 // all readers of the memory. Reads should be made through this wire.
3534 rewriter.setInsertionPointAfter(mem);
3535 auto memWire = WireOp::create(rewriter, loc, ty).getResult();
3536
3537 // The memory is replaced by a register, which we place at the end of the
3538 // block, so that any value driven to the original memory will dominate the
3539 // new register (including the clock). All other ops will be placed
3540 // after the register.
3541 rewriter.setInsertionPointToEnd(block);
3542 auto memReg =
3543 RegOp::create(rewriter, loc, ty, clock, mem.getName()).getResult();
3544
3545 // Connect the output of the register to the wire.
3546 MatchingConnectOp::create(rewriter, loc, memWire, memReg);
3547
3548 // Helper to insert a given number of pipeline stages through registers.
3549 // The pipelines are placed at the end of the block.
3550 auto pipeline = [&](Value value, Value clock, const Twine &name,
3551 unsigned latency) {
3552 for (unsigned i = 0; i < latency; ++i) {
3553 std::string regName;
3554 {
3555 llvm::raw_string_ostream os(regName);
3556 os << mem.getName() << "_" << name << "_" << i;
3557 }
3558 auto reg = RegOp::create(rewriter, mem.getLoc(), value.getType(), clock,
3559 rewriter.getStringAttr(regName))
3560 .getResult();
3561 MatchingConnectOp::create(rewriter, value.getLoc(), reg, value);
3562 value = reg;
3563 }
3564 return value;
3565 };
3566
3567 const unsigned writeStages = info.writeLatency - 1;
3568
3569 // Traverse each port. Replace reads with the pipelined register, discarding
3570 // the enable flag and reading unconditionally. Pipeline the mask, enable
3571 // and data bits of all write ports to be arbitrated and wired to the reg.
3572 SmallVector<std::tuple<Value, Value, Value>> writes;
3573 for (auto [i, port] : llvm::enumerate(mem.getResults())) {
3574 Value portClock = getPortFieldValue(port, "clk");
3575 StringRef name = mem.getPortName(i);
3576
3577 auto portPipeline = [&, port = port](StringRef field, unsigned stages) {
3578 Value value = getPortFieldValue(port, field);
3579 assert(value);
3580 return pipeline(value, portClock, name + "_" + field, stages);
3581 };
3582
3583 switch (mem.getPortKind(i)) {
3584 case MemOp::PortKind::Debug:
3585 llvm_unreachable("unknown port kind");
3586 case MemOp::PortKind::Read: {
3587 // Read ports pipeline the addr and enable signals. However, the
3588 // address must be 0 for single-address memories and the enable signal
3589 // is ignored, always reading out the register. Under these constraints,
3590 // the read port can be replaced with the value from the register.
3591 replacePortField(rewriter, port, "data", memWire);
3592 break;
3593 }
3594 case MemOp::PortKind::Write: {
3595 auto data = portPipeline("data", writeStages);
3596 auto en = portPipeline("en", writeStages);
3597 auto mask = portPipeline("mask", writeStages);
3598 writes.emplace_back(data, en, mask);
3599 break;
3600 }
3601 case MemOp::PortKind::ReadWrite: {
3602 // Always read the register into the read end.
3603 replacePortField(rewriter, port, "rdata", memWire);
3604
3605 // Create a write enable and pipeline stages.
3606 auto wdata = portPipeline("wdata", writeStages);
3607 auto wmask = portPipeline("wmask", writeStages);
3608
3609 Value en = getPortFieldValue(port, "en");
3610 Value wmode = getPortFieldValue(port, "wmode");
3611
3612 auto wen = AndPrimOp::create(rewriter, port.getLoc(), en, wmode);
3613 auto wenPipelined =
3614 pipeline(wen, portClock, name + "_wen", writeStages);
3615 writes.emplace_back(wdata, wenPipelined, wmask);
3616 break;
3617 }
3618 }
3619 }
3620
3621 // Regardless of `writeUnderWrite`, always implement PortOrder.
3622 Value next = memReg;
3623 for (auto &[data, en, mask] : writes) {
3624 Value masked;
3625
3626 // If a mask bit is used, emit muxes to select the input from the
3627 // register (no mask) or the input (mask bit set).
3628 Location loc = mem.getLoc();
3629 unsigned maskGran = info.dataWidth / info.maskBits;
3630 SmallVector<Value> chunks;
3631 for (unsigned i = 0; i < info.maskBits; ++i) {
3632 unsigned hi = (i + 1) * maskGran - 1;
3633 unsigned lo = i * maskGran;
3634
3635 auto dataPart = rewriter.createOrFold<BitsPrimOp>(loc, data, hi, lo);
3636 auto nextPart = rewriter.createOrFold<BitsPrimOp>(loc, next, hi, lo);
3637 auto bit = rewriter.createOrFold<BitsPrimOp>(loc, mask, i, i);
3638 auto chunk = MuxPrimOp::create(rewriter, loc, bit, dataPart, nextPart);
3639 chunks.push_back(chunk);
3640 }
3641
3642 std::reverse(chunks.begin(), chunks.end());
3643 masked = rewriter.createOrFold<CatPrimOp>(loc, chunks);
3644 next = MuxPrimOp::create(rewriter, next.getLoc(), en, masked, next);
3645 }
3646 Value typedNext = rewriter.createOrFold<BitCastOp>(next.getLoc(), ty, next);
3647 MatchingConnectOp::create(rewriter, memReg.getLoc(), memReg, typedNext);
3648
3649 // Delete the fields and their associated connects.
3650 for (Operation *conn : connects)
3651 rewriter.eraseOp(conn);
3652 for (auto portAccess : portAccesses)
3653 rewriter.eraseOp(portAccess);
3654 rewriter.eraseOp(mem);
3655
3656 return success();
3657 }
3658};
3659} // namespace
3660
3661void MemOp::getCanonicalizationPatterns(RewritePatternSet &results,
3662 MLIRContext *context) {
3663 results
3664 .insert<FoldZeroWidthMemory, FoldReadOrWriteOnlyMemory,
3665 FoldReadWritePorts, FoldUnusedPorts, FoldUnusedBits, FoldRegMems>(
3666 context);
3667}
3668
3669//===----------------------------------------------------------------------===//
3670// Declarations
3671//===----------------------------------------------------------------------===//
3672
3673// Turn synchronous reset looking register updates to registers with resets.
3674// Also, const prop registers that are driven by a mux tree containing only
3675// instances of one constant or self-assigns.
3676static LogicalResult foldHiddenReset(RegOp reg, PatternRewriter &rewriter) {
3677 // reg ; connect(reg, mux(port, const, val)) ->
3678 // reg.reset(port, const); connect(reg, val)
3679
3680 // Find the one true connect, or bail
3681 auto con = getSingleConnectUserOf(reg.getResult());
3682 if (!con)
3683 return failure();
3684
3685 auto mux = dyn_cast_or_null<MuxPrimOp>(con.getSrc().getDefiningOp());
3686 if (!mux)
3687 return failure();
3688 auto *high = mux.getHigh().getDefiningOp();
3689 auto *low = mux.getLow().getDefiningOp();
3690 // Reset value must be constant
3691 auto constOp = dyn_cast_or_null<ConstantOp>(high);
3692
3693 // Detect the case if a register only has two possible drivers:
3694 // (1) itself/uninit and (2) constant.
3695 // The mux can then be replaced with the constant.
3696 // r = mux(cond, r, 3) --> r = 3
3697 // r = mux(cond, 3, r) --> r = 3
3698 bool constReg = false;
3699
3700 if (constOp && low == reg)
3701 constReg = true;
3702 else if (dyn_cast_or_null<ConstantOp>(low) && high == reg) {
3703 constReg = true;
3704 constOp = dyn_cast<ConstantOp>(low);
3705 }
3706 if (!constOp)
3707 return failure();
3708
3709 // For a non-constant register, reset should be a module port (heuristic to
3710 // limit to intended reset lines). Replace the register anyway if constant.
3711 if (!isa<BlockArgument>(mux.getSel()) && !constReg)
3712 return failure();
3713
3714 // Check all types should be typed by now
3715 auto regTy = reg.getResult().getType();
3716 if (con.getDest().getType() != regTy || con.getSrc().getType() != regTy ||
3717 mux.getHigh().getType() != regTy || mux.getLow().getType() != regTy ||
3718 regTy.getBitWidthOrSentinel() < 0)
3719 return failure();
3720
3721 // Ok, we know we are doing the transformation.
3722
3723 // If we would fold the register to a constant, ensure the time-zero
3724 // simulation value is preserved.
3725 if (constReg && !preservesInitial(reg.getInitialAttr(), constOp.getValue()))
3726 return failure();
3727
3728 // Make sure the constant dominates all users.
3729 if (constOp != &con->getBlock()->front())
3730 constOp->moveBefore(&con->getBlock()->front());
3731
3732 if (!constReg) {
3733 SmallVector<NamedAttribute, 2> attrs(reg->getDialectAttrs());
3735 rewriter, reg, reg.getResult().getType(), reg.getClockVal(),
3736 mux.getSel(), mux.getHigh(), reg.getNameAttr(), reg.getNameKindAttr(),
3737 reg.getAnnotationsAttr(), reg.getInnerSymAttr(), reg.getForceableAttr(),
3738 reg.getInitialAttr());
3739 newReg->setDialectAttrs(attrs);
3740 }
3741 auto pt = rewriter.saveInsertionPoint();
3742 rewriter.setInsertionPoint(con);
3743 auto v = constReg ? (Value)constOp.getResult() : (Value)mux.getLow();
3744 replaceOpWithNewOpAndCopyName<ConnectOp>(rewriter, con, con.getDest(), v);
3745 rewriter.restoreInsertionPoint(pt);
3746 return success();
3747}
3748
3749LogicalResult RegOp::canonicalize(RegOp op, PatternRewriter &rewriter) {
3750 if (!hasDontTouch(op.getOperation()) && !op.isForceable() &&
3751 succeeded(foldHiddenReset(op, rewriter)))
3752 return success();
3753
3754 if (succeeded(demoteForceableIfUnused(op, rewriter)))
3755 return success();
3756
3757 return failure();
3758}
3759
3760//===----------------------------------------------------------------------===//
3761// Verification Ops.
3762//===----------------------------------------------------------------------===//
3763
3764static LogicalResult eraseIfZeroOrNotZero(Operation *op, Value predicate,
3765 Value enable,
3766 PatternRewriter &rewriter,
3767 bool eraseIfZero) {
3768 // If the verification op is never enabled, delete it.
3769 if (auto constant = enable.getDefiningOp<firrtl::ConstantOp>()) {
3770 if (constant.getValue().isZero()) {
3771 rewriter.eraseOp(op);
3772 return success();
3773 }
3774 }
3775
3776 // If the verification op is never triggered, delete it.
3777 if (auto constant = predicate.getDefiningOp<firrtl::ConstantOp>()) {
3778 if (constant.getValue().isZero() == eraseIfZero) {
3779 rewriter.eraseOp(op);
3780 return success();
3781 }
3782 }
3783
3784 return failure();
3785}
3786
3787template <class Op, bool EraseIfZero = false>
3788static LogicalResult canonicalizeImmediateVerifOp(Op op,
3789 PatternRewriter &rewriter) {
3790 return eraseIfZeroOrNotZero(op, op.getPredicate(), op.getEnable(), rewriter,
3791 EraseIfZero);
3792}
3793
3794void AssertOp::getCanonicalizationPatterns(RewritePatternSet &results,
3795 MLIRContext *context) {
3797 results.add<patterns::AssertXWhenX>(context);
3798}
3799
3800void AssumeOp::getCanonicalizationPatterns(RewritePatternSet &results,
3801 MLIRContext *context) {
3803 results.add<patterns::AssumeXWhenX>(context);
3804}
3805
3806void UnclockedAssumeIntrinsicOp::getCanonicalizationPatterns(
3807 RewritePatternSet &results, MLIRContext *context) {
3809 results.add<patterns::UnclockedAssumeIntrinsicXWhenX>(context);
3810}
3811
3812void CoverOp::getCanonicalizationPatterns(RewritePatternSet &results,
3813 MLIRContext *context) {
3815}
3816
3817//===----------------------------------------------------------------------===//
3818// InvalidValueOp
3819//===----------------------------------------------------------------------===//
3820
3821LogicalResult InvalidValueOp::canonicalize(InvalidValueOp op,
3822 PatternRewriter &rewriter) {
3823 // Remove `InvalidValueOp`s with no uses.
3824 if (op.use_empty()) {
3825 rewriter.eraseOp(op);
3826 return success();
3827 }
3828 // Propagate invalids through a single use which is a unary op. You cannot
3829 // propagate through multiple uses as that breaks invalid semantics. Nor
3830 // can you propagate through binary ops or generally any op which computes.
3831 // Not is an exception as it is a pure, all-bits inverse.
3832 if (op->hasOneUse() &&
3833 (isa<BitsPrimOp, HeadPrimOp, ShrPrimOp, TailPrimOp, SubfieldOp,
3834 SubindexOp, AsSIntPrimOp, AsUIntPrimOp, NotPrimOp, BitCastOp>(
3835 *op->user_begin()) ||
3836 (isa<CvtPrimOp>(*op->user_begin()) &&
3837 type_isa<SIntType>(op->user_begin()->getOperand(0).getType())) ||
3838 (isa<AndRPrimOp, XorRPrimOp, OrRPrimOp>(*op->user_begin()) &&
3839 type_cast<FIRRTLBaseType>(op->user_begin()->getOperand(0).getType())
3840 .getBitWidthOrSentinel() > 0))) {
3841 auto *modop = *op->user_begin();
3842 auto inv = InvalidValueOp::create(rewriter, op.getLoc(),
3843 modop->getResult(0).getType());
3844 rewriter.replaceAllOpUsesWith(modop, inv);
3845 rewriter.eraseOp(modop);
3846 rewriter.eraseOp(op);
3847 return success();
3848 }
3849 return failure();
3850}
3851
3852OpFoldResult InvalidValueOp::fold(FoldAdaptor adaptor) {
3853 if (getType().getBitWidthOrSentinel() == 0 && isa<IntType>(getType()))
3854 return getIntAttr(getType(), APInt(0, 0, isa<SIntType>(getType())));
3855 return {};
3856}
3857
3858//===----------------------------------------------------------------------===//
3859// ClockGateIntrinsicOp
3860//===----------------------------------------------------------------------===//
3861
3862OpFoldResult ClockGateIntrinsicOp::fold(FoldAdaptor adaptor) {
3863 // Forward the clock if one of the enables is always true.
3864 if (isConstantOne(adaptor.getEnable()) ||
3865 isConstantOne(adaptor.getTestEnable()))
3866 return getInput();
3867
3868 // Fold to a constant zero clock if the enables are always false.
3869 if (isConstantZero(adaptor.getEnable()) &&
3870 (!getTestEnable() || isConstantZero(adaptor.getTestEnable())))
3871 return BoolAttr::get(getContext(), false);
3872
3873 // Forward constant zero clocks.
3874 if (isConstantZero(adaptor.getInput()))
3875 return BoolAttr::get(getContext(), false);
3876
3877 return {};
3878}
3879
3880LogicalResult ClockGateIntrinsicOp::canonicalize(ClockGateIntrinsicOp op,
3881 PatternRewriter &rewriter) {
3882 // Remove constant false test enable.
3883 if (auto testEnable = op.getTestEnable()) {
3884 if (auto constOp = testEnable.getDefiningOp<ConstantOp>()) {
3885 if (constOp.getValue().isZero()) {
3886 rewriter.modifyOpInPlace(op,
3887 [&] { op.getTestEnableMutable().clear(); });
3888 return success();
3889 }
3890 }
3891 }
3892
3893 return failure();
3894}
3895
3896//===----------------------------------------------------------------------===//
3897// Reference Ops.
3898//===----------------------------------------------------------------------===//
3899
3900// refresolve(forceable.ref) -> forceable.data
3901static LogicalResult
3902canonicalizeRefResolveOfForceable(RefResolveOp op, PatternRewriter &rewriter) {
3903 auto forceable = op.getRef().getDefiningOp<Forceable>();
3904 if (!forceable || !forceable.isForceable() ||
3905 op.getRef() != forceable.getDataRef() ||
3906 op.getType() != forceable.getDataType())
3907 return failure();
3908 rewriter.replaceAllUsesWith(op, forceable.getData());
3909 return success();
3910}
3911
3912void RefResolveOp::getCanonicalizationPatterns(RewritePatternSet &results,
3913 MLIRContext *context) {
3914 results.insert<patterns::RefResolveOfRefSend>(context);
3915 results.insert(canonicalizeRefResolveOfForceable);
3916}
3917
3918OpFoldResult RefCastOp::fold(FoldAdaptor adaptor) {
3919 // RefCast is unnecessary if types match.
3920 if (getInput().getType() == getType())
3921 return getInput();
3922 return {};
3923}
3924
3925static bool isConstantZero(Value operand) {
3926 auto constOp = operand.getDefiningOp<ConstantOp>();
3927 return constOp && constOp.getValue().isZero();
3928}
3929
3930template <typename Op>
3931static LogicalResult eraseIfPredFalse(Op op, PatternRewriter &rewriter) {
3932 if (isConstantZero(op.getPredicate())) {
3933 rewriter.eraseOp(op);
3934 return success();
3935 }
3936 return failure();
3937}
3938
3939void RefForceOp::getCanonicalizationPatterns(RewritePatternSet &results,
3940 MLIRContext *context) {
3941 results.add(eraseIfPredFalse<RefForceOp>);
3942}
3943void RefForceInitialOp::getCanonicalizationPatterns(RewritePatternSet &results,
3944 MLIRContext *context) {
3946}
3947void RefReleaseOp::getCanonicalizationPatterns(RewritePatternSet &results,
3948 MLIRContext *context) {
3949 results.add(eraseIfPredFalse<RefReleaseOp>);
3950}
3951void RefReleaseInitialOp::getCanonicalizationPatterns(
3952 RewritePatternSet &results, MLIRContext *context) {
3954}
3955
3956//===----------------------------------------------------------------------===//
3957// HasBeenResetIntrinsicOp
3958//===----------------------------------------------------------------------===//
3959
3960OpFoldResult HasBeenResetIntrinsicOp::fold(FoldAdaptor adaptor) {
3961 // The folds in here should reflect the ones for `verif::HasBeenResetOp`.
3962
3963 // Fold to zero if the reset is a constant. In this case the op is either
3964 // permanently in reset or never resets. Both mean that the reset never
3965 // finishes, so this op never returns true.
3966 if (adaptor.getReset())
3967 return getIntZerosAttr(UIntType::get(getContext(), 1));
3968
3969 // Fold to zero if the clock is a constant and the reset is synchronous. In
3970 // that case the reset will never be started.
3971 if (isUInt1(getReset().getType()) && adaptor.getClock())
3972 return getIntZerosAttr(UIntType::get(getContext(), 1));
3973
3974 return {};
3975}
3976
3977//===----------------------------------------------------------------------===//
3978// FPGAProbeIntrinsicOp
3979//===----------------------------------------------------------------------===//
3980
3981static bool isTypeEmpty(FIRRTLType type) {
3983 .Case<FVectorType>(
3984 [&](auto ty) -> bool { return isTypeEmpty(ty.getElementType()); })
3985 .Case<BundleType>([&](auto ty) -> bool {
3986 for (auto elem : ty.getElements())
3987 if (!isTypeEmpty(elem.type))
3988 return false;
3989 return true;
3990 })
3991 .Case<IntType>([&](auto ty) { return ty.getWidth() == 0; })
3992 .Default([](auto) -> bool { return false; });
3993}
3994
3995LogicalResult FPGAProbeIntrinsicOp::canonicalize(FPGAProbeIntrinsicOp op,
3996 PatternRewriter &rewriter) {
3997 auto firrtlTy = type_dyn_cast<FIRRTLType>(op.getInput().getType());
3998 if (!firrtlTy)
3999 return failure();
4000
4001 if (!isTypeEmpty(firrtlTy))
4002 return failure();
4003
4004 rewriter.eraseOp(op);
4005 return success();
4006}
4007
4008//===----------------------------------------------------------------------===//
4009// Layer Block Op
4010//===----------------------------------------------------------------------===//
4011
4012LogicalResult LayerBlockOp::canonicalize(LayerBlockOp op,
4013 PatternRewriter &rewriter) {
4014
4015 // If the layerblock is empty, erase it.
4016 if (op.getBody()->empty()) {
4017 rewriter.eraseOp(op);
4018 return success();
4019 }
4020
4021 return failure();
4022}
4023
4024//===----------------------------------------------------------------------===//
4025// Domain-related Ops
4026//===----------------------------------------------------------------------===//
4027
4028OpFoldResult UnsafeDomainCastOp::fold(FoldAdaptor adaptor) {
4029 // If no domains are specified, then forward the input to the result.
4030 if (getDomains().empty())
4031 return getInput();
4032
4033 return {};
4034}
assert(baseType &&"element must be base type")
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:555
static std::unique_ptr< Context > context
static bool hasKnownWidthIntTypes(Operation *op)
Return true if this operation's operands and results all have a known width.
static LogicalResult canonicalizeImmediateVerifOp(Op op, PatternRewriter &rewriter)
static IntegerAttr foldFIntegerBinaryBitwise(MLIRContext *context, Attribute lhsAttr, Attribute rhsAttr, const function_ref< APInt(const APInt &, const APInt &)> &evaluate)
Fold bitwise operations on FIRRTL arbitrary precision signed integer properties.
static bool isDefinedByOneConstantOp(Value v)
static Attribute collectFields(MLIRContext *context, ArrayRef< Attribute > operands)
static LogicalResult canonicalizeSingleSetConnect(MatchingConnectOp op, PatternRewriter &rewriter)
static void erasePort(PatternRewriter &rewriter, Value port)
static void replaceOpWithRegion(PatternRewriter &rewriter, Operation *op, Region &region)
Replaces the given op with the contents of the given single-block region.
static std::optional< APSInt > getExtendedConstant(Value operand, Attribute constant, int32_t destWidth)
Implicitly replace the operand to a constant folding operation with a const 0 in case the operand is ...
static Value getPortFieldValue(Value port, StringRef name)
static AttachOp getDominatingAttachUser(Value value, AttachOp dominatedAttach)
If the specified value has an AttachOp user strictly dominating by "dominatingAttach" then return it.
static OpTy replaceOpWithNewOpAndCopyName(PatternRewriter &rewriter, Operation *op, Args &&...args)
A wrapper of PatternRewriter::replaceOpWithNewOp to propagate "name" attribute.
static void updateName(PatternRewriter &rewriter, Operation *op, StringAttr name)
Set the name of an op based on the best of two names: The current name, and the name passed in.
static bool isTypeEmpty(FIRRTLType type)
static bool isUInt1(Type type)
Return true if this value is 1 bit UInt.
static LogicalResult demoteForceableIfUnused(OpTy op, PatternRewriter &rewriter)
static bool isPortDisabled(Value port)
static LogicalResult eraseIfZeroOrNotZero(Operation *op, Value predicate, Value enable, PatternRewriter &rewriter, bool eraseIfZero)
static APInt getMaxSignedValue(unsigned bitWidth)
Get the largest signed value of a given bit width.
static Value dropWrite(PatternRewriter &rewriter, OpResult old, Value passthrough)
static LogicalResult canonicalizePrimOp(Operation *op, PatternRewriter &rewriter, const function_ref< OpFoldResult(ArrayRef< Attribute >)> &canonicalize)
Applies the canonicalization function canonicalize to the given operation.
static void replaceWithBits(Operation *op, Value value, unsigned hiBit, unsigned loBit, PatternRewriter &rewriter)
Replace the specified operation with a 'bits' op from the specified hi/lo bits.
static std::optional< bool > getBoolValue(Attribute attr)
static LogicalResult canonicalizeRegResetWithOneReset(RegResetOp reg, PatternRewriter &rewriter)
static LogicalResult eraseIfPredFalse(Op op, PatternRewriter &rewriter)
static OpFoldResult foldMux(OpTy op, typename OpTy::FoldAdaptor adaptor)
static APInt getMaxUnsignedValue(unsigned bitWidth)
Get the largest unsigned value of a given bit width.
static std::optional< APSInt > getConstant(Attribute operand)
Determine the value of a constant operand for the sake of constant folding.
static void replacePortField(PatternRewriter &rewriter, Value port, StringRef name, Value value)
BinOpKind
This is the policy for folding, which depends on the sort of operator we're processing.
static bool isPortUnused(Value port, StringRef data)
static bool isOkToPropagateName(Operation *op)
static LogicalResult canonicalizeRefResolveOfForceable(RefResolveOp op, PatternRewriter &rewriter)
static Attribute constFoldFIRRTLBinaryOp(Operation *op, ArrayRef< Attribute > operands, BinOpKind opKind, const function_ref< APInt(const APSInt &, const APSInt &)> &calculate)
Applies the constant folding function calculate to the given operands.
static APInt getMinSignedValue(unsigned bitWidth)
Get the smallest signed value of a given bit width.
static LogicalResult foldHiddenReset(RegOp reg, PatternRewriter &rewriter)
static Value moveNameHint(OpResult old, Value passthrough)
static void replaceOpAndCopyName(PatternRewriter &rewriter, Operation *op, Value newValue)
A wrapper of PatternRewriter::replaceOp to propagate "name" attribute.
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
if(parseOptionalKeywordOrOptionalString(parser, result.rawName, found)) return failure()
return success()
NamedAttrList attrs
static InstancePath empty
AndRCat(MLIRContext *context)
bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op, ConstantOp value, SmallVectorImpl< Value > &remaining) const override
Handle a constant operand in the cat operation.
bool getIdentityValue() const override
Return the unit value for this reduction operation:
bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op, ConstantOp value, SmallVectorImpl< Value > &remaining) const override
Handle a constant operand in the cat operation.
OrRCat(MLIRContext *context)
bool getIdentityValue() const override
Return the unit value for this reduction operation:
virtual bool getIdentityValue() const =0
Return the unit value for this reduction operation:
virtual bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op, ConstantOp constantOp, SmallVectorImpl< Value > &remaining) const =0
Handle a constant operand in the cat operation.
LogicalResult matchAndRewrite(Operation *op, mlir::PatternRewriter &rewriter) const override
ReductionCat(MLIRContext *context, llvm::StringLiteral opName)
XorRCat(MLIRContext *context)
bool handleConstant(mlir::PatternRewriter &rewriter, Operation *op, ConstantOp value, SmallVectorImpl< Value > &remaining) const override
Handle a constant operand in the cat operation.
bool getIdentityValue() const override
Return the unit value for this reduction operation:
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
This is the common base class between SIntType and UIntType.
int32_t getWidthOrSentinel() const
Return the width of this type, or -1 if it has none specified.
static IntType get(MLIRContext *context, bool isSigned, int32_t widthOrSentinel=-1, bool isConst=false)
Return an SIntType or UIntType with the specified signedness, width, and constness.
bool hasWidth() const
Return true if this integer type has a known width.
std::optional< int32_t > getWidth() const
Return an optional containing the width, if the width is known (or empty if width is unknown).
uint64_t getWidth(Type t)
Definition ESIPasses.cpp:30
Forceable replaceWithNewForceability(Forceable op, bool forceable, ::mlir::PatternRewriter *rewriter=nullptr)
Replace a Forceable op with equivalent, changing whether forceable.
BaseTy type_cast(Type type)
bool areAnonymousTypesEquivalent(FIRRTLBaseType lhs, FIRRTLBaseType rhs)
Return true if anonymous types of given arguments are equivalent by pointer comparison.
IntegerAttr getIntAttr(Type type, const APInt &value)
Utiility for generating a constant attribute.
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
bool hasDroppableName(Operation *op)
Return true if the name is droppable.
bool preservesInitial(IntegerAttr initial, std::optional< APInt > foldedValue=std::nullopt)
Return true if replacing a register carrying the time-zero initial value with foldedValue does not ch...
BaseTy type_dyn_cast(Type type)
MatchingConnectOp getSingleConnectUserOf(Value value)
Scan all the uses of the specified value, checking to see if there is exactly one connect that has th...
bool type_isa(Type type)
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
BaseTy type_cast(Type type)
Definition HWTypes.h:222
BaseTy type_dyn_cast(Type type)
Definition HWTypes.h:234
bool type_isa(Type type)
Definition HWTypes.h:201
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
APSInt extOrTruncZeroWidth(APSInt value, unsigned width)
A safe version of APSInt::extOrTrunc that will NOT assert on zero-width signed APSInts.
Definition APInt.cpp:22
APInt sextZeroWidth(APInt value, unsigned width)
A safe version of APInt::sext that will NOT assert on zero-width signed APSInts.
Definition APInt.cpp:18
StringRef chooseName(StringRef a, StringRef b)
Choose a good name for an item from two options.
Definition Naming.cpp:47
static bool isConstantZero(Attribute operand)
Determine whether a constant operand is a zero value.
Definition FoldUtils.h:28
static bool isConstantOne(Attribute operand)
Determine whether a constant operand is a one value.
Definition FoldUtils.h:35
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
LogicalResult matchAndRewrite(BitsPrimOp bits, mlir::PatternRewriter &rewriter) const override