CIRCT 22.0.0git
Loading...
Searching...
No Matches
CombOps.cpp
Go to the documentation of this file.
1//===- CombOps.cpp - Implement the Comb operations ------------------------===//
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 implements combinational ops.
10//
11//===----------------------------------------------------------------------===//
12
16#include "mlir/IR/Builders.h"
17#include "mlir/IR/ImplicitLocOpBuilder.h"
18#include "mlir/IR/Matchers.h"
19#include "mlir/IR/PatternMatch.h"
20#include "llvm/Support/FormatVariadic.h"
21
22using namespace circt;
23using namespace comb;
24
25Value comb::createZExt(OpBuilder &builder, Location loc, Value value,
26 unsigned targetWidth) {
27 assert(value.getType().isSignlessInteger());
28 auto inputWidth = value.getType().getIntOrFloatBitWidth();
29 assert(inputWidth <= targetWidth);
30
31 // Nothing to do if the width already matches.
32 if (inputWidth == targetWidth)
33 return value;
34
35 // Create a zero constant for the upper bits.
36 auto zeros = hw::ConstantOp::create(
37 builder, loc, builder.getIntegerType(targetWidth - inputWidth), 0);
38 return builder.createOrFold<ConcatOp>(loc, zeros, value);
39}
40
41/// Create a sign extension operation from a value of integer type to an equal
42/// or larger integer type.
43Value comb::createOrFoldSExt(Location loc, Value value, Type destTy,
44 OpBuilder &builder) {
45 IntegerType valueType = dyn_cast<IntegerType>(value.getType());
46 assert(valueType && isa<IntegerType>(destTy) &&
47 valueType.getWidth() <= destTy.getIntOrFloatBitWidth() &&
48 valueType.getWidth() != 0 && "invalid sext operands");
49 // If already the right size, we are done.
50 if (valueType == destTy)
51 return value;
52
53 // sext is concat with a replicate of the sign bits and the bottom part.
54 auto signBit =
55 builder.createOrFold<ExtractOp>(loc, value, valueType.getWidth() - 1, 1);
56 auto signBits = builder.createOrFold<ReplicateOp>(
57 loc, signBit, destTy.getIntOrFloatBitWidth() - valueType.getWidth());
58 return builder.createOrFold<ConcatOp>(loc, signBits, value);
59}
60
61Value comb::createOrFoldSExt(Value value, Type destTy,
62 ImplicitLocOpBuilder &builder) {
63 return createOrFoldSExt(builder.getLoc(), value, destTy, builder);
64}
65
66Value comb::createOrFoldNot(Location loc, Value value, OpBuilder &builder,
67 bool twoState) {
68 auto allOnes = hw::ConstantOp::create(builder, loc, value.getType(), -1);
69 return builder.createOrFold<XorOp>(loc, value, allOnes, twoState);
70}
71
72Value comb::createOrFoldNot(Value value, ImplicitLocOpBuilder &builder,
73 bool twoState) {
74 return createOrFoldNot(builder.getLoc(), value, builder, twoState);
75}
76
77// Extract individual bits from a value
78void comb::extractBits(OpBuilder &builder, Value val,
79 SmallVectorImpl<Value> &bits) {
80 assert(val.getType().isInteger() && "expected integer");
81 auto width = val.getType().getIntOrFloatBitWidth();
82 bits.reserve(width);
83
84 // Check if we can reuse concat operands
85 if (auto concat = val.getDefiningOp<comb::ConcatOp>()) {
86 if (concat.getNumOperands() == width &&
87 llvm::all_of(concat.getOperandTypes(), [](Type type) {
88 return type.getIntOrFloatBitWidth() == 1;
89 })) {
90 // Reverse the operands to match the bit order
91 bits.append(std::make_reverse_iterator(concat.getOperands().end()),
92 std::make_reverse_iterator(concat.getOperands().begin()));
93 return;
94 }
95 }
96
97 // Extract individual bits
98 for (int64_t i = 0; i < width; ++i)
99 bits.push_back(
100 builder.createOrFold<comb::ExtractOp>(val.getLoc(), val, i, 1));
101}
102
103// Construct a mux tree for given leaf nodes. `selectors` is the selector for
104// each level of the tree. Currently the selector is tested from MSB to LSB.
105Value comb::constructMuxTree(OpBuilder &builder, Location loc,
106 ArrayRef<Value> selectors,
107 ArrayRef<Value> leafNodes,
108 Value outOfBoundsValue) {
109 // Recursive helper function to construct the mux tree
110 std::function<Value(size_t, size_t)> constructTreeHelper =
111 [&](size_t id, size_t level) -> Value {
112 // Base case: at the lowest level, return the result
113 if (level == 0) {
114 // Return the result for the given index. If the index is out of bounds,
115 // return the out-of-bound value.
116 return id < leafNodes.size() ? leafNodes[id] : outOfBoundsValue;
117 }
118
119 auto selector = selectors[level - 1];
120
121 // Recursive case: create muxes for true and false branches
122 auto trueVal = constructTreeHelper(2 * id + 1, level - 1);
123 auto falseVal = constructTreeHelper(2 * id, level - 1);
124
125 // Combine the results with a mux
126 return builder.createOrFold<comb::MuxOp>(loc, selector, trueVal, falseVal);
127 };
128
129 return constructTreeHelper(0, llvm::Log2_64_Ceil(leafNodes.size()));
130}
131
132Value comb::createDynamicExtract(OpBuilder &builder, Location loc, Value value,
133 Value offset, unsigned width) {
134 assert(value.getType().isSignlessInteger());
135 auto valueWidth = value.getType().getIntOrFloatBitWidth();
136 assert(width <= valueWidth);
137
138 // Handle the special case where the offset is constant.
139 APInt constOffset;
140 if (matchPattern(offset, mlir::m_ConstantInt(&constOffset)))
141 if (constOffset.getActiveBits() < 32)
142 return builder.createOrFold<comb::ExtractOp>(
143 loc, value, constOffset.getZExtValue(), width);
144
145 // Zero-extend the offset, shift the value down, and extract the requested
146 // number of bits.
147 offset = createZExt(builder, loc, offset, valueWidth);
148 value = builder.createOrFold<comb::ShrUOp>(loc, value, offset);
149 return builder.createOrFold<comb::ExtractOp>(loc, value, 0, width);
150}
151
152Value comb::createDynamicInject(OpBuilder &builder, Location loc, Value value,
153 Value offset, Value replacement,
154 bool twoState) {
155 assert(value.getType().isSignlessInteger());
156 assert(replacement.getType().isSignlessInteger());
157 auto largeWidth = value.getType().getIntOrFloatBitWidth();
158 auto smallWidth = replacement.getType().getIntOrFloatBitWidth();
159 assert(smallWidth <= largeWidth);
160
161 // If we're inserting a zero-width value there's nothing to do.
162 if (smallWidth == 0)
163 return value;
164
165 // Handle the special case where the offset is constant.
166 APInt constOffset;
167 if (matchPattern(offset, mlir::m_ConstantInt(&constOffset)))
168 if (constOffset.getActiveBits() < 32)
169 return createInject(builder, loc, value, constOffset.getZExtValue(),
170 replacement);
171
172 // Zero-extend the offset and clear the value bits we are replacing.
173 offset = createZExt(builder, loc, offset, largeWidth);
174 Value mask = hw::ConstantOp::create(
175 builder, loc, APInt::getLowBitsSet(largeWidth, smallWidth));
176 mask = builder.createOrFold<comb::ShlOp>(loc, mask, offset);
177 mask = createOrFoldNot(loc, mask, builder, true);
178 value = builder.createOrFold<comb::AndOp>(loc, value, mask, twoState);
179
180 // Zero-extend the replacement value, shift it up to the offset, and merge it
181 // with the value that has the corresponding bits cleared.
182 replacement = createZExt(builder, loc, replacement, largeWidth);
183 replacement = builder.createOrFold<comb::ShlOp>(loc, replacement, offset);
184 return builder.createOrFold<comb::OrOp>(loc, value, replacement, twoState);
185}
186
187Value comb::createInject(OpBuilder &builder, Location loc, Value value,
188 unsigned offset, Value replacement) {
189 assert(value.getType().isSignlessInteger());
190 assert(replacement.getType().isSignlessInteger());
191 auto largeWidth = value.getType().getIntOrFloatBitWidth();
192 auto smallWidth = replacement.getType().getIntOrFloatBitWidth();
193 assert(smallWidth <= largeWidth);
194
195 // If the offset is outside the value there's nothing to do.
196 if (offset >= largeWidth)
197 return value;
198
199 // If we're inserting a zero-width value there's nothing to do.
200 if (smallWidth == 0)
201 return value;
202
203 // Assemble the pieces of the injection as everything below the offset, the
204 // replacement value, and everything above the replacement value.
205 SmallVector<Value, 3> fragments;
206 auto end = offset + smallWidth;
207 if (end < largeWidth)
208 fragments.push_back(
209 comb::ExtractOp::create(builder, loc, value, end, largeWidth - end));
210 if (end <= largeWidth)
211 fragments.push_back(replacement);
212 else
213 fragments.push_back(comb::ExtractOp::create(builder, loc, replacement, 0,
214 largeWidth - offset));
215 if (offset > 0)
216 fragments.push_back(
217 comb::ExtractOp::create(builder, loc, value, 0, offset));
218 return builder.createOrFold<comb::ConcatOp>(loc, fragments);
219}
220
221llvm::LogicalResult comb::convertSubToAdd(comb::SubOp subOp,
222 mlir::PatternRewriter &rewriter) {
223 auto lhs = subOp.getLhs();
224 auto rhs = subOp.getRhs();
225 // Since `-rhs = ~rhs + 1` holds, rewrite `sub(lhs, rhs)` to:
226 // sub(lhs, rhs) => add(lhs, -rhs) => add(lhs, add(~rhs, 1))
227 // => add(lhs, ~rhs, 1)
228 auto notRhs =
229 comb::createOrFoldNot(subOp.getLoc(), rhs, rewriter, subOp.getTwoState());
230 auto one =
231 hw::ConstantOp::create(rewriter, subOp.getLoc(), subOp.getType(), 1);
232 replaceOpWithNewOpAndCopyNamehint<comb::AddOp>(
233 rewriter, subOp, ValueRange{lhs, notRhs, one}, subOp.getTwoState());
234 return success();
235}
236
237static llvm::LogicalResult convertDivModUByPowerOfTwo(PatternRewriter &rewriter,
238 Operation *op, Value lhs,
239 Value rhs, bool isDiv) {
240 // Check if the divisor is a power of two constant.
241 auto rhsConstantOp = rhs.getDefiningOp<hw::ConstantOp>();
242 if (!rhsConstantOp)
243 return failure();
244
245 APInt rhsValue = rhsConstantOp.getValue();
246 if (!rhsValue.isPowerOf2())
247 return failure();
248
249 Location loc = op->getLoc();
250
251 unsigned width = lhs.getType().getIntOrFloatBitWidth();
252 unsigned bitPosition = rhsValue.ceilLogBase2();
253
254 if (isDiv) {
255 // divu(x, 2^n) -> concat(0...0, extract(x, n, width-n))
256 // This is equivalent to a right shift by n bits.
257
258 // Extract the upper bits (equivalent to right shift).
259 Value upperBits = rewriter.createOrFold<comb::ExtractOp>(
260 loc, lhs, bitPosition, width - bitPosition);
261
262 // Concatenate with zeros on the left.
263 Value zeros =
264 hw::ConstantOp::create(rewriter, loc, APInt::getZero(bitPosition));
265
266 // use replaceOpWithNewOpAndCopyNamehint?
268 rewriter, op,
269 comb::ConcatOp::create(rewriter, loc,
270 ArrayRef<Value>{zeros, upperBits}));
271 return success();
272 }
273
274 // modu(x, 2^n) -> concat(0...0, extract(x, 0, n))
275 // This extracts the lower n bits (equivalent to bitwise AND with 2^n - 1).
276
277 // Extract the lower bits.
278 Value lowerBits =
279 rewriter.createOrFold<comb::ExtractOp>(loc, lhs, 0, bitPosition);
280
281 // Concatenate with zeros on the left.
282 Value zeros = hw::ConstantOp::create(rewriter, loc,
283 APInt::getZero(width - bitPosition));
284
286 rewriter, op,
287 comb::ConcatOp::create(rewriter, loc, ArrayRef<Value>{zeros, lowerBits}));
288 return success();
289}
290
291LogicalResult comb::convertDivUByPowerOfTwo(DivUOp divOp,
292 mlir::PatternRewriter &rewriter) {
293 return convertDivModUByPowerOfTwo(rewriter, divOp, divOp.getLhs(),
294 divOp.getRhs(), /*isDiv=*/true);
295}
296
297LogicalResult comb::convertModUByPowerOfTwo(ModUOp modOp,
298 mlir::PatternRewriter &rewriter) {
299 return convertDivModUByPowerOfTwo(rewriter, modOp, modOp.getLhs(),
300 modOp.getRhs(), /*isDiv=*/false);
301}
302
303//===----------------------------------------------------------------------===//
304// ICmpOp
305//===----------------------------------------------------------------------===//
306
307ICmpPredicate ICmpOp::getFlippedPredicate(ICmpPredicate predicate) {
308 switch (predicate) {
309 case ICmpPredicate::eq:
310 return ICmpPredicate::eq;
311 case ICmpPredicate::ne:
312 return ICmpPredicate::ne;
313 case ICmpPredicate::slt:
314 return ICmpPredicate::sgt;
315 case ICmpPredicate::sle:
316 return ICmpPredicate::sge;
317 case ICmpPredicate::sgt:
318 return ICmpPredicate::slt;
319 case ICmpPredicate::sge:
320 return ICmpPredicate::sle;
321 case ICmpPredicate::ult:
322 return ICmpPredicate::ugt;
323 case ICmpPredicate::ule:
324 return ICmpPredicate::uge;
325 case ICmpPredicate::ugt:
326 return ICmpPredicate::ult;
327 case ICmpPredicate::uge:
328 return ICmpPredicate::ule;
329 case ICmpPredicate::ceq:
330 return ICmpPredicate::ceq;
331 case ICmpPredicate::cne:
332 return ICmpPredicate::cne;
333 case ICmpPredicate::weq:
334 return ICmpPredicate::weq;
335 case ICmpPredicate::wne:
336 return ICmpPredicate::wne;
337 }
338 llvm_unreachable("unknown comparison predicate");
339}
340
341bool ICmpOp::isPredicateSigned(ICmpPredicate predicate) {
342 switch (predicate) {
343 case ICmpPredicate::ult:
344 case ICmpPredicate::ugt:
345 case ICmpPredicate::ule:
346 case ICmpPredicate::uge:
347 case ICmpPredicate::ne:
348 case ICmpPredicate::eq:
349 case ICmpPredicate::cne:
350 case ICmpPredicate::ceq:
351 case ICmpPredicate::wne:
352 case ICmpPredicate::weq:
353 return false;
354 case ICmpPredicate::slt:
355 case ICmpPredicate::sgt:
356 case ICmpPredicate::sle:
357 case ICmpPredicate::sge:
358 return true;
359 }
360 llvm_unreachable("unknown comparison predicate");
361}
362
363/// Returns the predicate for a logically negated comparison, e.g. mapping
364/// EQ => NE and SLE => SGT.
365ICmpPredicate ICmpOp::getNegatedPredicate(ICmpPredicate predicate) {
366 switch (predicate) {
367 case ICmpPredicate::eq:
368 return ICmpPredicate::ne;
369 case ICmpPredicate::ne:
370 return ICmpPredicate::eq;
371 case ICmpPredicate::slt:
372 return ICmpPredicate::sge;
373 case ICmpPredicate::sle:
374 return ICmpPredicate::sgt;
375 case ICmpPredicate::sgt:
376 return ICmpPredicate::sle;
377 case ICmpPredicate::sge:
378 return ICmpPredicate::slt;
379 case ICmpPredicate::ult:
380 return ICmpPredicate::uge;
381 case ICmpPredicate::ule:
382 return ICmpPredicate::ugt;
383 case ICmpPredicate::ugt:
384 return ICmpPredicate::ule;
385 case ICmpPredicate::uge:
386 return ICmpPredicate::ult;
387 case ICmpPredicate::ceq:
388 return ICmpPredicate::cne;
389 case ICmpPredicate::cne:
390 return ICmpPredicate::ceq;
391 case ICmpPredicate::weq:
392 return ICmpPredicate::wne;
393 case ICmpPredicate::wne:
394 return ICmpPredicate::weq;
395 }
396 llvm_unreachable("unknown comparison predicate");
397}
398
399/// Return true if this is an equality test with -1, which is a "reduction
400/// and" operation in Verilog.
401bool ICmpOp::isEqualAllOnes() {
402 if (getPredicate() != ICmpPredicate::eq)
403 return false;
404
405 if (auto op1 =
406 dyn_cast_or_null<hw::ConstantOp>(getOperand(1).getDefiningOp()))
407 return op1.getValue().isAllOnes();
408 return false;
409}
410
411/// Return true if this is a not equal test with 0, which is a "reduction
412/// or" operation in Verilog.
413bool ICmpOp::isNotEqualZero() {
414 if (getPredicate() != ICmpPredicate::ne)
415 return false;
416
417 if (auto op1 =
418 dyn_cast_or_null<hw::ConstantOp>(getOperand(1).getDefiningOp()))
419 return op1.getValue().isZero();
420 return false;
421}
422
423//===----------------------------------------------------------------------===//
424// Unary Operations
425//===----------------------------------------------------------------------===//
426
427LogicalResult ReplicateOp::verify() {
428 // The source must be equal or smaller than the dest type, and an even
429 // multiple of it. Both are already known to be signless integers.
430 auto srcWidth = cast<IntegerType>(getOperand().getType()).getWidth();
431 auto dstWidth = cast<IntegerType>(getType()).getWidth();
432 if (srcWidth == 0)
433 return emitOpError("replicate does not take zero bit integer");
434
435 if (srcWidth > dstWidth)
436 return emitOpError("replicate cannot shrink bitwidth of operand"),
437 failure();
438
439 if (dstWidth % srcWidth)
440 return emitOpError("replicate must produce integer multiple of operand"),
441 failure();
442
443 return success();
444}
445
446//===----------------------------------------------------------------------===//
447// Variadic operations
448//===----------------------------------------------------------------------===//
449
450static LogicalResult verifyUTBinOp(Operation *op) {
451 if (op->getOperands().empty())
452 return op->emitOpError("requires 1 or more args");
453 return success();
454}
455
456LogicalResult AddOp::verify() { return verifyUTBinOp(*this); }
457
458LogicalResult MulOp::verify() { return verifyUTBinOp(*this); }
459
460LogicalResult AndOp::verify() { return verifyUTBinOp(*this); }
461
462LogicalResult OrOp::verify() { return verifyUTBinOp(*this); }
463
464LogicalResult XorOp::verify() { return verifyUTBinOp(*this); }
465
466/// Return true if this is a two operand xor with an all ones constant as
467/// its RHS operand.
468bool XorOp::isBinaryNot() {
469 if (getNumOperands() != 2)
470 return false;
471 if (auto cst = getOperand(1).getDefiningOp<hw::ConstantOp>())
472 if (cst.getValue().isAllOnes())
473 return true;
474 return false;
475}
476
477//===----------------------------------------------------------------------===//
478// ConcatOp
479//===----------------------------------------------------------------------===//
480
481static unsigned getTotalWidth(ValueRange inputs) {
482 unsigned resultWidth = 0;
483 for (auto input : inputs) {
484 resultWidth += hw::type_cast<IntegerType>(input.getType()).getWidth();
485 }
486 return resultWidth;
487}
488
489void ConcatOp::build(OpBuilder &builder, OperationState &result, Value hd,
490 ValueRange tl) {
491 result.addOperands(ValueRange{hd});
492 result.addOperands(tl);
493 unsigned hdWidth = cast<IntegerType>(hd.getType()).getWidth();
494 result.addTypes(builder.getIntegerType(getTotalWidth(tl) + hdWidth));
495}
496
497LogicalResult ConcatOp::inferReturnTypes(
498 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
499 DictionaryAttr attrs, mlir::OpaqueProperties properties,
500 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
501 unsigned resultWidth = getTotalWidth(operands);
502 results.push_back(IntegerType::get(context, resultWidth));
503 return success();
504}
505
506//===----------------------------------------------------------------------===//
507// ReverseOp
508//===----------------------------------------------------------------------===//
509
510// Folding of ReverseOp: if the input is constant, compute the reverse at
511// compile time.
512OpFoldResult comb::ReverseOp::fold(FoldAdaptor adaptor) {
513 // Try to cast the input attribute to an IntegerAttr.
514 auto cstInput = llvm::dyn_cast_or_null<mlir::IntegerAttr>(adaptor.getInput());
515 if (!cstInput)
516 return {};
517
518 APInt val = cstInput.getValue();
519 APInt reversedVal = val.reverseBits();
520
521 return mlir::IntegerAttr::get(getType(), reversedVal);
522}
523
524namespace {
525struct ReverseOfReverse : public OpRewritePattern<comb::ReverseOp> {
526 using OpRewritePattern<comb::ReverseOp>::OpRewritePattern;
527
528 LogicalResult matchAndRewrite(comb::ReverseOp op,
529 PatternRewriter &rewriter) const override {
530 auto inputOp = op.getInput().getDefiningOp<comb::ReverseOp>();
531 if (!inputOp)
532 return failure();
533
534 rewriter.replaceOp(op, inputOp.getInput());
535 return success();
536 }
537};
538} // namespace
539
540void comb::ReverseOp::getCanonicalizationPatterns(RewritePatternSet &results,
541 MLIRContext *context) {
542 results.add<ReverseOfReverse>(context);
543}
544
545//===----------------------------------------------------------------------===//
546// Other Operations
547//===----------------------------------------------------------------------===//
548
549LogicalResult ExtractOp::verify() {
550 unsigned srcWidth = cast<IntegerType>(getInput().getType()).getWidth();
551 unsigned dstWidth = cast<IntegerType>(getType()).getWidth();
552 if (getLowBit() >= srcWidth || srcWidth - getLowBit() < dstWidth)
553 return emitOpError("from bit too large for input"), failure();
554
555 return success();
556}
557
558LogicalResult TruthTableOp::verify() {
559 size_t numInputs = getInputs().size();
560 if (numInputs >= sizeof(size_t) * 8)
561 return emitOpError("Truth tables support a maximum of ")
562 << sizeof(size_t) * 8 - 1 << " inputs on your platform";
563
564 ArrayAttr table = getLookupTable();
565 if (table.size() != (1ull << numInputs))
566 return emitOpError("Expected lookup table of 2^n length");
567 return success();
568}
569
570//===----------------------------------------------------------------------===//
571// TableGen generated logic.
572//===----------------------------------------------------------------------===//
573
574// Provide the autogenerated implementation guts for the Op classes.
575#define GET_OP_CLASSES
576#include "circt/Dialect/Comb/Comb.cpp.inc"
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:540
static size_t getTotalWidth(ArrayRef< Value > operands)
static LogicalResult verifyUTBinOp(Operation *op)
Definition CombOps.cpp:450
static llvm::LogicalResult convertDivModUByPowerOfTwo(PatternRewriter &rewriter, Operation *op, Value lhs, Value rhs, bool isDiv)
Definition CombOps.cpp:237
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:433
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition CombOps.cpp:66
Value createInject(OpBuilder &builder, Location loc, Value value, unsigned offset, Value replacement)
Replace a range of bits in an integer and return the updated integer value.
Definition CombOps.cpp:187
Value createOrFoldSExt(Location loc, Value value, Type destTy, OpBuilder &builder)
Create a sign extension operation from a value of integer type to an equal or larger integer type.
Definition CombOps.cpp:43
Value createZExt(OpBuilder &builder, Location loc, Value value, unsigned targetWidth)
Create the ops to zero-extend a value to an integer of equal or larger type.
Definition CombOps.cpp:25
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void replaceOpAndCopyNamehint(PatternRewriter &rewriter, Operation *op, Value newValue)
A wrapper of PatternRewriter::replaceOp to propagate "sv.namehint" attribute.
Definition Naming.cpp:73
Definition comb.py:1