CIRCT 24.0.0git
Loading...
Searching...
No Matches
CombToArith.cpp
Go to the documentation of this file.
1//===- CombToArith.cpp ----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
12#include "mlir/Dialect/Arith/IR/Arith.h"
13#include "mlir/Pass/Pass.h"
14#include "mlir/Transforms/DialectConversion.h"
15
16namespace circt {
17#define GEN_PASS_DEF_CONVERTCOMBTOARITH
18#include "circt/Conversion/Passes.h.inc"
19} // namespace circt
20
21using namespace circt;
22using namespace hw;
23using namespace comb;
24using namespace mlir;
25using namespace arith;
26
27//===----------------------------------------------------------------------===//
28// Conversion patterns
29//===----------------------------------------------------------------------===//
30
31namespace {
32/// Lower a comb::ReplicateOp operation to a comb::ConcatOp
33struct CombReplicateOpConversion : OpConversionPattern<ReplicateOp> {
34 using OpConversionPattern<ReplicateOp>::OpConversionPattern;
35
36 LogicalResult
37 matchAndRewrite(ReplicateOp op, OpAdaptor adaptor,
38 ConversionPatternRewriter &rewriter) const override {
39
40 Type inputType = op.getInput().getType();
41 if (isa<IntegerType>(inputType) && inputType.getIntOrFloatBitWidth() == 1) {
42 Type outType = rewriter.getIntegerType(op.getMultiple());
43 rewriter.replaceOpWithNewOp<ExtSIOp>(op, outType, adaptor.getInput());
44 return success();
45 }
46
47 SmallVector<Value> inputs(op.getMultiple(), adaptor.getInput());
48 rewriter.replaceOpWithNewOp<ConcatOp>(op, inputs);
49 return success();
50 }
51};
52
53/// Lower a hw::ConstantOp operation to a arith::ConstantOp
54struct HWConstantOpConversion : OpConversionPattern<hw::ConstantOp> {
55 using OpConversionPattern<hw::ConstantOp>::OpConversionPattern;
56
57 LogicalResult
58 matchAndRewrite(hw::ConstantOp op, OpAdaptor adaptor,
59 ConversionPatternRewriter &rewriter) const override {
60
61 rewriter.replaceOpWithNewOp<arith::ConstantOp>(op, adaptor.getValueAttr());
62 return success();
63 }
64};
65
66/// Lower a comb::ICmpOp operation to a arith::CmpIOp
67struct IcmpOpConversion : OpConversionPattern<ICmpOp> {
68 using OpConversionPattern<ICmpOp>::OpConversionPattern;
69
70 LogicalResult
71 matchAndRewrite(ICmpOp op, OpAdaptor adaptor,
72 ConversionPatternRewriter &rewriter) const override {
73
74 CmpIPredicate pred;
75 switch (adaptor.getPredicate()) {
76 case ICmpPredicate::cne:
77 case ICmpPredicate::wne:
78 case ICmpPredicate::ne:
79 pred = CmpIPredicate::ne;
80 break;
81 case ICmpPredicate::ceq:
82 case ICmpPredicate::weq:
83 case ICmpPredicate::eq:
84 pred = CmpIPredicate::eq;
85 break;
86 case ICmpPredicate::sge:
87 pred = CmpIPredicate::sge;
88 break;
89 case ICmpPredicate::sgt:
90 pred = CmpIPredicate::sgt;
91 break;
92 case ICmpPredicate::sle:
93 pred = CmpIPredicate::sle;
94 break;
95 case ICmpPredicate::slt:
96 pred = CmpIPredicate::slt;
97 break;
98 case ICmpPredicate::uge:
99 pred = CmpIPredicate::uge;
100 break;
101 case ICmpPredicate::ugt:
102 pred = CmpIPredicate::ugt;
103 break;
104 case ICmpPredicate::ule:
105 pred = CmpIPredicate::ule;
106 break;
107 case ICmpPredicate::ult:
108 pred = CmpIPredicate::ult;
109 break;
110 }
111
112 rewriter.replaceOpWithNewOp<CmpIOp>(op, pred, adaptor.getLhs(),
113 adaptor.getRhs());
114 return success();
115 }
116};
117
118/// Lower a comb::ExtractOp operation to the arith dialect
119struct ExtractOpConversion : OpConversionPattern<ExtractOp> {
120 using OpConversionPattern<ExtractOp>::OpConversionPattern;
121
122 LogicalResult
123 matchAndRewrite(ExtractOp op, OpAdaptor adaptor,
124 ConversionPatternRewriter &rewriter) const override {
125
126 Value lowBit = arith::ConstantOp::create(
127 rewriter, op.getLoc(),
128 IntegerAttr::get(adaptor.getInput().getType(), adaptor.getLowBit()));
129 Value shifted =
130 ShRUIOp::create(rewriter, op.getLoc(), adaptor.getInput(), lowBit);
131 rewriter.replaceOpWithNewOp<TruncIOp>(op, op.getResult().getType(),
132 shifted);
133 return success();
134 }
135};
136
137/// Lower a comb::ConcatOp operation to the arith dialect
138struct ConcatOpConversion : OpConversionPattern<ConcatOp> {
139 using OpConversionPattern<ConcatOp>::OpConversionPattern;
140
141 LogicalResult
142 matchAndRewrite(ConcatOp op, OpAdaptor adaptor,
143 ConversionPatternRewriter &rewriter) const override {
144 Type type = op.getResult().getType();
145 Location loc = op.getLoc();
146
147 // Handle the trivial case where we have only one operand. The concat is a
148 // no-op in this case.
149 if (op.getNumOperands() == 1) {
150 rewriter.replaceOp(op, adaptor.getOperands().back());
151 return success();
152 }
153
154 // The operand at the least significant bit position (the one all the way on
155 // the right at the highest index) does not need to be shifted and can just
156 // be zero-extended to the final bit width.
157 Value aggregate =
158 rewriter.createOrFold<ExtUIOp>(loc, type, adaptor.getOperands().back());
159
160 // Shift and OR all the other operands onto the aggregate. Skip the last
161 // operand because it has already been incorporated into the aggregate.
162 unsigned offset = type.getIntOrFloatBitWidth();
163 for (auto operand : adaptor.getOperands().drop_back()) {
164 offset -= operand.getType().getIntOrFloatBitWidth();
165 auto offsetConst = arith::ConstantOp::create(
166 rewriter, loc, IntegerAttr::get(type, offset));
167 auto extended = rewriter.createOrFold<ExtUIOp>(loc, type, operand);
168 auto shifted = rewriter.createOrFold<ShLIOp>(loc, extended, offsetConst);
169 aggregate = rewriter.createOrFold<OrIOp>(loc, aggregate, shifted);
170 }
171
172 rewriter.replaceOp(op, aggregate);
173 return success();
174 }
175};
176
177/// Lower the two-operand SourceOp to the two-operand TargetOp
178template <typename SourceOp, typename TargetOp>
179struct BinaryOpConversion : OpConversionPattern<SourceOp> {
180 using OpConversionPattern<SourceOp>::OpConversionPattern;
181 using OpAdaptor = typename SourceOp::Adaptor;
182
183 LogicalResult
184 matchAndRewrite(SourceOp op, OpAdaptor adaptor,
185 ConversionPatternRewriter &rewriter) const override {
186
187 typename TargetOp::Properties properties{};
188 TargetOp::populateDefaultProperties(
189 OperationName(TargetOp::getOperationName(), rewriter.getContext()),
190 properties);
191 rewriter.replaceOpWithNewOp<TargetOp>(op,
192 TypeRange{op.getResult().getType()},
193 adaptor.getOperands(), properties);
194 return success();
195 }
196};
197
198/// Lowering for unsigned division / remainder operations that need to
199/// special-case zero-value divisors to not run coarser UB than CIRCT defines.
200template <typename SourceOp, typename TargetOp>
201struct DivUOpConversion : OpConversionPattern<SourceOp> {
202 using OpConversionPattern<SourceOp>::OpConversionPattern;
203 using OpAdaptor = typename SourceOp::Adaptor;
204
205 LogicalResult
206 matchAndRewrite(SourceOp op, OpAdaptor adaptor,
207 ConversionPatternRewriter &rewriter) const override {
208 // Rewrite: divu(a, b) ~>
209 // isZero = (b == 0)
210 // divisor = isZero ? 1 : b;
211 // result = divu(a, divisor)
212 Location loc = op.getLoc();
213 Value zero = arith::ConstantOp::create(
214 rewriter, loc, rewriter.getIntegerAttr(adaptor.getRhs().getType(), 0));
215 Value one = arith::ConstantOp::create(
216 rewriter, loc, rewriter.getIntegerAttr(adaptor.getRhs().getType(), 1));
217 Value isZero = arith::CmpIOp::create(rewriter, loc, CmpIPredicate::eq,
218 adaptor.getRhs(), zero);
219 Value divisor =
220 arith::SelectOp::create(rewriter, loc, isZero, one, adaptor.getRhs());
221 rewriter.replaceOpWithNewOp<TargetOp>(op, adaptor.getLhs(), divisor);
222 return success();
223 }
224};
225
226/// Lowering for signed division / remainder that need to special-case INT_MIN /
227/// -1 (and division by zero).
228template <typename SourceOp, typename TargetOp, bool IsRem>
229class DivSOpConversion : public OpConversionPattern<SourceOp> {
230public:
231 using OpConversionPattern<SourceOp>::OpConversionPattern;
232 using OpAdaptor = typename SourceOp::Adaptor;
233
234 LogicalResult
235 matchAndRewrite(SourceOp op, OpAdaptor adaptor,
236 ConversionPatternRewriter &rewriter) const override {
237 Value dividend = adaptor.getLhs();
238 Value divisor = adaptor.getRhs();
239 Type ty = op.getType();
240
241 // Rewrite: divs(a, b) ~>
242 // isZero = (b == 0)
243 // isOverflow = (b == -1 && a == INT_MIN)
244 // pred = isZero || isOverflow
245 // rhs_safe = pred ? 1 : b;
246 // c = divs(a, rhs_safe)
247 // result = isOverflow ? INT_MIN : c;
248 //
249 // mods is the same except the result is zero when isOverflow is true. These
250 // values were chosen to align with the behavior of existing Verilog
251 // simulators.
252 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
253 auto eq = [&](Value lhs, Value rhs) {
254 return arith::CmpIOp::create(b, CmpIPredicate::eq, lhs, rhs);
255 };
256 auto and_ = [&](Value lhs, Value rhs) {
257 return arith::AndIOp::create(b, lhs, rhs);
258 };
259 auto or_ = [&](Value lhs, Value rhs) {
260 return arith::OrIOp::create(b, lhs, rhs);
261 };
262
263 int bitwidth = ty.getIntOrFloatBitWidth();
264 Value zero = arith::ConstantOp::create(b, rewriter.getIntegerAttr(ty, 0));
265 Value one = arith::ConstantOp::create(b, rewriter.getIntegerAttr(ty, 1));
266 Value int_min = arith::ConstantOp::create(
267 b, rewriter.getIntegerAttr(ty, APInt::getSignedMinValue(bitwidth)));
268 Value minus_one = arith::ConstantOp::create(
269 b, rewriter.getIntegerAttr(ty, APInt::getAllOnes(bitwidth)));
270
271 Value isZero = eq(divisor, zero);
272 Value isOverflow = and_(eq(dividend, int_min), eq(divisor, minus_one));
273 Value pred = or_(isZero, isOverflow);
274 Value safeDivisor = arith::SelectOp::create(b, pred, one, divisor);
275 auto newOp = TargetOp::create(b, dividend, safeDivisor);
276
277 Value resultIfOverflow = IsRem ? zero : int_min;
278 Value result =
279 arith::SelectOp::create(b, isOverflow, resultIfOverflow, newOp);
280 rewriter.replaceOp(op, result);
281 return success();
282 }
283};
284
285/// Lower a comb::ReplicateOp operation to the LLVM dialect.
286template <typename SourceOp, typename TargetOp>
287struct VariadicOpConversion : OpConversionPattern<SourceOp> {
288 using OpConversionPattern<SourceOp>::OpConversionPattern;
289 using OpAdaptor = typename SourceOp::Adaptor;
290
291 LogicalResult
292 matchAndRewrite(SourceOp op, OpAdaptor adaptor,
293 ConversionPatternRewriter &rewriter) const override {
294
295 // TODO: building a tree would be better here
296 ValueRange operands = adaptor.getOperands();
297 Value runner = operands[0];
298 for (Value operand :
299 llvm::make_range(operands.begin() + 1, operands.end())) {
300 runner = TargetOp::create(rewriter, op.getLoc(), runner, operand);
301 }
302 rewriter.replaceOp(op, runner);
303 return success();
304 }
305};
306
307// Shifts greater than or equal to the width of the lhs are currently
308// unspecified in arith and produce poison in LLVM IR. To prevent undefined
309// behaviour we handle this case explicitly.
310
311/// Lower the logical shift SourceOp to the logical shift TargetOp
312/// Ensure to produce zero for shift amounts greater than or equal to the width
313/// of the lhs
314template <typename SourceOp, typename TargetOp>
315struct LogicalShiftConversion : OpConversionPattern<SourceOp> {
316 using OpConversionPattern<SourceOp>::OpConversionPattern;
317 using OpAdaptor = typename SourceOp::Adaptor;
318
319 LogicalResult
320 matchAndRewrite(SourceOp op, OpAdaptor adaptor,
321 ConversionPatternRewriter &rewriter) const override {
322 unsigned shifteeWidth =
323 hw::type_cast<IntegerType>(adaptor.getLhs().getType())
324 .getIntOrFloatBitWidth();
325 auto zeroConstOp = arith::ConstantOp::create(
326 rewriter, op.getLoc(), IntegerAttr::get(adaptor.getLhs().getType(), 0));
327 auto maxShamtConstOp = arith::ConstantOp::create(
328 rewriter, op.getLoc(),
329 IntegerAttr::get(adaptor.getLhs().getType(), shifteeWidth));
330 auto shiftOp = rewriter.createOrFold<TargetOp>(
331 op.getLoc(), adaptor.getLhs(), adaptor.getRhs());
332 auto isAllZeroOp = rewriter.createOrFold<CmpIOp>(
333 op.getLoc(), CmpIPredicate::uge, adaptor.getRhs(),
334 maxShamtConstOp.getResult());
335 rewriter.replaceOpWithNewOp<SelectOp>(op, isAllZeroOp, zeroConstOp,
336 shiftOp);
337 return success();
338 }
339};
340
341/// Lower a comb::ShrSOp operation to a (saturating) arith::ShRSIOp
342struct ShrSOpConversion : OpConversionPattern<ShrSOp> {
343 using OpConversionPattern<ShrSOp>::OpConversionPattern;
344
345 LogicalResult
346 matchAndRewrite(ShrSOp op, OpAdaptor adaptor,
347 ConversionPatternRewriter &rewriter) const override {
348 unsigned shifteeWidth =
349 hw::type_cast<IntegerType>(adaptor.getLhs().getType())
350 .getIntOrFloatBitWidth();
351 // Clamp the shift amount to shifteeWidth - 1
352 auto maxShamtMinusOneConstOp = arith::ConstantOp::create(
353 rewriter, op.getLoc(),
354 IntegerAttr::get(adaptor.getLhs().getType(), shifteeWidth - 1));
355 auto shamtOp = rewriter.createOrFold<MinUIOp>(op.getLoc(), adaptor.getRhs(),
356 maxShamtMinusOneConstOp);
357 rewriter.replaceOpWithNewOp<ShRSIOp>(op, adaptor.getLhs(), shamtOp);
358 return success();
359 }
360};
361
362} // namespace
363
364//===----------------------------------------------------------------------===//
365// Convert Comb to Arith pass
366//===----------------------------------------------------------------------===//
367
368namespace {
369struct ConvertCombToArithPass
370 : public circt::impl::ConvertCombToArithBase<ConvertCombToArithPass> {
371 void runOnOperation() override;
372};
373} // namespace
374
376 TypeConverter &converter, mlir::RewritePatternSet &patterns) {
377 patterns.add<
378 CombReplicateOpConversion, HWConstantOpConversion, IcmpOpConversion,
379 ExtractOpConversion, ConcatOpConversion, ShrSOpConversion,
380 LogicalShiftConversion<ShlOp, ShLIOp>,
381 LogicalShiftConversion<ShrUOp, ShRUIOp>,
382 BinaryOpConversion<SubOp, SubIOp>,
383 DivSOpConversion<DivSOp, DivSIOp, /*IsRem=*/false>,
384 DivUOpConversion<DivUOp, DivUIOp>,
385 DivSOpConversion<ModSOp, RemSIOp, /*IsRem=*/true>,
386 DivUOpConversion<ModUOp, RemUIOp>, BinaryOpConversion<MuxOp, SelectOp>,
387 VariadicOpConversion<AddOp, AddIOp>, VariadicOpConversion<MulOp, MulIOp>,
388 VariadicOpConversion<AndOp, AndIOp>, VariadicOpConversion<OrOp, OrIOp>,
389 VariadicOpConversion<XorOp, XOrIOp>>(converter, patterns.getContext());
390}
391
392void ConvertCombToArithPass::runOnOperation() {
393 ConversionTarget target(getContext());
394 target.addIllegalDialect<comb::CombDialect>();
395 target.addIllegalOp<hw::ConstantOp>();
396 target.addLegalDialect<ArithDialect>();
397 // Arith does not have an operation equivalent to comb.parity. A lowering
398 // would result in undesirably complex logic, therefore, we mark it legal
399 // here.
400 target.addLegalOp<comb::ParityOp>();
401 // Arith does not have bitreverse, so we leave it for the CombToLLVM pass.
402 target.addLegalOp<comb::ReverseOp>();
403 // This pass is intended to rewrite Comb ops into Arith ops. Other dialects
404 // (e.g. LLVM) may legitimately be present when this pass is used in custom
405 // pipelines. Treat all unknown operations as legal so we don't attempt to
406 // fold/legalize unrelated ops.
407 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
408 RewritePatternSet patterns(&getContext());
409 TypeConverter converter;
410 converter.addConversion([](Type type) { return type; });
411 // TODO: a pattern for comb.parity
412 populateCombToArithConversionPatterns(converter, patterns);
413
414 ConversionConfig config;
415 config.allowPatternRollback = false;
416 if (failed(mlir::applyPartialConversion(getOperation(), target,
417 std::move(patterns), config)))
418 signalPassFailure();
419}
420
421std::unique_ptr<Pass> circt::createConvertCombToArithPass() {
422 return std::make_unique<ConvertCombToArithPass>();
423}
return success()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateCombToArithConversionPatterns(TypeConverter &converter, RewritePatternSet &patterns)
std::unique_ptr< Pass > createConvertCombToArithPass()
Definition comb.py:1
Definition hw.py:1