MLIR 24.0.0git
TosaToLinalg.cpp
Go to the documentation of this file.
1//===- TosaToLinalg.cpp - Lowering Tosa to Linalg Dialect -----------------===//
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// These rewriters lower from the Tosa to the Linalg dialect.
10//
11//===----------------------------------------------------------------------===//
12
14#include "TosaToLinalgUtils.h"
26#include "mlir/IR/Matchers.h"
30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/Sequence.h"
32#include "llvm/ADT/SmallVectorExtras.h"
33
34#include <type_traits>
35
36using namespace mlir;
37using namespace mlir::tosa;
38
39template <typename OpTy>
41 TypeRange resultTypes,
42 ValueRange operands) {
43 typename OpTy::Properties properties{};
44 OpTy::populateDefaultProperties(
45 OperationName(OpTy::getOperationName(), builder.getContext()),
46 properties);
47 return OpTy::create(builder, loc, resultTypes, operands, properties,
48 /*discardableAttributes=*/{});
49}
50
51// Helper function to materialize the semantically correct compare and select
52// operations given a binary operation with a specific NaN propagation mode.
53//
54// In the case of "PROPAGATE" semantics no compare and selection is required and
55// this function does nothing.
56//
57// In the case of "IGNORE" semantics this function materializes a comparison of
58// the current operands to the op which will return true for any NaN
59// argument and then selects between the non-NaN operation argument and the
60// calculated result based on whether the lhs or rhs is NaN or not. In pseudo
61// code:
62//
63// In the case that the op is operating on non floating point types we ignore
64// the attribute completely, this is consistent with the TOSA spec which has
65// the following wording: "This attribute is ignored by non floating-point
66// types."
67//
68// binary<op>(lhs, rhs):
69// result = op(lhs, rhs)
70// if lhs == NaN return rhs
71// if rhs == NaN return lhs
72// return result
73template <typename OpTy>
74static Value
76 Value lhs, Value rhs, Value result) {
77 // NaN propagation has no meaning for non floating point types.
78 if (!isa<FloatType>(getElementTypeOrSelf(lhs)))
79 return result;
80
81 auto nanMode = op.getNanMode();
82 if (nanMode == NanPropagationMode::PROPAGATE)
83 return result;
84
85 // Unordered comparison of NaN against itself will always return true.
86 Value lhsIsNaN = arith::CmpFOp::create(rewriter, op.getLoc(),
87 arith::CmpFPredicate::UNO, lhs, lhs);
88 Value rhsIsNaN = arith::CmpFOp::create(rewriter, op.getLoc(),
89 arith::CmpFPredicate::UNO, rhs, rhs);
90 Value rhsOrResult =
91 arith::SelectOp::create(rewriter, op.getLoc(), lhsIsNaN, rhs, result);
92 return arith::SelectOp::create(rewriter, op.getLoc(), rhsIsNaN, lhs,
93 rhsOrResult);
94}
95
97 Operation *op, ValueRange args, ArrayRef<Type> resultTypes,
98 ConversionPatternRewriter &rewriter) {
99 Location loc = op->getLoc();
100 auto elementTy =
101 cast<ShapedType>(op->getOperand(0).getType()).getElementType();
102
103 // tosa::AbsOp
104 if (isa<tosa::AbsOp>(op) && isa<FloatType>(elementTy))
105 return createWithDefaultProperties<math::AbsFOp>(rewriter, loc, resultTypes,
106 args);
107
108 if (isa<tosa::AbsOp>(op) && isa<IntegerType>(elementTy)) {
109 auto zero = arith::ConstantOp::create(rewriter, loc,
110 rewriter.getZeroAttr(elementTy));
111 auto neg = arith::SubIOp::create(rewriter, loc, zero, args[0]);
112 return arith::MaxSIOp::create(rewriter, loc, args[0], neg);
113 }
114
115 // tosa::AddOp
116 if (isa<tosa::AddOp>(op) && isa<FloatType>(elementTy))
118 resultTypes, args);
119
120 if (isa<tosa::AddOp>(op) && isa<IntegerType>(elementTy))
122 resultTypes, args);
123
124 // tosa::SubOp
125 if (isa<tosa::SubOp>(op) && isa<FloatType>(elementTy))
127 resultTypes, args);
128
129 if (isa<tosa::SubOp>(op) && isa<IntegerType>(elementTy))
131 resultTypes, args);
132
133 // tosa::IntDivOp
134 if (isa<tosa::IntDivOp>(op) && isa<IntegerType>(elementTy))
136 resultTypes, args);
137
138 // tosa::ReciprocalOp
139 if (isa<tosa::ReciprocalOp>(op) && isa<FloatType>(elementTy)) {
140 auto one =
141 arith::ConstantOp::create(rewriter, loc, FloatAttr::get(elementTy, 1));
142 return arith::DivFOp::create(rewriter, loc, one, args[0]);
143 }
144
145 // tosa::MulOp
146 if (isa<tosa::MulOp>(op)) {
147 auto shiftVal = cast<tosa::MulOp>(op).getShift();
148 DenseElementsAttr shiftElem;
149 bool shiftIsConstant = true;
150 int32_t shift = 0;
151 if (matchPattern(shiftVal, m_Constant(&shiftElem)))
152 shift = shiftElem.getValues<IntegerAttr>()[0].getInt();
153 else
154 shiftIsConstant = false;
155
156 if (isa<FloatType>(elementTy)) {
157 if (shift != 0) {
158 (void)rewriter.notifyMatchFailure(op,
159 "Cannot have shift value for float");
160 return nullptr;
161 }
162 return arith::MulFOp::create(rewriter, loc, args[0], args[1]);
163 }
164
165 if (isa<IntegerType>(elementTy)) {
166 Value a = args[0];
167 Value b = args[1];
168
169 if (shift > 0 || !shiftIsConstant) {
170 Value shiftConst;
171 if (shiftIsConstant)
172 shiftConst = arith::ConstantIntOp::create(rewriter, loc, shift,
173 /*bitwidth=*/8);
174
175 if (!a.getType().isInteger(32))
176 a = arith::ExtSIOp::create(rewriter, loc, rewriter.getI32Type(), a);
177
178 if (!b.getType().isInteger(32))
179 b = arith::ExtSIOp::create(rewriter, loc, rewriter.getI32Type(), b);
180
181 auto shiftAmount = shiftIsConstant ? shiftConst : args[2];
182 auto roundingAttr = RoundingModeAttr::get(rewriter.getContext(),
183 RoundingMode::SINGLE_ROUND);
184 auto result =
185 tosa::ApplyScaleOp::create(rewriter, loc, rewriter.getI32Type(), a,
186 b, shiftAmount, roundingAttr);
187
188 return result;
189 }
190
191 int aWidth = a.getType().getIntOrFloatBitWidth();
192 int bWidth = b.getType().getIntOrFloatBitWidth();
193 int cWidth = resultTypes[0].getIntOrFloatBitWidth();
194
195 if (aWidth < cWidth)
196 a = arith::ExtSIOp::create(rewriter, loc, resultTypes[0], a);
197 if (bWidth < cWidth)
198 b = arith::ExtSIOp::create(rewriter, loc, resultTypes[0], b);
199
200 return arith::MulIOp::create(rewriter, loc, resultTypes, a, b);
201 }
202 }
203
204 // tosa::NegateOp
205 if (isa<tosa::NegateOp>(op)) {
206 auto negate = cast<tosa::NegateOp>(op);
207
208 int64_t inZp = 0, outZp = 0;
209 FailureOr<int64_t> maybeInZp = negate.getInput1ZeroPoint();
210 FailureOr<int64_t> maybeOutZp = negate.getOutputZeroPoint();
211 bool hasInZp = !failed(maybeInZp);
212 bool hasOutZp = !failed(maybeOutZp);
213 if (hasInZp)
214 inZp = *maybeInZp;
215 if (hasOutZp)
216 outZp = *maybeOutZp;
217
218 if (isa<FloatType>(elementTy))
219 return arith::NegFOp::create(rewriter, loc, resultTypes, args[0]);
220
221 if (isa<IntegerType>(elementTy)) {
222 Value zpAddValue;
223 Type intermediateType;
224 // Compute the maximum value that can occur in the intermediate buffer.
225 const int32_t inputBitWidth = elementTy.getIntOrFloatBitWidth();
226 int intermediateBitWidth = 64;
227
228 if (hasInZp && hasOutZp) {
229 // Compute the maximum value that can occur in the intermediate buffer.
230 // Zero points can only be non-zero for int8, so their sum fits in
231 // int64_t. The bound is computed with 128 bits so that it cannot
232 // overflow for 64-bit inputs.
233 const int64_t zpAdd = inZp + outZp;
234 const APInt maxValue =
235 APInt::getSignedMaxValue(inputBitWidth).sext(128) +
236 std::abs(zpAdd) + 1;
237
238 // Convert that maximum value into the maximum bitwidth needed to
239 // represent it.
240 if (maxValue.sle(APInt::getSignedMaxValue(16).getSExtValue())) {
241 intermediateBitWidth = 16;
242 } else if (maxValue.sle(APInt::getSignedMaxValue(32).getSExtValue())) {
243 intermediateBitWidth = 32;
244 }
245
246 intermediateType = rewriter.getIntegerType(intermediateBitWidth);
247 zpAddValue = arith::ConstantOp::create(
248 rewriter, loc, rewriter.getIntegerAttr(intermediateType, zpAdd));
249 } else {
250 intermediateType = rewriter.getIntegerType(intermediateBitWidth);
251 Value arg1 = args[1];
252 Value arg2 = args[2];
253 // Avoid verifier-invalid no-op sign-extends; only widen when needed.
254 if (arg1.getType() != intermediateType)
255 arg1 = arith::ExtSIOp::create(rewriter, loc, intermediateType, arg1);
256 if (arg2.getType() != intermediateType)
257 arg2 = arith::ExtSIOp::create(rewriter, loc, intermediateType, arg2);
258 zpAddValue =
259 arith::AddIOp::create(rewriter, loc, intermediateType, arg1, arg2);
260 }
261
262 // The negation can be applied by doing:
263 // outputValue = inZp + outZp - inputValue
264 Value ext = args[0];
265 if (ext.getType() != intermediateType)
266 ext = arith::ExtSIOp::create(rewriter, loc, intermediateType, ext);
267 auto sub = arith::SubIOp::create(rewriter, loc, zpAddValue, ext);
268
269 // Clamp to the negation range.
271 rewriter, loc, intermediateType,
272 APInt::getSignedMinValue(inputBitWidth).getSExtValue());
274 rewriter, loc, intermediateType,
275 APInt::getSignedMaxValue(inputBitWidth).getSExtValue());
276 auto clamp = clampIntHelper(loc, sub, min, max, rewriter, false);
277
278 // Truncate to the final value, skipping no-op trunci when widths match.
279 if (clamp.getType() == elementTy)
280 return clamp;
281 return arith::TruncIOp::create(rewriter, loc, elementTy, clamp);
282 }
283 }
284
285 // tosa::BitwiseAndOp
286 if (isa<tosa::BitwiseAndOp>(op) && isa<IntegerType>(elementTy))
287 return arith::AndIOp::create(rewriter, loc, resultTypes, args);
288
289 // tosa::BitwiseOrOp
290 if (isa<tosa::BitwiseOrOp>(op) && isa<IntegerType>(elementTy))
291 return arith::OrIOp::create(rewriter, loc, resultTypes, args);
292
293 // tosa::BitwiseNotOp
294 if (isa<tosa::BitwiseNotOp>(op) && isa<IntegerType>(elementTy)) {
295 auto allOnesAttr = rewriter.getIntegerAttr(
296 elementTy, APInt::getAllOnes(elementTy.getIntOrFloatBitWidth()));
297 auto allOnes = arith::ConstantOp::create(rewriter, loc, allOnesAttr);
298 return arith::XOrIOp::create(rewriter, loc, resultTypes, args[0], allOnes);
299 }
300
301 // tosa::BitwiseXOrOp
302 if (isa<tosa::BitwiseXorOp>(op) && isa<IntegerType>(elementTy))
303 return arith::XOrIOp::create(rewriter, loc, resultTypes, args);
304
305 // tosa::LogicalLeftShiftOp
306 if (isa<tosa::LogicalLeftShiftOp>(op) && isa<IntegerType>(elementTy))
308 resultTypes, args);
309
310 // tosa::LogicalRightShiftOp
311 if (isa<tosa::LogicalRightShiftOp>(op) && isa<IntegerType>(elementTy))
313 resultTypes, args);
314
315 // tosa::ArithmeticRightShiftOp
316 if (isa<tosa::ArithmeticRightShiftOp>(op) && isa<IntegerType>(elementTy)) {
318 rewriter, loc, resultTypes, args);
319 bool round = cast<tosa::ArithmeticRightShiftOp>(op).getRound();
320 if (!round) {
321 return result;
322 }
323
324 Type i1Ty = IntegerType::get(rewriter.getContext(), /*width=*/1);
325 auto one = arith::ConstantOp::create(rewriter, loc,
326 IntegerAttr::get(elementTy, 1));
327 auto zero = arith::ConstantOp::create(rewriter, loc,
328 IntegerAttr::get(elementTy, 0));
329 auto i1zero =
330 arith::ConstantOp::create(rewriter, loc, IntegerAttr::get(i1Ty, 0));
331 auto i1one =
332 arith::ConstantOp::create(rewriter, loc, IntegerAttr::get(i1Ty, 1));
333
334 // Checking that input2 != 0
335 auto shiftValueGreaterThanZero = arith::CmpIOp::create(
336 rewriter, loc, arith::CmpIPredicate::sgt, args[1], zero);
337
338 // Checking for the last bit of input1 to be 1
339 auto subtract =
340 arith::SubIOp::create(rewriter, loc, resultTypes, args[1], one);
341 auto shifted =
342 arith::ShRSIOp::create(rewriter, loc, resultTypes, args[0], subtract)
343 ->getResults();
345 rewriter, loc, TypeRange{i1Ty}, shifted);
346 auto isInputOdd =
347 arith::AndIOp::create(rewriter, loc, i1Ty, truncated, i1one);
348 // shifted, truncated, isInputOdd can be poison when input2 is 0.
349 auto shouldRound = arith::SelectOp::create(
350 rewriter, loc, i1Ty, shiftValueGreaterThanZero, isInputOdd, i1zero);
351 auto extended =
352 arith::ExtUIOp::create(rewriter, loc, resultTypes, shouldRound);
353 return arith::AddIOp::create(rewriter, loc, resultTypes, result, extended);
354 }
355
356 // tosa::ClzOp
357 if (isa<tosa::ClzOp>(op) && isa<IntegerType>(elementTy)) {
358 return math::CountLeadingZerosOp::create(rewriter, loc, elementTy, args[0]);
359 }
360
361 // tosa::LogicalAnd
362 if (isa<tosa::LogicalAndOp>(op) && elementTy.isInteger(1))
363 return arith::AndIOp::create(rewriter, loc, resultTypes, args);
364
365 // tosa::LogicalNot
366 if (isa<tosa::LogicalNotOp>(op) && elementTy.isInteger(1)) {
367 auto one = arith::ConstantOp::create(rewriter, loc,
368 rewriter.getIntegerAttr(elementTy, 1));
369 return arith::XOrIOp::create(rewriter, loc, resultTypes, args[0], one);
370 }
371
372 // tosa::LogicalOr
373 if (isa<tosa::LogicalOrOp>(op) && elementTy.isInteger(1))
374 return arith::OrIOp::create(rewriter, loc, resultTypes, args);
375
376 // tosa::LogicalXor
377 if (isa<tosa::LogicalXorOp>(op) && elementTy.isInteger(1))
378 return arith::XOrIOp::create(rewriter, loc, resultTypes, args);
379
380 // tosa::PowOp
381 if (isa<tosa::PowOp>(op) && isa<FloatType>(elementTy))
383 resultTypes, args);
384
385 // tosa::RsqrtOp
386 if (isa<tosa::RsqrtOp>(op) && isa<FloatType>(elementTy))
388 resultTypes, args);
389
390 // tosa::LogOp
391 if (isa<tosa::LogOp>(op) && isa<FloatType>(elementTy))
393 resultTypes, args);
394
395 // tosa::ExpOp
396 if (isa<tosa::ExpOp>(op) && isa<FloatType>(elementTy))
398 resultTypes, args);
399
400 // tosa::SinOp
401 if (isa<tosa::SinOp>(op) && isa<FloatType>(elementTy))
403 resultTypes, args);
404
405 // tosa::CosOp
406 if (isa<tosa::CosOp>(op) && isa<FloatType>(elementTy))
408 resultTypes, args);
409
410 // tosa::TanhOp
411 if (isa<tosa::TanhOp>(op) && isa<FloatType>(elementTy))
413 resultTypes, args);
414
415 // tosa::ErfOp
416 if (isa<tosa::ErfOp>(op) && llvm::isa<FloatType>(elementTy))
418 resultTypes, args);
419
420 // tosa::GreaterOp
421 if (isa<tosa::GreaterOp>(op) && isa<FloatType>(elementTy))
422 return arith::CmpFOp::create(rewriter, loc, arith::CmpFPredicate::OGT,
423 args[0], args[1]);
424
425 if (isa<tosa::GreaterOp>(op) && elementTy.isSignlessInteger())
426 return arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::sgt,
427 args[0], args[1]);
428
429 // tosa::GreaterEqualOp
430 if (isa<tosa::GreaterEqualOp>(op) && isa<FloatType>(elementTy))
431 return arith::CmpFOp::create(rewriter, loc, arith::CmpFPredicate::OGE,
432 args[0], args[1]);
433
434 if (isa<tosa::GreaterEqualOp>(op) && elementTy.isSignlessInteger())
435 return arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::sge,
436 args[0], args[1]);
437
438 // tosa::EqualOp
439 if (isa<tosa::EqualOp>(op) && isa<FloatType>(elementTy))
440 return arith::CmpFOp::create(rewriter, loc, arith::CmpFPredicate::OEQ,
441 args[0], args[1]);
442
443 if (isa<tosa::EqualOp>(op) && elementTy.isSignlessInteger())
444 return arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
445 args[0], args[1]);
446
447 // tosa::SelectOp
448 if (isa<tosa::SelectOp>(op)) {
449 elementTy = cast<ShapedType>(op->getOperand(1).getType()).getElementType();
450 if (isa<FloatType>(elementTy) || isa<IntegerType>(elementTy))
451 return arith::SelectOp::create(rewriter, loc, args[0], args[1], args[2]);
452 }
453
454 // tosa::MaximumOp
455 if (isa<tosa::MaximumOp>(op) && isa<FloatType>(elementTy)) {
456 auto max = arith::MaximumFOp::create(rewriter, loc, args[0], args[1]);
457 return materializeBinaryNanCheckIfRequired(llvm::cast<tosa::MaximumOp>(op),
458 rewriter, args[0], args[1], max);
459 }
460
461 if (isa<tosa::MaximumOp>(op) && elementTy.isSignlessInteger()) {
462 return arith::MaxSIOp::create(rewriter, loc, args[0], args[1]);
463 }
464
465 // tosa::MinimumOp
466 if (isa<tosa::MinimumOp>(op) && isa<FloatType>(elementTy)) {
467 auto min = arith::MinimumFOp::create(rewriter, loc, args[0], args[1]);
468 return materializeBinaryNanCheckIfRequired(llvm::cast<tosa::MinimumOp>(op),
469 rewriter, args[0], args[1], min);
470 }
471
472 if (isa<tosa::MinimumOp>(op) && elementTy.isSignlessInteger()) {
473 return arith::MinSIOp::create(rewriter, loc, args[0], args[1]);
474 }
475
476 // tosa::CeilOp
477 if (isa<tosa::CeilOp>(op) && isa<FloatType>(elementTy))
478 return createWithDefaultProperties<math::CeilOp>(rewriter, loc, resultTypes,
479 args);
480
481 // tosa::FloorOp
482 if (isa<tosa::FloorOp>(op) && isa<FloatType>(elementTy))
484 resultTypes, args);
485
486 // tosa::ClampOp
487 if (isa<tosa::ClampOp>(op) && isa<FloatType>(elementTy)) {
488 bool losesInfo = false;
489 auto clampOp = cast<tosa::ClampOp>(op);
490 APFloat minApf = cast<FloatAttr>(clampOp.getMinValAttr()).getValue();
491 APFloat maxApf = cast<FloatAttr>(clampOp.getMaxValAttr()).getValue();
492 minApf.convert(cast<FloatType>(elementTy).getFloatSemantics(),
493 APFloat::rmNearestTiesToEven, &losesInfo);
494 maxApf.convert(cast<FloatType>(elementTy).getFloatSemantics(),
495 APFloat::rmNearestTiesToEven, &losesInfo);
496 auto min = arith::ConstantOp::create(
497 rewriter, loc, elementTy, rewriter.getFloatAttr(elementTy, minApf));
498 auto max = arith::ConstantOp::create(
499 rewriter, loc, elementTy, rewriter.getFloatAttr(elementTy, maxApf));
500 auto result = clampFloatHelper(loc, args[0], min, max, rewriter);
501
502 const auto nanMode = clampOp.getNanMode();
503
504 // NaN propagation has no meaning for non floating point types.
505 if (!isa<FloatType>(elementTy))
506 return result;
507
508 // In the case of "PROPAGATE" semantics no compare and selection is
509 // required.
510 if (nanMode == NanPropagationMode::PROPAGATE)
511 return result;
512
513 // In the case of "IGNORE" semantics materialize a comparison
514 // of the current operand to the reduction which will return true for a NaN
515 // argument and then selects between the initial reduction value and the
516 // calculated result based on whether the argument is NaN or not. In pseudo
517 // code:
518 //
519 // reduce<op>(x, init):
520 // result = op(init, x)
521 // return init if x == NaN else result
522
523 // Unordered comparison of NaN against itself will always return true.
524 Value isNaN = arith::CmpFOp::create(
525 rewriter, op->getLoc(), arith::CmpFPredicate::UNO, args[0], args[0]);
526 // TOSA specifies that in "ignore" NaN mode the result is "min" if the input
527 // is NaN.
528 return arith::SelectOp::create(rewriter, op->getLoc(), isNaN, min, result);
529 }
530
531 if (isa<tosa::ClampOp>(op) && isa<IntegerType>(elementTy)) {
532 auto intTy = cast<IntegerType>(elementTy);
533 auto clampOp = cast<tosa::ClampOp>(op);
534 int64_t min =
535 cast<IntegerAttr>(clampOp.getMinValAttr()).getValue().getSExtValue();
536 int64_t max =
537 cast<IntegerAttr>(clampOp.getMaxValAttr()).getValue().getSExtValue();
538
539 int64_t minRepresentable = std::numeric_limits<int64_t>::min();
540 int64_t maxRepresentable = std::numeric_limits<int64_t>::max();
541 if (intTy.isUnsignedInteger()) {
542 minRepresentable = 0;
543 if (intTy.getIntOrFloatBitWidth() <= 63) {
544 maxRepresentable =
545 (int64_t)APInt::getMaxValue(intTy.getIntOrFloatBitWidth())
546 .getZExtValue();
547 }
548 } else if (intTy.getIntOrFloatBitWidth() <= 64) {
549 // Ensure that min & max fit into signed n-bit constants.
550 minRepresentable = APInt::getSignedMinValue(intTy.getIntOrFloatBitWidth())
551 .getSExtValue();
552 maxRepresentable = APInt::getSignedMaxValue(intTy.getIntOrFloatBitWidth())
553 .getSExtValue();
554 }
555 // Ensure that the bounds are representable as n-bit signed/unsigned
556 // integers.
557 min = std::max(min, minRepresentable);
558 max = std::max(max, minRepresentable);
559 min = std::min(min, maxRepresentable);
560 max = std::min(max, maxRepresentable);
561
562 auto minVal = arith::ConstantIntOp::create(rewriter, loc, min,
563 intTy.getIntOrFloatBitWidth());
564 auto maxVal = arith::ConstantIntOp::create(rewriter, loc, max,
565 intTy.getIntOrFloatBitWidth());
566 return clampIntHelper(loc, args[0], minVal, maxVal, rewriter,
567 intTy.isUnsignedInteger());
568 }
569
570 // tosa::SigmoidOp
571 if (isa<tosa::SigmoidOp>(op) && isa<FloatType>(elementTy)) {
572 auto one =
573 arith::ConstantOp::create(rewriter, loc, FloatAttr::get(elementTy, 1));
574 auto negate = arith::NegFOp::create(rewriter, loc, resultTypes, args[0]);
575 auto exp = mlir::math::ExpOp::create(rewriter, loc, resultTypes, negate);
576 auto added = arith::AddFOp::create(rewriter, loc, exp, one);
577 return arith::DivFOp::create(rewriter, loc, one, added);
578 }
579
580 // tosa::CastOp
581 if (isa<tosa::CastOp>(op)) {
582 Type srcTy = elementTy;
583 Type dstTy = resultTypes.front();
584 if (!srcTy.isIntOrFloat() || !dstTy.isIntOrFloat()) {
585 (void)rewriter.notifyMatchFailure(op, "unsupported type");
586 return nullptr;
587 }
588
589 bool bitExtend =
591
592 // With `input_unsigned`, the integer input is read as unsigned.
593 bool inputUnsigned = cast<tosa::CastOp>(op).getInputUnsigned();
594
595 if (srcTy == dstTy)
596 return args.front();
597
598 if (isa<FloatType>(srcTy) && isa<FloatType>(dstTy) && bitExtend)
600 resultTypes, args);
601
602 if (isa<FloatType>(srcTy) && isa<FloatType>(dstTy) && !bitExtend)
604 resultTypes, args);
605
606 // 1-bit integers need to be treated as signless.
607 if (srcTy.isInteger(1) && arith::UIToFPOp::areCastCompatible(srcTy, dstTy))
609 resultTypes, args);
610
611 if (srcTy.isInteger(1) && isa<IntegerType>(dstTy) && bitExtend)
613 resultTypes, args);
614
615 // Unsigned integers need an unrealized cast so that they can be passed
616 // to UIToFP.
617 if (srcTy.isUnsignedInteger() && isa<FloatType>(dstTy)) {
618 auto unrealizedCast =
619 UnrealizedConversionCastOp::create(
620 rewriter, loc,
621 rewriter.getIntegerType(srcTy.getIntOrFloatBitWidth()), args[0])
622 .getResult(0);
623 return arith::UIToFPOp::create(rewriter, loc, resultTypes[0],
624 unrealizedCast);
625 }
626
627 // Unsigned inputs are converted with UIToFP.
628 if (inputUnsigned && arith::UIToFPOp::areCastCompatible(srcTy, dstTy))
630 resultTypes, args);
631
632 // All other si-to-fp conversions should be handled by SIToFP.
633 if (arith::SIToFPOp::areCastCompatible(srcTy, dstTy))
634 return arith::SIToFPOp::create(rewriter, loc, resultTypes, args,
636
637 // Casting to boolean, floats need to only be checked as not-equal to zero.
638 if (isa<FloatType>(srcTy) && dstTy.isInteger(1)) {
639 Value zero = arith::ConstantOp::create(rewriter, loc,
640 rewriter.getFloatAttr(srcTy, 0.0));
641 return arith::CmpFOp::create(rewriter, loc, arith::CmpFPredicate::UNE,
642 args.front(), zero);
643 }
644
645 if (arith::FPToSIOp::areCastCompatible(srcTy, dstTy)) {
646 auto rounded = math::RoundEvenOp::create(rewriter, loc, args[0]);
647
648 const auto &fltSemantics = cast<FloatType>(srcTy).getFloatSemantics();
649 // Check whether neither int min nor int max can be represented in the
650 // input floating-point type due to too short exponent range.
651 if (static_cast<int>(dstTy.getIntOrFloatBitWidth()) - 1 >
652 APFloat::semanticsMaxExponent(fltSemantics)) {
653 // Use cmp + select to replace infinites by int min / int max. Other
654 // integral values can be represented in the integer space.
655 auto conv = arith::FPToSIOp::create(rewriter, loc, dstTy, rounded);
656 auto posInf = arith::ConstantOp::create(
657 rewriter, loc,
658 rewriter.getFloatAttr(getElementTypeOrSelf(srcTy),
659 APFloat::getInf(fltSemantics)));
660 auto negInf = arith::ConstantOp::create(
661 rewriter, loc,
662 rewriter.getFloatAttr(
664 APFloat::getInf(fltSemantics, /*Negative=*/true)));
665 auto overflow = arith::CmpFOp::create(
666 rewriter, loc, arith::CmpFPredicate::UEQ, rounded, posInf);
667 auto underflow = arith::CmpFOp::create(
668 rewriter, loc, arith::CmpFPredicate::UEQ, rounded, negInf);
669 auto intMin = arith::ConstantOp::create(
670 rewriter, loc,
671 rewriter.getIntegerAttr(
673 APInt::getSignedMinValue(dstTy.getIntOrFloatBitWidth())));
674 auto intMax = arith::ConstantOp::create(
675 rewriter, loc,
676 rewriter.getIntegerAttr(
678 APInt::getSignedMaxValue(dstTy.getIntOrFloatBitWidth())));
679 auto maxClamped =
680 arith::SelectOp::create(rewriter, loc, overflow, intMax, conv);
681 return arith::SelectOp::create(rewriter, loc, underflow, intMin,
682 maxClamped);
683 }
684
685 auto intMinFP = arith::ConstantOp::create(
686 rewriter, loc,
687 rewriter.getFloatAttr(
689 APInt::getSignedMinValue(dstTy.getIntOrFloatBitWidth())
690 .getSExtValue()));
691
692 // Check whether the mantissa has enough bits to represent int max.
693 if (cast<FloatType>(srcTy).getFPMantissaWidth() >=
694 dstTy.getIntOrFloatBitWidth() - 1) {
695 // Int min can also be represented since it is a power of two and thus
696 // consists of a single leading bit. Therefore we can clamp the input
697 // in the floating-point domain.
698
699 auto intMaxFP = arith::ConstantOp::create(
700 rewriter, loc,
701 rewriter.getFloatAttr(
703 APInt::getSignedMaxValue(dstTy.getIntOrFloatBitWidth())
704 .getSExtValue()));
705
706 Value clamped =
707 clampFloatHelper(loc, rounded, intMinFP, intMaxFP, rewriter);
708 return arith::FPToSIOp::create(rewriter, loc, dstTy, clamped);
709 }
710
711 // Due to earlier check we know exponant range is big enough to represent
712 // int min. We can therefore rely on int max + 1 being representable as
713 // well because it's just int min with a positive sign. So clamp the min
714 // value and compare against that to select the max int value if needed.
715 auto intMaxPlusOneFP = arith::ConstantOp::create(
716 rewriter, loc,
717 rewriter.getFloatAttr(
719 static_cast<double>(
720 APInt::getSignedMaxValue(dstTy.getIntOrFloatBitWidth())
721 .getSExtValue()) +
722 1.0f));
723
724 auto intMax = arith::ConstantOp::create(
725 rewriter, loc,
726 rewriter.getIntegerAttr(
728 APInt::getSignedMaxValue(dstTy.getIntOrFloatBitWidth())));
729 auto minClampedFP =
730 arith::MaximumFOp::create(rewriter, loc, rounded, intMinFP);
731 auto minClamped =
732 arith::FPToSIOp::create(rewriter, loc, dstTy, minClampedFP);
733 auto overflow = arith::CmpFOp::create(
734 rewriter, loc, arith::CmpFPredicate::UGE, rounded, intMaxPlusOneFP);
735 return arith::SelectOp::create(rewriter, loc, overflow, intMax,
736 minClamped);
737 }
738
739 // Casting to boolean, integers need to only be checked as not-equal to
740 // zero.
741 if (isa<IntegerType>(srcTy) && dstTy.isInteger(1)) {
742 Value zero = arith::ConstantIntOp::create(rewriter, loc, 0,
743 srcTy.getIntOrFloatBitWidth());
744 return arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::ne,
745 args.front(), zero);
746 }
747
748 // Unsigned inputs are zero-extended.
749 if (inputUnsigned && isa<IntegerType>(srcTy) && isa<IntegerType>(dstTy) &&
750 bitExtend)
752 resultTypes, args);
753
754 if (isa<IntegerType>(srcTy) && isa<IntegerType>(dstTy) && bitExtend)
755 return arith::ExtSIOp::create(rewriter, loc, resultTypes, args,
757
758 if (isa<IntegerType>(srcTy) && isa<IntegerType>(dstTy) && !bitExtend) {
759 return arith::TruncIOp::create(rewriter, loc, dstTy, args[0]);
760 }
761 }
762
763 (void)rewriter.notifyMatchFailure(
764 op, "unhandled op for linalg body calculation for elementwise op");
765 return nullptr;
766}
767
768// Emit an 'arith.constant' op for the given index if it has not been created
769// yet, or return an existing constant. This will prevent an excessive creation
770// of redundant constants, easing readability of emitted code for unit tests.
772 IndexPool &indexPool, int64_t index) {
773 auto [it, inserted] = indexPool.try_emplace(index);
774 if (inserted)
775 it->second =
776 arith::ConstantOp::create(rewriter, loc, rewriter.getIndexAttr(index));
777 return it->second;
778}
779
781 IndexPool &indexPool, Value tensor, int64_t index) {
782 auto indexValue = createIndex(rewriter, loc, indexPool, index);
783 return tensor::DimOp::create(rewriter, loc, tensor, indexValue).getResult();
784}
785
787 IndexPool &indexPool, Value tensor,
788 int64_t index) {
789 auto shapedType = dyn_cast<ShapedType>(tensor.getType());
790 assert(shapedType && shapedType.hasRank() && "expected a ranked shaped type");
791 assert(index >= 0 && index < shapedType.getRank() && "index out of bounds");
792 if (shapedType.isDynamicDim(index))
793 return getTensorDim(rewriter, loc, indexPool, tensor, index);
794 return rewriter.getIndexAttr(shapedType.getDimSize(index));
795}
796
797static bool operandsAndResultsRanked(Operation *operation) {
798 auto isRanked = [](Value value) {
799 return isa<RankedTensorType>(value.getType());
800 };
801 return llvm::all_of(operation->getOperands(), isRanked) &&
802 llvm::all_of(operation->getResults(), isRanked);
803}
804
805// Compute the runtime dimension size for dimension 'dim' of the output by
806// inspecting input 'operands', all of which are expected to have the same rank.
807// This function returns a pair {targetSize, masterOperand}.
808//
809// The runtime size of the output dimension is returned either as a statically
810// computed attribute or as a runtime SSA value.
811//
812// If the target size was inferred directly from one dominating operand, that
813// operand is returned in 'masterOperand'. If the target size is inferred from
814// multiple operands, 'masterOperand' is set to nullptr.
815std::pair<OpFoldResult, Value>
817 IndexPool &indexPool, ValueRange operands,
818 int64_t dim) {
819 // If any input operand contains a static size greater than 1 for this
820 // dimension, that is the target size. An occurrence of an additional static
821 // dimension greater than 1 with a different value is undefined behavior.
822 for (auto operand : operands) {
823 auto size = cast<RankedTensorType>(operand.getType()).getDimSize(dim);
824 if (ShapedType::isStatic(size) && size > 1)
825 return {rewriter.getIndexAttr(size), operand};
826 }
827
828 // Filter operands with dynamic dimension
829 auto operandsWithDynamicDim =
830 llvm::filter_to_vector(operands, [&](Value operand) {
831 return cast<RankedTensorType>(operand.getType()).isDynamicDim(dim);
832 });
833
834 // If no operand has a dynamic dimension, it means all sizes were 1
835 if (operandsWithDynamicDim.empty())
836 return {rewriter.getIndexAttr(1), operands.front()};
837
838 // Emit code that computes the runtime size for this dimension. If there is
839 // only one operand with a dynamic dimension, it is considered the master
840 // operand that determines the runtime size of the output dimension.
841 auto targetSize =
842 getTensorDim(rewriter, loc, indexPool, operandsWithDynamicDim[0], dim);
843 if (operandsWithDynamicDim.size() == 1)
844 return {targetSize, operandsWithDynamicDim[0]};
845
846 // Calculate maximum size among all dynamic dimensions
847 for (size_t i = 1; i < operandsWithDynamicDim.size(); i++) {
848 auto nextSize =
849 getTensorDim(rewriter, loc, indexPool, operandsWithDynamicDim[i], dim);
850 targetSize = arith::MaxUIOp::create(rewriter, loc, targetSize, nextSize);
851 }
852 return {targetSize, nullptr};
853}
854
855// Compute the runtime output size for all dimensions. This function returns
856// a pair {targetShape, masterOperands}.
857static std::pair<SmallVector<OpFoldResult>, SmallVector<Value>>
859 IndexPool &indexPool, ValueRange operands) {
860 assert(!operands.empty());
861 auto rank = cast<RankedTensorType>(operands.front().getType()).getRank();
862 SmallVector<OpFoldResult> targetShape;
863 SmallVector<Value> masterOperands;
864 for (auto dim : llvm::seq<int64_t>(0, rank)) {
865 auto [targetSize, masterOperand] =
866 computeTargetSize(rewriter, loc, indexPool, operands, dim);
867 targetShape.push_back(targetSize);
868 masterOperands.push_back(masterOperand);
869 }
870 return {targetShape, masterOperands};
871}
872
874 IndexPool &indexPool, Value operand,
875 int64_t dim, OpFoldResult targetSize,
876 Value masterOperand) {
877 // Nothing to do if this is a static dimension
878 auto rankedTensorType = cast<RankedTensorType>(operand.getType());
879 if (!rankedTensorType.isDynamicDim(dim))
880 return operand;
881
882 // If the target size for this dimension was directly inferred by only taking
883 // this operand into account, there is no need to broadcast. This is an
884 // optimization that will prevent redundant control flow, and constitutes the
885 // main motivation for tracking "master operands".
886 if (operand == masterOperand)
887 return operand;
888
889 // Affine maps for 'linalg.generic' op
890 auto rank = rankedTensorType.getRank();
891 SmallVector<AffineExpr> affineExprs;
892 for (auto index : llvm::seq<int64_t>(0, rank)) {
893 auto affineExpr = index == dim ? rewriter.getAffineConstantExpr(0)
894 : rewriter.getAffineDimExpr(index);
895 affineExprs.push_back(affineExpr);
896 }
897 auto broadcastAffineMap =
898 AffineMap::get(rank, 0, affineExprs, rewriter.getContext());
899 auto identityAffineMap = rewriter.getMultiDimIdentityMap(rank);
900 SmallVector<AffineMap> affineMaps = {broadcastAffineMap, identityAffineMap};
901
902 // Check if broadcast is necessary
903 auto one = createIndex(rewriter, loc, indexPool, 1);
904 auto runtimeSize = getTensorDim(rewriter, loc, indexPool, operand, dim);
905 auto broadcastNecessary = arith::CmpIOp::create(
906 rewriter, loc, arith::CmpIPredicate::eq, runtimeSize, one);
907
908 // Emit 'then' region of 'scf.if'
909 auto emitThenRegion = [&](OpBuilder &opBuilder, Location loc) {
910 // It is not safe to cache constants across regions.
911 // New constants could potentially violate dominance requirements.
912 IndexPool localPool;
913
914 // Emit 'tensor.empty' op
915 SmallVector<OpFoldResult> outputTensorShape;
916 for (auto index : llvm::seq<int64_t>(0, rank)) {
917 auto size = index == dim ? targetSize
918 : getOrFoldTensorDim(rewriter, loc, localPool,
919 operand, index);
920 outputTensorShape.push_back(size);
921 }
922 Value outputTensor = tensor::EmptyOp::create(
923 opBuilder, loc, outputTensorShape, rankedTensorType.getElementType());
924
925 // Emit 'linalg.generic' op
926 auto resultTensor =
927 linalg::GenericOp::create(
928 opBuilder, loc, outputTensor.getType(), operand, outputTensor,
929 affineMaps, getNParallelLoopsAttrs(rank),
930 [&](OpBuilder &opBuilder, Location loc, ValueRange blockArgs) {
931 // Emit 'linalg.yield' op
932 linalg::YieldOp::create(opBuilder, loc, blockArgs.front());
933 })
934 .getResult(0);
935
936 // Cast to original operand type if necessary
937 auto castResultTensor = rewriter.createOrFold<tensor::CastOp>(
938 loc, operand.getType(), resultTensor);
939
940 // Emit 'scf.yield' op
941 scf::YieldOp::create(opBuilder, loc, castResultTensor);
942 };
943
944 // Emit 'else' region of 'scf.if'
945 auto emitElseRegion = [&](OpBuilder &opBuilder, Location loc) {
946 scf::YieldOp::create(opBuilder, loc, operand);
947 };
948
949 // Emit 'scf.if' op
950 auto ifOp = scf::IfOp::create(rewriter, loc, broadcastNecessary,
951 emitThenRegion, emitElseRegion);
952 return ifOp.getResult(0);
953}
954
956 IndexPool &indexPool, Value operand,
957 ArrayRef<OpFoldResult> targetShape,
958 ArrayRef<Value> masterOperands) {
959 int64_t rank = cast<RankedTensorType>(operand.getType()).getRank();
960 assert((int64_t)targetShape.size() == rank);
961 assert((int64_t)masterOperands.size() == rank);
962 for (auto index : llvm::seq<int64_t>(0, rank))
963 operand =
964 broadcastDynamicDimension(rewriter, loc, indexPool, operand, index,
965 targetShape[index], masterOperands[index]);
966 return operand;
967}
968
971 IndexPool &indexPool, ValueRange operands,
972 ArrayRef<OpFoldResult> targetShape,
973 ArrayRef<Value> masterOperands) {
974 // No need to broadcast for unary operations
975 if (operands.size() == 1)
976 return operands;
977
978 // No need to broadcast for static shape
979 bool hasDynamic = false;
980 for (auto op : operands) {
981 const auto tType = dyn_cast<RankedTensorType>(op.getType());
982 if (tType && !tType.hasStaticShape()) {
983 hasDynamic = true;
984 break;
985 }
986 }
987 if (!hasDynamic)
988 return operands;
989
990 // Broadcast dynamic dimensions operand by operand
991 return llvm::map_to_vector(operands, [&](Value operand) {
992 return broadcastDynamicDimensions(rewriter, loc, indexPool, operand,
993 targetShape, masterOperands);
994 });
995}
996
997static LogicalResult emitElementwiseComputation(
998 ConversionPatternRewriter &rewriter, Location loc, Operation *operation,
999 ValueRange operands, ValueRange scalarOperands,
1000 ArrayRef<OpFoldResult> targetShape, const TypeConverter &converter) {
1001 // Generate output tensor
1002 auto resultType = cast_or_null<RankedTensorType>(
1003 converter.convertType(operation->getResultTypes().front()));
1004 if (!resultType) {
1005 return rewriter.notifyMatchFailure(operation, "failed to convert type");
1006 }
1007 Value outputTensor = tensor::EmptyOp::create(rewriter, loc, targetShape,
1008 resultType.getElementType());
1009
1010 // Create affine maps. Input affine maps broadcast static dimensions of size
1011 // 1. The output affine map is an identity map.
1012 //
1013 auto rank = resultType.getRank();
1014 auto affineMaps = llvm::map_to_vector(operands, [&](Value operand) {
1015 auto shape = cast<ShapedType>(operand.getType()).getShape();
1016 SmallVector<AffineExpr> affineExprs;
1017 for (auto it : llvm::enumerate(shape)) {
1018 // Prefer producting identity maps whenever possible (i.e. no broadcasting
1019 // needed) because some transforms (like reshape folding)
1020 // do not support affine constant exprs.
1021 bool requiresBroadcast =
1022 (it.value() == 1 && resultType.getDimSize(it.index()) != 1);
1023 auto affineExpr = requiresBroadcast
1024 ? rewriter.getAffineConstantExpr(0)
1025 : rewriter.getAffineDimExpr(it.index());
1026 affineExprs.push_back(affineExpr);
1027 }
1028 return AffineMap::get(rank, 0, affineExprs, rewriter.getContext());
1029 });
1030 // Scalar operands are single-element tensors: every iteration reads their
1031 // first element.
1032 for (Value scalar : scalarOperands) {
1033 auto scalarRank = cast<ShapedType>(scalar.getType()).getRank();
1034 SmallVector<AffineExpr> zeros(scalarRank,
1035 rewriter.getAffineConstantExpr(0));
1036 affineMaps.push_back(AffineMap::get(rank, 0, zeros, rewriter.getContext()));
1037 }
1038 affineMaps.push_back(rewriter.getMultiDimIdentityMap(rank));
1039
1040 SmallVector<Value> inputs(operands);
1041 llvm::append_range(inputs, scalarOperands);
1042
1043 // Emit 'linalg.generic' op
1044 bool encounteredError = false;
1045 auto linalgOp = linalg::GenericOp::create(
1046 rewriter, loc, outputTensor.getType(), inputs, outputTensor, affineMaps,
1048 [&](OpBuilder &opBuilder, Location loc, ValueRange blockArgs) {
1050 operation, blockArgs.take_front(operation->getNumOperands()),
1051 {resultType.getElementType()}, rewriter);
1052 if (!opResult) {
1053 encounteredError = true;
1054 return;
1055 }
1056 linalg::YieldOp::create(opBuilder, loc, opResult);
1057 });
1058 if (encounteredError)
1059 return rewriter.notifyMatchFailure(
1060 operation, "unable to create linalg.generic body for elementwise op");
1061
1062 // Cast 'linalg.generic' result into original result type if needed
1063 auto castResult = rewriter.createOrFold<tensor::CastOp>(
1064 loc, resultType, linalgOp->getResult(0));
1065 rewriter.replaceOp(operation, castResult);
1066 return success();
1067}
1068
1069// The shift of tosa.mul and the zero points of tosa.negate are single-element
1070// tensors, so they never take part in broadcasting.
1072 ValueRange operands) {
1073 if (isa<tosa::MulOp>(operation))
1074 return operands.take_front(2);
1075 if (isa<tosa::NegateOp>(operation))
1076 return operands.take_front(1);
1077 return operands;
1078}
1079
1080// Returns the single-element operands that the body reads as block arguments:
1081// a non-constant tosa.mul shift, or both tosa.negate zero points when either of
1082// them is non-constant. Constant ones are created in the body instead.
1083static ValueRange getScalarOperands(Operation *operation, ValueRange operands) {
1084 if (isa<tosa::MulOp>(operation)) {
1085 DenseElementsAttr shiftElems;
1086 if (matchPattern(operation->getOperand(2), m_Constant(&shiftElems)))
1087 return {};
1088 return operands.drop_front(2);
1089 }
1090 if (auto negate = dyn_cast<tosa::NegateOp>(operation)) {
1091 if (succeeded(negate.getInput1ZeroPoint()) &&
1092 succeeded(negate.getOutputZeroPoint()))
1093 return {};
1094 return operands.drop_front(1);
1095 }
1096 return {};
1097}
1098
1099static LogicalResult
1101 ConversionPatternRewriter &rewriter,
1102 const TypeConverter &converter) {
1103
1104 // Collect op properties
1105 assert(operation->getNumResults() == 1 && "elementwise op expects 1 result");
1106 assert(operation->getNumOperands() >= 1 &&
1107 "elementwise op expects at least 1 operand");
1108 if (!operandsAndResultsRanked(operation))
1109 return rewriter.notifyMatchFailure(operation,
1110 "Unranked tensors not supported");
1111
1112 // Lower operation
1113 IndexPool indexPool;
1114 auto loc = operation->getLoc();
1115 auto operandsToBroadcast = getBroadcastableOperands(operation, operands);
1116 auto scalarOperands = getScalarOperands(operation, operands);
1117 auto [targetShape, masterOperands] =
1118 computeTargetShape(rewriter, loc, indexPool, operandsToBroadcast);
1119 auto broadcastOperands =
1120 broadcastDynamicDimensions(rewriter, loc, indexPool, operandsToBroadcast,
1121 targetShape, masterOperands);
1122 return emitElementwiseComputation(rewriter, loc, operation, broadcastOperands,
1123 scalarOperands, targetShape, converter);
1124}
1125
1126// Returns the constant initial value for a given reduction operation. The
1127// attribute type varies depending on the element type required.
1128static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy,
1129 PatternRewriter &rewriter,
1130 bool allowNonFinites) {
1131 if (isa<tosa::ReduceSumOp>(op) && isa<FloatType>(elementTy))
1132 return rewriter.getFloatAttr(elementTy, 0.0);
1133
1134 if (isa<tosa::ReduceSumOp>(op) && isa<IntegerType>(elementTy))
1135 return rewriter.getIntegerAttr(elementTy, 0);
1136
1137 if (isa<tosa::ReduceProductOp>(op) && isa<FloatType>(elementTy))
1138 return rewriter.getFloatAttr(elementTy, 1.0);
1139
1140 if (isa<tosa::ReduceProductOp>(op) && isa<IntegerType>(elementTy))
1141 return rewriter.getIntegerAttr(elementTy, 1);
1142
1143 if (isa<tosa::ReduceMinOp>(op) && isa<FloatType>(elementTy))
1144 return rewriter.getFloatAttr(
1145 elementTy,
1146 getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
1147 /*negative=*/false, allowNonFinites));
1148
1149 if (isa<tosa::ReduceMinOp>(op) && isa<IntegerType>(elementTy))
1150 return rewriter.getIntegerAttr(
1151 elementTy, APInt::getSignedMaxValue(elementTy.getIntOrFloatBitWidth()));
1152
1153 if (isa<tosa::ReduceMaxOp>(op) && isa<FloatType>(elementTy))
1154 return rewriter.getFloatAttr(
1155 elementTy,
1156 getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
1157 /*negative=*/true, allowNonFinites));
1158
1159 if (isa<tosa::ReduceMaxOp>(op) && isa<IntegerType>(elementTy))
1160 return rewriter.getIntegerAttr(
1161 elementTy, APInt::getSignedMinValue(elementTy.getIntOrFloatBitWidth()));
1162
1163 if (isa<tosa::ReduceAllOp>(op) && elementTy.isInteger(1))
1164 return rewriter.getIntegerAttr(elementTy, APInt::getAllOnes(1));
1165
1166 if (isa<tosa::ReduceAnyOp>(op) && elementTy.isInteger(1))
1167 return rewriter.getIntegerAttr(elementTy, APInt::getZero(1));
1168
1169 if (isa<tosa::ArgMaxOp>(op) && isa<FloatType>(elementTy))
1170 return rewriter.getFloatAttr(
1171 elementTy,
1172 getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
1173 /*negative=*/true, allowNonFinites));
1174
1175 if (isa<tosa::ArgMaxOp>(op) && isa<IntegerType>(elementTy))
1176 return rewriter.getIntegerAttr(
1177 elementTy, APInt::getSignedMinValue(elementTy.getIntOrFloatBitWidth()));
1178
1179 if (isa<tosa::ArgMinOp>(op) && isa<FloatType>(elementTy))
1180 return rewriter.getFloatAttr(
1181 elementTy,
1182 getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
1183 /*negative=*/false, allowNonFinites));
1184
1185 if (isa<tosa::ArgMinOp>(op) && isa<IntegerType>(elementTy))
1186 return rewriter.getIntegerAttr(
1187 elementTy, APInt::getSignedMaxValue(elementTy.getIntOrFloatBitWidth()));
1188
1189 return {};
1190}
1191
1192// Creates the body calculation for a reduction. The operations vary depending
1193// on the input type.
1195 ValueRange args,
1196 Type elementTy,
1197 PatternRewriter &rewriter) {
1198 Location loc = op->getLoc();
1199 if (isa<tosa::ReduceSumOp>(op) && isa<FloatType>(elementTy)) {
1201 rewriter, loc, TypeRange{elementTy}, args);
1202 }
1203
1204 if (isa<tosa::ReduceSumOp>(op) && isa<IntegerType>(elementTy)) {
1206 rewriter, loc, TypeRange{elementTy}, args);
1207 }
1208
1209 if (isa<tosa::ReduceProductOp>(op) && isa<FloatType>(elementTy)) {
1211 rewriter, loc, TypeRange{elementTy}, args);
1212 }
1213
1214 if (isa<tosa::ReduceProductOp>(op) && isa<IntegerType>(elementTy)) {
1216 rewriter, loc, TypeRange{elementTy}, args);
1217 }
1218
1219 if (isa<tosa::ReduceMinOp>(op) && isa<FloatType>(elementTy)) {
1220 return arith::MinimumFOp::create(rewriter, loc, args[0], args[1]);
1221 }
1222
1223 if (isa<tosa::ReduceMinOp>(op) && isa<IntegerType>(elementTy)) {
1224 return arith::MinSIOp::create(rewriter, loc, args[0], args[1]);
1225 }
1226
1227 if (isa<tosa::ReduceMaxOp>(op) && isa<FloatType>(elementTy)) {
1228 return arith::MaximumFOp::create(rewriter, loc, args[0], args[1]);
1229 }
1230
1231 if (isa<tosa::ReduceMaxOp>(op) && isa<IntegerType>(elementTy)) {
1232 return arith::MaxSIOp::create(rewriter, loc, args[0], args[1]);
1233 }
1234
1235 if (isa<tosa::ReduceAllOp>(op) && elementTy.isInteger(1))
1236 return arith::AndIOp::create(rewriter, loc, args);
1237
1238 if (isa<tosa::ReduceAnyOp>(op) && elementTy.isInteger(1))
1239 return arith::OrIOp::create(rewriter, loc, args);
1240
1241 return {};
1242}
1243
1244// Performs the match and rewrite for reduction operations. This includes
1245// declaring a correctly sized initial value, and the linalg.generic operation
1246// that reduces across the specified axis.
1247template <typename OpTy>
1248static LogicalResult reduceMatchAndRewriteHelper(OpTy op, uint64_t axis,
1249 PatternRewriter &rewriter,
1250 bool allowNonFinites) {
1251 auto loc = op->getLoc();
1252 auto inputTy = dyn_cast<RankedTensorType>(op->getOperand(0).getType());
1253 auto resultTy = dyn_cast<RankedTensorType>(op->getResult(0).getType());
1254 if (!inputTy || !resultTy)
1255 return rewriter.notifyMatchFailure(op, "unranked tensors not supported");
1256
1257 auto elementTy = resultTy.getElementType();
1258 Value input = op->getOperand(0);
1259
1260 // Figure out the accType if needed
1261 bool widenAccTy = std::is_same_v<OpTy, tosa::ReduceSumOp> &&
1262 isa<FloatType>(elementTy) &&
1263 cast<FloatType>(elementTy).isBF16();
1264 Type accTy = widenAccTy ? rewriter.getF32Type() : elementTy;
1265
1266 SmallVector<int64_t> reduceShape;
1267 SmallVector<Value> dynDims;
1268 for (unsigned i = 0; i < inputTy.getRank(); i++) {
1269 if (axis != i) {
1270 reduceShape.push_back(inputTy.getDimSize(i));
1271 if (inputTy.isDynamicDim(i))
1272 dynDims.push_back(tensor::DimOp::create(rewriter, loc, input, i));
1273 }
1274 }
1275
1276 SmallVector<Value> inputs, outputs;
1277 inputs.push_back(input);
1278
1279 // First fill the output buffer with the init value.
1280 auto emptyTensor =
1281 tensor::EmptyOp::create(rewriter, loc, reduceShape, accTy, dynDims)
1282 .getResult();
1283
1284 auto fillValueAttr =
1285 createInitialValueForReduceOp(op, accTy, rewriter, allowNonFinites);
1286 if (!fillValueAttr)
1287 return rewriter.notifyMatchFailure(
1288 op, "No initial value found for reduction operation");
1289
1290 auto fillValue = arith::ConstantOp::create(rewriter, loc, fillValueAttr);
1291 auto filledTensor =
1292 linalg::FillOp::create(rewriter, loc, ValueRange{fillValue},
1293 ValueRange{emptyTensor})
1294 .result();
1295 outputs.push_back(filledTensor);
1296
1297 bool isNanIgnoreMode = false;
1298 if constexpr (std::is_same_v<OpTy, tosa::ReduceMinOp> ||
1299 std::is_same_v<OpTy, tosa::ReduceMaxOp>) {
1300 // NaN propagation has no meaning for non floating point types.
1301 if (isa<FloatType>(elementTy) &&
1302 op.getNanMode() == NanPropagationMode::IGNORE) {
1303 isNanIgnoreMode = true;
1304 // Because the TOSA spec requires the result be NaN iff all elements in
1305 // the reduction are NaN we can't simply perform a compare and select.
1306 // Additionally we have to keep track of whether we've seen any non-NaN
1307 // values and then do a final select based on this predicate.
1308 auto trueAttr = rewriter.getBoolAttr(true);
1309 auto trueValue = arith::ConstantOp::create(rewriter, loc, trueAttr);
1310 auto emptyBoolTensor =
1311 tensor::EmptyOp::create(rewriter, loc, reduceShape,
1312 trueValue.getType(), dynDims)
1313 .getResult();
1314 auto allResultsNaNTensor =
1315 linalg::FillOp::create(rewriter, loc, ValueRange{trueValue},
1316 ValueRange{emptyBoolTensor})
1317 .result();
1318 // Note that because the linalg::ReduceOp has two variadic arguments
1319 // (inputs and outputs) and it has the SameVariadicOperandSize trait we
1320 // need to have the same number of inputs and outputs.
1321 //
1322 // The second input isn't actually used anywhere since the value used to
1323 // update the NaN flag is calculated inside the body of the reduction and
1324 // then used to update an out value.
1325 // In order to satisfy type constraints we just pass another copy of the
1326 // input here.
1327 inputs.push_back(input);
1328 outputs.push_back(allResultsNaNTensor);
1329 }
1330 }
1331
1332 bool didEncounterError = false;
1333 linalg::LinalgOp linalgOp = linalg::ReduceOp::create(
1334 rewriter, loc, inputs, outputs, axis,
1335 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange blockArgs) {
1336 std::array<Value, 2> binaryArgs{
1337 blockArgs[0], isNanIgnoreMode ? blockArgs[2] : blockArgs[1]};
1338
1339 // If reduction type differs then extend (applicable to reduce_sum)
1340 if (binaryArgs[0].getType() != accTy)
1341 binaryArgs[0] = arith::ExtFOp::create(
1342 nestedBuilder, nestedLoc, TypeRange{accTy},
1343 ValueRange{binaryArgs[0]}, arith::ExtFOp::Properties{});
1344
1345 auto result = createLinalgBodyCalculationForReduceOp(op, binaryArgs,
1346 accTy, rewriter);
1347 if (result)
1348 didEncounterError = true;
1349
1350 SmallVector<Value> resultsToYield;
1351 if (isNanIgnoreMode) {
1352 auto inputValue = blockArgs[0];
1353 auto initialValue = blockArgs[2];
1354 auto oldAllResultsNanFlagValue = blockArgs[3];
1355
1356 // Unordered comparison of NaN against itself will always return true.
1357 Value isNaN = arith::CmpFOp::create(nestedBuilder, op->getLoc(),
1358 arith::CmpFPredicate::UNO,
1359 inputValue, inputValue);
1360 // If we've encountered a NaN, take the non-NaN value.
1361 auto selectOp = arith::SelectOp::create(nestedBuilder, op->getLoc(),
1362 isNaN, initialValue, result);
1363 // Update the flag which keeps track of whether we have seen a non-NaN
1364 // value.
1365 auto newAllResultsNanFlagValue = arith::AndIOp::create(
1366 nestedBuilder, op->getLoc(), oldAllResultsNanFlagValue, isNaN);
1367 resultsToYield.push_back(selectOp);
1368 resultsToYield.push_back(newAllResultsNanFlagValue);
1369 } else {
1370 resultsToYield.push_back(result);
1371 }
1372 linalg::YieldOp::create(nestedBuilder, loc, resultsToYield);
1373 });
1374
1375 if (!didEncounterError)
1376 return rewriter.notifyMatchFailure(
1377 op, "unable to create linalg.generic body for reduce op");
1378
1379 if (isNanIgnoreMode) {
1380 // Materialize a check to see whether we encountered any non-NaN values, if
1381 // we didn't we need to select a tensor of NaNs since the result will just
1382 // be the initial identity value propagated through all the compares and
1383 // selects inside the reduction.
1384
1385 // Create a tensor full of NaNs.
1386 auto nanValueAttr = rewriter.getFloatAttr(
1387 accTy,
1388 APFloat::getNaN(cast<FloatType>(elementTy).getFloatSemantics(), false));
1389 auto nanValue = arith::ConstantOp::create(rewriter, loc, nanValueAttr);
1390 auto emptyNanTensor =
1391 tensor::EmptyOp::create(rewriter, loc, reduceShape, accTy, dynDims)
1392 .getResult();
1393 auto nanFilledTensor =
1394 linalg::FillOp::create(rewriter, loc, ValueRange{nanValue},
1395 ValueRange{emptyNanTensor})
1396 .result();
1397
1398 // Create an empty tensor, non need to fill this since it will be
1399 // overwritten by the select.
1400 auto finalEmptyTensor =
1401 tensor::EmptyOp::create(rewriter, loc, reduceShape, accTy, dynDims)
1402 .getResult();
1403
1404 // Do a selection between the tensors akin to:
1405 // result = NaN if "all results NaN" else result.
1406 SmallVector<Value> ins, outs;
1407 ins.push_back(linalgOp->getOpResult(1));
1408 ins.push_back(nanFilledTensor);
1409 ins.push_back(linalgOp->getResult(0));
1410 outs.push_back(finalEmptyTensor);
1411 auto linalgSelect =
1412 linalg::ElementwiseOp::create(rewriter, op->getLoc(), ins, outs,
1413 mlir::linalg::ElementwiseKind::select);
1414 linalgOp = linalgSelect;
1415 }
1416
1417 // Truncate back to resultTy if needed
1418 Value reducedRes = linalgOp->getResult(0);
1419 if (widenAccTy) {
1420 auto resEmptyOp =
1421 tensor::EmptyOp::create(rewriter, loc, reduceShape, elementTy, dynDims)
1422 .getResult();
1423
1424 const unsigned reducedRank =
1425 cast<ShapedType>(reducedRes.getType()).getRank();
1426 auto identityMap = rewriter.getMultiDimIdentityMap(reducedRank);
1427 reducedRes =
1428 linalg::GenericOp::create(
1429 rewriter, loc, resEmptyOp.getType(), ValueRange{reducedRes},
1430 ValueRange{resEmptyOp},
1431 ArrayRef<AffineMap>{identityMap, identityMap},
1432 getNParallelLoopsAttrs(reducedRank),
1433 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange args) {
1434 Value truncf = arith::TruncFOp::create(nestedBuilder, nestedLoc,
1435 elementTy, args[0]);
1436 linalg::YieldOp::create(nestedBuilder, nestedLoc, truncf);
1437 })
1438 .getResults()[0];
1439 }
1440
1441 SmallVector<ReassociationExprs, 4> reassociationMap;
1442 uint64_t expandInputRank = cast<ShapedType>(reducedRes.getType()).getRank();
1443 reassociationMap.resize(expandInputRank);
1444
1445 for (uint64_t i = 0; i < expandInputRank; i++) {
1446 int32_t dimToPush = i > axis ? i + 1 : i;
1447 reassociationMap[i].push_back(rewriter.getAffineDimExpr(dimToPush));
1448 }
1449
1450 if (expandInputRank != 0) {
1451 int32_t expandedDim = axis < expandInputRank ? axis : expandInputRank - 1;
1452 reassociationMap[expandedDim].push_back(
1453 rewriter.getAffineDimExpr(expandedDim + 1));
1454 }
1455
1456 // Lower directly to `tensor::ExpandShapeOp` instead of `tosa::ReshapeOp`,
1457 // since here we know which dimension to expand, and `tosa::ReshapeOp` would
1458 // not have access to such information. This matters when handling dynamically
1459 // sized tensors.
1460 rewriter.replaceOpWithNewOp<tensor::ExpandShapeOp>(op, resultTy, reducedRes,
1461 reassociationMap);
1462 return success();
1463}
1464
1465// Insert missing leading batch dimensions, taking their sizes from `other`.
1467 Location loc, Value operand,
1468 Value other) {
1469 auto type = cast<RankedTensorType>(operand.getType());
1470 int64_t missingDims =
1471 cast<RankedTensorType>(other.getType()).getRank() - type.getRank();
1472 if (missingDims <= 0)
1473 return operand;
1474
1476 SmallVector<int64_t> dimensions;
1477 for (int64_t dim = 0; dim < missingDims; ++dim) {
1478 shape.push_back(tensor::getMixedSize(rewriter, loc, other, dim));
1479 dimensions.push_back(dim);
1480 }
1481 llvm::append_range(shape, tensor::getMixedSizes(rewriter, loc, operand));
1482 Value empty = tensor::EmptyOp::create(
1483 rewriter, loc, shape, type.getElementType(), type.getEncoding());
1484 return linalg::BroadcastOp::create(rewriter, loc, operand, empty, dimensions)
1485 .getResult()
1486 .front();
1487}
1488
1489// Expand an existing singleton batch dimension to `size`, preserving the rank.
1490// Missing dimensions are handled by broadcastMatMulLeadingDimensions.
1491// linalg.broadcast inserts dimensions, so first remove the singleton dimension
1492// with a rank-reducing slice, then reinsert it at the target size.
1494 Location loc, Value operand,
1495 int64_t dim, OpFoldResult size) {
1496 auto type = cast<RankedTensorType>(operand.getType());
1497 SmallVector<int64_t> sliceShape(type.getShape());
1498 sliceShape.erase(sliceShape.begin() + dim);
1499 auto sliceType = RankedTensorType::get(sliceShape, type.getElementType(),
1500 type.getEncoding());
1502 rewriter, loc, operand, sliceType);
1503
1505 tensor::getMixedSizes(rewriter, loc, operand);
1506 shape[dim] = size;
1507 Value empty = tensor::EmptyOp::create(
1508 rewriter, loc, shape, type.getElementType(), type.getEncoding());
1509 return linalg::BroadcastOp::create(rewriter, loc, slice, empty,
1510 ArrayRef<int64_t>{dim})
1511 .getResult()
1512 .front();
1513}
1514
1515// Align the ranks, then broadcast each shared batch dimension independently.
1516static std::pair<Value, Value>
1518 Value b, int64_t batchRank) {
1519 int64_t aRank = cast<RankedTensorType>(a.getType()).getRank();
1520 int64_t bRank = cast<RankedTensorType>(b.getType()).getRank();
1521 int64_t firstSharedDim = std::max(aRank, bRank) - std::min(aRank, bRank);
1522 a = broadcastMatMulLeadingDimensions(rewriter, loc, a, b);
1523 b = broadcastMatMulLeadingDimensions(rewriter, loc, b, a);
1524
1525 IndexPool indexPool;
1526 // The inserted leading dimensions already match their partner's sizes.
1527 for (int64_t dim = firstSharedDim; dim < batchRank; ++dim) {
1528 auto aType = cast<RankedTensorType>(a.getType());
1529 auto bType = cast<RankedTensorType>(b.getType());
1530 int64_t aSize = aType.getDimSize(dim);
1531 int64_t bSize = bType.getDimSize(dim);
1532 if (aSize == bSize && ShapedType::isStatic(aSize))
1533 continue;
1534
1535 // Compute the shared broadcast target only for batch dimensions.
1536 // The matrix dimensions may differ between A and B.
1537 auto [size, masterOperand] =
1538 computeTargetSize(rewriter, loc, indexPool, ValueRange{a, b}, dim);
1539 if (aSize == 1) {
1540 a = broadcastMatMulSingletonDimension(rewriter, loc, a, dim, size);
1541 continue;
1542 }
1543 if (bSize == 1) {
1544 b = broadcastMatMulSingletonDimension(rewriter, loc, b, dim, size);
1545 continue;
1546 }
1547 a = broadcastDynamicDimension(rewriter, loc, indexPool, a, dim, size,
1548 masterOperand);
1549 b = broadcastDynamicDimension(rewriter, loc, indexPool, b, dim, size,
1550 masterOperand);
1551 }
1552 return {a, b};
1553}
1554
1556 Location loc, Value a,
1557 Value b,
1558 RankedTensorType outputType,
1559 bool transposeB) {
1560 const int64_t outputRank = outputType.getRank();
1561 const int64_t batchRank = outputRank - 2;
1562 auto normalizedOperands =
1563 normalizeMatMulBatchDimensions(rewriter, loc, a, b, batchRank);
1564 a = normalizedOperands.first;
1565 b = normalizedOperands.second;
1566
1567 SmallVector<OpFoldResult> outputShape;
1568 outputShape.reserve(outputRank);
1569 for (int64_t batchDim = 0; batchDim < batchRank; ++batchDim)
1570 outputShape.push_back(tensor::getMixedSize(rewriter, loc, a, batchDim));
1571 outputShape.push_back(tensor::getMixedSize(rewriter, loc, a, outputRank - 2));
1572 outputShape.push_back(
1573 tensor::getMixedSize(rewriter, loc, b, outputRank - 1 - transposeB));
1574 SmallVector<Value> dynamicDims;
1575 for (auto [dim, size] : llvm::enumerate(outputShape))
1576 if (outputType.isDynamicDim(dim))
1577 dynamicDims.push_back(
1578 getValueOrCreateConstantIndexOp(rewriter, loc, size));
1579
1580 Value zero = arith::ConstantOp::create(
1581 rewriter, loc, rewriter.getZeroAttr(outputType.getElementType()));
1582 Value emptyTensor = tensor::EmptyOp::create(
1583 rewriter, loc, outputType.getShape(), outputType.getElementType(),
1584 condenseValues(dynamicDims));
1585 Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
1586 ValueRange{emptyTensor})
1587 .result();
1588
1589 return {a, b, zeroTensor};
1590}
1591
1592namespace {
1593// Linalg has quantized matmul and batch matmul operations, but neither has a
1594// transpose-B variant. Lower nonzero zero points separately to linalg.generic
1595// so the reduction can accumulate (A - aZp) * (B - bZp).
1596class QuantizedMatMulTConverter : public OpConversionPattern<tosa::MatMulTOp> {
1597public:
1598 using OpConversionPattern<tosa::MatMulTOp>::OpConversionPattern;
1599 LogicalResult
1600 matchAndRewrite(tosa::MatMulTOp op, OpAdaptor adaptor,
1601 ConversionPatternRewriter &rewriter) const final {
1602 Location loc = op.getLoc();
1603 auto aType = dyn_cast<RankedTensorType>(adaptor.getA().getType());
1604 auto bType = dyn_cast<RankedTensorType>(adaptor.getB().getType());
1605 auto outputType = dyn_cast<RankedTensorType>(op.getType());
1606 if (!aType || !bType || !outputType)
1607 return rewriter.notifyMatchFailure(op, "only supports ranked tensors");
1608
1609 FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
1610 FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
1611 if (failed(maybeAZp))
1612 return rewriter.notifyMatchFailure(
1613 op, "input A zero point cannot be statically determined");
1614 if (failed(maybeBZp))
1615 return rewriter.notifyMatchFailure(
1616 op, "input B zero point cannot be statically determined");
1617
1618 const int64_t aZpVal = *maybeAZp;
1619 const int64_t bZpVal = *maybeBZp;
1620
1621 if (aZpVal == 0 && bZpVal == 0)
1622 return rewriter.notifyMatchFailure(
1623 op, "requires at least one nonzero zero point");
1624
1625 const int64_t outputRank = outputType.getRank();
1626 const int64_t batchRank = outputRank - 2;
1627 auto [a, b, zeroTensor] =
1628 prepareMatMulOperands(rewriter, loc, adaptor.getA(), adaptor.getB(),
1629 outputType, /*transposeB=*/true);
1630 Type outputElementType = outputType.getElementType();
1631
1632 int64_t loopRank = outputRank + 1;
1633 SmallVector<AffineExpr> dimensions =
1634 llvm::map_to_vector(llvm::seq<int64_t>(0, loopRank), [&](int64_t dim) {
1635 return rewriter.getAffineDimExpr(dim);
1636 });
1637 SmallVector<AffineExpr> aMap(dimensions.begin(),
1638 dimensions.begin() + batchRank);
1639 aMap.push_back(dimensions[batchRank]);
1640 aMap.push_back(dimensions[batchRank + 2]);
1641 SmallVector<AffineExpr> bMap(dimensions.begin(),
1642 dimensions.begin() + batchRank);
1643 bMap.push_back(dimensions[batchRank + 1]);
1644 bMap.push_back(dimensions[batchRank + 2]);
1645 SmallVector<AffineExpr> outputMap(dimensions.begin(),
1646 dimensions.begin() + batchRank);
1647 outputMap.push_back(dimensions[batchRank]);
1648 outputMap.push_back(dimensions[batchRank + 1]);
1649 SmallVector<AffineMap> indexingMaps = {
1650 AffineMap::get(loopRank, 0, aMap, rewriter.getContext()),
1651 AffineMap::get(loopRank, 0, bMap, rewriter.getContext()),
1652 AffineMap::get(loopRank, 0, outputMap, rewriter.getContext())};
1653 SmallVector<utils::IteratorType> iteratorTypes(
1654 loopRank, utils::IteratorType::parallel);
1655 iteratorTypes.back() = utils::IteratorType::reduction;
1656
1657 auto generic = linalg::GenericOp::create(
1658 rewriter, loc, outputType, ValueRange{a, b}, zeroTensor, indexingMaps,
1659 iteratorTypes,
1660 [=](OpBuilder &builder, Location nestedLoc, ValueRange args) {
1661 auto extendToOutputType = [&](Value value) {
1662 if (value.getType() == outputElementType)
1663 return value;
1664 return arith::ExtSIOp::create(builder, nestedLoc, outputElementType,
1665 value)
1666 .getResult();
1667 };
1668
1669 Value aValue = extendToOutputType(args[0]);
1670 Value bValue = extendToOutputType(args[1]);
1671 Value aZp = arith::ConstantOp::create(
1672 builder, nestedLoc,
1673 builder.getIntegerAttr(outputElementType, aZpVal));
1674 Value bZp = arith::ConstantOp::create(
1675 builder, nestedLoc,
1676 builder.getIntegerAttr(outputElementType, bZpVal));
1677 Value adjustedA =
1678 arith::SubIOp::create(builder, nestedLoc, aValue, aZp);
1679 Value adjustedB =
1680 arith::SubIOp::create(builder, nestedLoc, bValue, bZp);
1681 Value product =
1682 arith::MulIOp::create(builder, nestedLoc, adjustedA, adjustedB);
1683 Value sum =
1684 arith::AddIOp::create(builder, nestedLoc, args[2], product);
1685 linalg::YieldOp::create(builder, nestedLoc, sum);
1686 });
1687 rewriter.replaceOp(op, generic.getResult(0));
1688 return success();
1689 }
1690};
1691
1692template <typename SrcOp>
1693class PointwiseConverter : public OpConversionPattern<SrcOp> {
1694public:
1695 using OpConversionPattern<SrcOp>::OpConversionPattern;
1696 using typename OpConversionPattern<SrcOp>::OpAdaptor;
1697
1698 LogicalResult
1699 matchAndRewrite(SrcOp op, OpAdaptor operands,
1700 ConversionPatternRewriter &rewriter) const final {
1702 op, operands.getOperands(), rewriter, *this->getTypeConverter());
1703 }
1704};
1705
1706// Collapse tensor<1xiN> into tensor<iN>
1707// E.g. tensor.collapse_shape %arg1 [] : tensor<1xi16> into tensor<i16>
1708static Value collapse1xNTensorToN(PatternRewriter &rewriter, Value input,
1709 Location loc) {
1711 // Create the collapsed type
1712 auto inputType = cast<RankedTensorType>(input.getType());
1713 auto elemType = inputType.getElementType();
1714 auto collapsedType = RankedTensorType::get({}, elemType);
1715 // Emit the collapse op
1716 return tensor::CollapseShapeOp::create(rewriter, loc, collapsedType, input,
1717 reassociation);
1718}
1719
1721convertToI8(const llvm::SmallVector<int32_t> &input) {
1723 output.reserve(input.size());
1724
1725 for (auto v : llvm::map_range(
1726 input, [](int32_t val) { return static_cast<int8_t>(val); })) {
1727 output.push_back(v);
1728 }
1729 return output;
1730}
1731
1732// The shift or multiplier may be either constant or non-constant, depending on
1733// whether dynamic extension is enabled.
1734// - If the shift or multiplier is non-constant, add it as an input to
1735// linalg::GenericOp by:
1736// 1. Pushing it into 'genericInputs'.
1737// 2. Appending a corresponding affine map to 'indexingMaps'.
1738// - If the shift or multiplier is constant, set 'constant' instead.
1739static void setupLinalgGenericOpInputAndIndexingMap(
1741 SmallVector<Value, 4> &genericInputs, SmallVector<AffineMap> &indexingMaps,
1742 bool isConstant, tosa::RescaleOp op, Value &constant, int64_t &arg,
1743 bool isShift = false) {
1744
1745 auto loc = op.getLoc();
1746 auto inputTy = cast<ShapedType>(op.getInput().getType());
1747 unsigned rank = inputTy.getRank();
1748 SmallVector<AffineExpr, 2> exprs = {rewriter.getAffineDimExpr(rank - 1)};
1749
1750 if (isConstant) {
1751 // If we are rescaling per-channel then we need to store the
1752 // values in a buffer.
1753 if (values.size() == 1) {
1754 IntegerAttr intAttr = isShift
1755 ? rewriter.getI8IntegerAttr(values.front())
1756 : rewriter.getI32IntegerAttr(values.front());
1757 constant = arith::ConstantOp::create(rewriter, loc, intAttr);
1758 } else {
1759 auto elementType =
1760 isShift ? rewriter.getIntegerType(8) : rewriter.getI32Type();
1761 auto tensorType = RankedTensorType::get(
1762 {static_cast<int64_t>(values.size())}, elementType);
1763 DenseIntElementsAttr EltAttr;
1764 if (isShift)
1765 EltAttr = DenseIntElementsAttr::get(tensorType, convertToI8(values));
1766 else
1767 EltAttr = DenseIntElementsAttr::get(tensorType, values);
1768 genericInputs.push_back(
1769 arith::ConstantOp::create(rewriter, loc, EltAttr));
1770 indexingMaps.push_back(AffineMap::get(/*dimCount=*/rank,
1771 /*symbolCount=*/0, exprs,
1772 rewriter.getContext()));
1773 }
1774 } else {
1775 // If we are not rescaling per-channel then we need to collapse 1xN to N
1776 // and push broadcastMap.
1777 auto operand = isShift ? op.getShift() : op.getMultiplier();
1778 auto tensorType = dyn_cast<RankedTensorType>(operand.getType());
1779 if (tensorType && tensorType.hasStaticShape() &&
1780 tensorType.getShape()[0] == 1) {
1781 // broadcastMap = affine_map<(d0, d1) -> ()>
1782 // It would affect as broadcast for scalar values in linalg::GenericOp.
1783 AffineMap broadcastMap =
1784 AffineMap::get(rank, 0, {}, rewriter.getContext());
1785 genericInputs.push_back(collapse1xNTensorToN(rewriter, operand, loc));
1786 indexingMaps.push_back(broadcastMap);
1787 } else {
1788 genericInputs.push_back(operand);
1789 indexingMaps.push_back(AffineMap::get(/*dimCount=*/rank,
1790 /*symbolCount=*/0, exprs,
1791 rewriter.getContext()));
1792 }
1793 }
1794 arg = indexingMaps.size() - 1;
1795}
1796
1797// Return the extended Zp to be used in subsequent arithmetic operations.
1798static Value getExtendZp(OpBuilder &builder, Type valueTy,
1799 FailureOr<int64_t> maybeZp, Location loc,
1800 ValueRange blockArgs, int64_t zpArg,
1801 bool isOutputZp = false) {
1802 Value result;
1803 const int32_t bitwidth = valueTy.getIntOrFloatBitWidth();
1804 const uint32_t attrBitwidth =
1805 isOutputZp ? 32 : (bitwidth > 32 ? bitwidth : 32);
1806 auto extendType = builder.getIntegerType(attrBitwidth);
1807 // The Zp value can be either constant or non-constant, depending on
1808 // whether dynamic extension is enabled.
1809 // If 'maybeZp' fails, it indicates that Zp is non-constant and will
1810 // be passed as an input to linalg::GenericOp.
1811 if (failed(maybeZp)) {
1812 result = blockArgs[zpArg];
1813 auto zpTy = result.getType();
1814 if (zpTy.getIntOrFloatBitWidth() < attrBitwidth) {
1815 // For ExtUIOp, the input must be signless.
1816 // UnrealizedConversionCastOp will cast the input to signless type.
1817 if (zpTy.isUnsignedInteger()) {
1818 result =
1819 UnrealizedConversionCastOp::create(
1820 builder, loc,
1821 builder.getIntegerType(zpTy.getIntOrFloatBitWidth()), result)
1822 .getResult(0);
1823 }
1824 if (zpTy.isUnsignedInteger()) {
1825 return arith::ExtUIOp::create(builder, loc, extendType, result);
1826 } else {
1827 return arith::ExtSIOp::create(builder, loc, extendType, result);
1828 }
1829 }
1830 } else {
1831 return arith::ConstantOp::create(builder, loc,
1832 IntegerAttr::get(extendType, *maybeZp));
1833 }
1834 return result;
1835}
1836
1837class RescaleConverter : public OpRewritePattern<tosa::RescaleOp> {
1838public:
1839 using OpRewritePattern<tosa::RescaleOp>::OpRewritePattern;
1840
1841 LogicalResult matchAndRewrite(tosa::RescaleOp op,
1842 PatternRewriter &rewriter) const final {
1843 auto loc = op.getLoc();
1844 auto input = op.getInput();
1845 auto inputTy = cast<ShapedType>(op.getInput().getType());
1846 auto outputTy = cast<ShapedType>(op.getOutput().getType());
1847 unsigned rank = inputTy.getRank();
1848
1849 // This is an illegal configuration. terminate and log an error
1850 if (op.getRoundingMode() == RoundingMode::INEXACT_ROUND)
1851 return rewriter.notifyMatchFailure(
1852 op, "tosa.rescale with rounding mode = 'INEXACT_ROUND' is not "
1853 "currently supported");
1854 if (op.getRoundingMode() == RoundingMode::DOUBLE_ROUND && !op.getScale32())
1855 return rewriter.notifyMatchFailure(
1856 op, "tosa.rescale requires scale32 for double_round to be true");
1857
1858 if (!isa<IntegerType>(inputTy.getElementType()))
1859 return rewriter.notifyMatchFailure(op, "only support integer type");
1860
1861 // The generic body computes in i32 and saturates to the output type.
1862 auto outputElemTy = dyn_cast<IntegerType>(outputTy.getElementType());
1863 if (!outputElemTy || outputElemTy.getWidth() > 32)
1864 return rewriter.notifyMatchFailure(
1865 op, "only support integer output types up to 32 bits");
1866
1867 // Zero points are already validated by tosa-validate under
1868 // strict-op-spec-alignment, but reject invalid values here as well so
1869 // that conversion without validation fails instead of silently
1870 // generating incorrect arithmetic.
1871 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
1872 if (succeeded(maybeIZp) && op.verifyInputZeroPoint(*maybeIZp).failed())
1873 return rewriter.notifyMatchFailure(op, "unsupported input zero point");
1874 FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
1875 if (succeeded(maybeOZp) && op.verifyOutputZeroPoint(*maybeOZp).failed())
1876 return rewriter.notifyMatchFailure(op, "unsupported output zero point");
1877
1878 SmallVector<Value> dynDims;
1879 for (int i = 0; i < outputTy.getRank(); i++) {
1880 if (outputTy.isDynamicDim(i)) {
1881 dynDims.push_back(tensor::DimOp::create(rewriter, loc, input, i));
1882 }
1883 }
1884
1885 DenseElementsAttr shiftElems;
1886 bool isShiftConstant = false;
1887 if (matchPattern(op.getShift(), m_Constant(&shiftElems)))
1888 isShiftConstant = true;
1889
1890 DenseElementsAttr multiplierElems;
1891 bool isMultiplierConstant = false;
1892 if (matchPattern(op.getMultiplier(), m_Constant(&multiplierElems)))
1893 isMultiplierConstant = true;
1894
1895 llvm::SmallVector<int32_t> shiftValues;
1896 llvm::SmallVector<int32_t> multiplierValues;
1897 bool doubleRound;
1898
1899 if (isMultiplierConstant && isShiftConstant) {
1900 // explicit cast is required here
1901 shiftValues = llvm::map_to_vector(
1902 shiftElems.getValues<IntegerAttr>(), [](IntegerAttr attr) -> int32_t {
1903 return static_cast<int32_t>(attr.getInt());
1904 });
1905 multiplierValues =
1906 llvm::map_to_vector(multiplierElems.getValues<IntegerAttr>(),
1907 [](IntegerAttr attr) -> int32_t {
1908 return static_cast<int32_t>(attr.getInt());
1909 });
1910
1911 // If we shift by more than the bitwidth, this just sets to 0.
1912 for (int i = 0, s = multiplierValues.size(); i < s; i++) {
1913 if (shiftValues[i] > 63) {
1914 shiftValues[i] = 0;
1915 multiplierValues[i] = 0;
1916 }
1917 }
1918 // Double round only occurs if shift is greater than 31, check that this
1919 // is ever true.
1920 doubleRound = op.getRoundingMode() == RoundingMode::DOUBLE_ROUND &&
1921 llvm::any_of(shiftValues, [](int32_t v) { return v > 31; });
1922 } else
1923 doubleRound = op.getRoundingMode() == RoundingMode::DOUBLE_ROUND;
1924
1925 RoundingMode roundingMode =
1926 doubleRound ? RoundingMode::DOUBLE_ROUND : RoundingMode::SINGLE_ROUND;
1927
1928 SmallVector<AffineMap> indexingMaps = {
1929 rewriter.getMultiDimIdentityMap(rank)};
1930 SmallVector<Value, 4> genericInputs = {input};
1931
1932 // If we are rescaling per-channel then we need to store the multiplier
1933 // values in a buffer.
1934 Value multiplierConstant;
1935 int64_t multiplierArg = 0;
1936 setupLinalgGenericOpInputAndIndexingMap(
1937 rewriter, multiplierValues, genericInputs, indexingMaps,
1938 isMultiplierConstant, op, multiplierConstant, multiplierArg);
1939
1940 // If we are rescaling per-channel then we need to store the shift
1941 // values in a buffer.
1942 Value shiftConstant;
1943 int64_t shiftArg = 0;
1944 setupLinalgGenericOpInputAndIndexingMap(
1945 rewriter, shiftValues, genericInputs, indexingMaps, isShiftConstant, op,
1946 shiftConstant, shiftArg, true);
1947
1948 // broadcastMap = affine_map<(d0, d1) -> ()>
1949 // It would affect as broadcast for scalar values in linalg::GenericOp.
1950 AffineMap broadcastMap = AffineMap::get(rank, 0, {}, rewriter.getContext());
1951 // The inputZp and outputZp may be either constant or non-constant,
1952 // depending on whether dynamic extension is enabled.
1953 // - If the zp's are non-constant, add them as an inputs to
1954 // linalg::GenericOp by:
1955 // 1. Pushing it into 'genericInputs'.
1956 // 2. Appending a corresponding affine map to 'indexingMaps'.
1957 // - If the zp's are constant, they would be generated as arith.constant.
1958 int64_t iZpArg = 0;
1959 if (failed(maybeIZp)) {
1960 genericInputs.push_back(
1961 collapse1xNTensorToN(rewriter, op->getOperand(3), loc));
1962 indexingMaps.push_back(broadcastMap);
1963 iZpArg = indexingMaps.size() - 1;
1964 }
1965 int64_t oZpArg = 0;
1966 if (failed(maybeOZp)) {
1967 genericInputs.push_back(
1968 collapse1xNTensorToN(rewriter, op->getOperand(4), loc));
1969 indexingMaps.push_back(broadcastMap);
1970 oZpArg = indexingMaps.size() - 1;
1971 }
1972
1973 // Indexing maps for output values.
1974 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(rank));
1975
1976 // Construct the indexing maps needed for linalg.generic ops.
1977 Value emptyTensor = tensor::EmptyOp::create(
1978 rewriter, loc, outputTy.getShape(), outputTy.getElementType(),
1979 ArrayRef<Value>({dynDims}));
1980
1981 auto linalgOp = linalg::GenericOp::create(
1982 rewriter, loc, outputTy, genericInputs, ValueRange{emptyTensor},
1983 indexingMaps, getNParallelLoopsAttrs(rank),
1984 [&](OpBuilder &nestedBuilder, Location nestedLoc,
1985 ValueRange blockArgs) {
1986 Value value = blockArgs[0];
1987 Type valueTy = value.getType();
1988
1989 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
1990 auto inputZp = getExtendZp(nestedBuilder, valueTy, maybeIZp,
1991 nestedLoc, blockArgs, iZpArg);
1992
1993 FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
1994 auto outputZp = getExtendZp(nestedBuilder, valueTy, maybeOZp,
1995 nestedLoc, blockArgs, oZpArg, true);
1996
1997 IntegerType outIntType =
1998 cast<IntegerType>(blockArgs.back().getType());
1999 unsigned outBitWidth = outIntType.getWidth();
2000 assert(outBitWidth <= 32 && "Unexpected output zeropoint bitwidth");
2001
2002 Value multiplier = multiplierConstant ? multiplierConstant
2003 : blockArgs[multiplierArg];
2004 Value shift = shiftConstant ? shiftConstant : blockArgs[shiftArg];
2005
2006 if (valueTy.isUnsignedInteger()) {
2007 value = UnrealizedConversionCastOp::create(
2008 nestedBuilder, nestedLoc,
2009 nestedBuilder.getIntegerType(
2010 valueTy.getIntOrFloatBitWidth()),
2011 value)
2012 .getResult(0);
2013 }
2014 if (valueTy.getIntOrFloatBitWidth() < 32) {
2015 if (op.getInputUnsigned()) {
2016 value = arith::ExtUIOp::create(nestedBuilder, nestedLoc,
2017 nestedBuilder.getI32Type(), value);
2018 } else {
2019 value = arith::ExtSIOp::create(nestedBuilder, nestedLoc,
2020 nestedBuilder.getI32Type(), value);
2021 }
2022 }
2023
2024 value =
2025 arith::SubIOp::create(nestedBuilder, nestedLoc, value, inputZp);
2026
2027 value = tosa::ApplyScaleOp::create(nestedBuilder, loc,
2028 nestedBuilder.getI32Type(), value,
2029 multiplier, shift, roundingMode);
2030
2031 // Move to the new zero-point.
2032 value =
2033 arith::AddIOp::create(nestedBuilder, nestedLoc, value, outputZp);
2034
2035 // Saturate to the output size.
2036 int32_t intMin = APInt::getSignedMinValue(outBitWidth).getSExtValue();
2037 int32_t intMax = APInt::getSignedMaxValue(outBitWidth).getSExtValue();
2038
2039 // Unsigned integers have a difference output value.
2040 if (op.getOutputUnsigned()) {
2041 intMin = 0;
2042 intMax = APInt::getMaxValue(outBitWidth).getZExtValue();
2043 }
2044
2045 auto intMinVal = arith::ConstantOp::create(
2046 nestedBuilder, loc, nestedBuilder.getI32IntegerAttr(intMin));
2047 auto intMaxVal = arith::ConstantOp::create(
2048 nestedBuilder, loc, nestedBuilder.getI32IntegerAttr(intMax));
2049
2050 value = clampIntHelper(nestedLoc, value, intMinVal, intMaxVal,
2051 nestedBuilder, /*isUnsigned=*/false);
2052
2053 if (outIntType.getWidth() < 32) {
2054 value = arith::TruncIOp::create(
2055 nestedBuilder, nestedLoc,
2056 rewriter.getIntegerType(outIntType.getWidth()), value);
2057 }
2058
2059 if (outIntType.isUnsignedInteger()) {
2060 value = UnrealizedConversionCastOp::create(nestedBuilder, nestedLoc,
2061 outIntType, value)
2062 .getResult(0);
2063 }
2064 linalg::YieldOp::create(nestedBuilder, loc, value);
2065 });
2066
2067 rewriter.replaceOp(op, linalgOp->getResults());
2068 return success();
2069 }
2070};
2071
2072// Handle the resize case where the input is a 1x1 image. This case
2073// can entirely avoiding having extract operations which target much
2074// more difficult to optimize away.
2075class ResizeUnaryConverter : public OpRewritePattern<tosa::ResizeOp> {
2076public:
2077 using OpRewritePattern<tosa::ResizeOp>::OpRewritePattern;
2078
2079 LogicalResult matchAndRewrite(tosa::ResizeOp op,
2080 PatternRewriter &rewriter) const final {
2081 Location loc = op.getLoc();
2082 ImplicitLocOpBuilder builder(loc, rewriter);
2083 auto input = op.getInput();
2084 auto inputTy = cast<RankedTensorType>(input.getType());
2085 auto resultTy = cast<RankedTensorType>(op.getType());
2086 const bool isBilinear = op.getMode() == ResizeMode::BILINEAR;
2087
2088 auto inputH = inputTy.getDimSize(1);
2089 auto inputW = inputTy.getDimSize(2);
2090 auto outputH = resultTy.getDimSize(1);
2091 auto outputW = resultTy.getDimSize(2);
2092
2093 if (inputH != 1 || inputW != 1 || outputH != 1 || outputW != 1)
2094 return rewriter.notifyMatchFailure(
2095 op, "tosa.resize is not a pure 1x1->1x1 image operation");
2096
2097 if (op.getMode() != ResizeMode::NEAREST_NEIGHBOR &&
2098 op.getMode() != ResizeMode::BILINEAR)
2099 return rewriter.notifyMatchFailure(
2100 op, "tosa.resize mode should be NEAREST_NEIGHBOR or BILINEAR");
2101
2102 if (inputTy == resultTy) {
2103 rewriter.replaceOp(op, input);
2104 return success();
2105 }
2106
2107 SmallVector<int64_t> scale;
2108 if (!tosa::getConstShapeValues(op.getScale().getDefiningOp(), scale)) {
2109 return failure();
2110 }
2111
2112 // Collapse the unit width and height away.
2113 SmallVector<ReassociationExprs, 4> reassociationMap(2);
2114 reassociationMap[0].push_back(builder.getAffineDimExpr(0));
2115 reassociationMap[1].push_back(builder.getAffineDimExpr(1));
2116 reassociationMap[1].push_back(builder.getAffineDimExpr(2));
2117 reassociationMap[1].push_back(builder.getAffineDimExpr(3));
2118
2119 auto collapseTy =
2120 RankedTensorType::get({inputTy.getDimSize(0), inputTy.getDimSize(3)},
2121 inputTy.getElementType());
2122 Value collapse = tensor::CollapseShapeOp::create(builder, collapseTy, input,
2123 reassociationMap);
2124
2125 // Get any dynamic shapes that appear in the input format.
2126 llvm::SmallVector<Value> outputDynSize;
2127 if (inputTy.isDynamicDim(0))
2128 outputDynSize.push_back(tensor::DimOp::create(builder, input, 0));
2129 if (inputTy.isDynamicDim(3))
2130 outputDynSize.push_back(tensor::DimOp::create(builder, input, 3));
2131
2132 // Generate the elementwise operation for casting scaling the input value.
2133 auto genericTy = collapseTy.clone(resultTy.getElementType());
2134 Value empty =
2135 tensor::EmptyOp::create(builder, genericTy.getShape(),
2136 resultTy.getElementType(), outputDynSize);
2137 auto genericMap = rewriter.getMultiDimIdentityMap(genericTy.getRank());
2138 SmallVector<utils::IteratorType> iterators(genericTy.getRank(),
2139 utils::IteratorType::parallel);
2140
2141 auto generic = linalg::GenericOp::create(
2142 builder, genericTy, ValueRange{collapse}, ValueRange{empty},
2143 ArrayRef<AffineMap>{genericMap, genericMap}, iterators,
2144 [=](OpBuilder &b, Location loc, ValueRange args) {
2145 Value value = args[0];
2146 // This is the quantized case.
2147 if (inputTy.getElementType() != resultTy.getElementType()) {
2148 value = arith::ExtSIOp::create(b, loc, resultTy.getElementType(),
2149 value);
2150
2151 if (isBilinear && scale[0] != 0) {
2152 Value scaleY = arith::ConstantOp::create(
2153 b, loc, b.getI32IntegerAttr(scale[0]));
2154 value = arith::MulIOp::create(b, loc, value, scaleY);
2155 }
2156
2157 if (isBilinear && scale[2] != 0) {
2158 Value scaleX = arith::ConstantOp::create(
2159 b, loc, b.getI32IntegerAttr(scale[2]));
2160 value = arith::MulIOp::create(b, loc, value, scaleX);
2161 }
2162 }
2163
2164 linalg::YieldOp::create(b, loc, value);
2165 });
2166
2167 rewriter.replaceOpWithNewOp<tensor::ExpandShapeOp>(
2168 op, resultTy, generic.getResults()[0], reassociationMap);
2169 return success();
2170 }
2171};
2172
2173// TOSA resize with width or height of 1 may be broadcasted to a wider
2174// dimension. This is done by materializing a new tosa.resize without
2175// the broadcasting behavior, and an explicit broadcast afterwards.
2176class MaterializeResizeBroadcast : public OpRewritePattern<tosa::ResizeOp> {
2177public:
2178 using OpRewritePattern<tosa::ResizeOp>::OpRewritePattern;
2179
2180 LogicalResult matchAndRewrite(tosa::ResizeOp op,
2181 PatternRewriter &rewriter) const final {
2182 Location loc = op.getLoc();
2183 ImplicitLocOpBuilder builder(loc, rewriter);
2184 auto input = op.getInput();
2185 auto inputTy = dyn_cast<RankedTensorType>(input.getType());
2186 auto resultTy = dyn_cast<RankedTensorType>(op.getType());
2187
2188 if (!inputTy || !resultTy)
2189 return rewriter.notifyMatchFailure(op,
2190 "requires ranked input/output types");
2191
2192 auto batch = inputTy.getDimSize(0);
2193 auto channels = inputTy.getDimSize(3);
2194 auto inputH = inputTy.getDimSize(1);
2195 auto inputW = inputTy.getDimSize(2);
2196 auto outputH = resultTy.getDimSize(1);
2197 auto outputW = resultTy.getDimSize(2);
2198
2199 if ((inputH != 1 || outputH == 1) && (inputW != 1 || outputW == 1))
2200 return rewriter.notifyMatchFailure(
2201 op, "tosa.resize has no broadcasting behavior");
2202
2203 // For any dimension that is broadcastable we generate a width of 1
2204 // on the output.
2205 llvm::SmallVector<int64_t> resizeShape;
2206 resizeShape.push_back(batch);
2207 resizeShape.push_back(inputH == 1 ? 1 : outputH);
2208 resizeShape.push_back(inputW == 1 ? 1 : outputW);
2209 resizeShape.push_back(channels);
2210
2211 auto resizeTy = resultTy.clone(resizeShape);
2212 auto resize =
2213 tosa::ResizeOp::create(builder, resizeTy, input, op.getScale(),
2214 op.getOffset(), op.getBorder(), op.getMode());
2215
2216 // Collapse an unit result dims.
2217 SmallVector<ReassociationExprs, 4> reassociationMap(2);
2218 reassociationMap[0].push_back(builder.getAffineDimExpr(0));
2219 reassociationMap.back().push_back(builder.getAffineDimExpr(1));
2220 if (inputH != 1)
2221 reassociationMap.push_back({});
2222 reassociationMap.back().push_back(builder.getAffineDimExpr(2));
2223 if (inputW != 1)
2224 reassociationMap.push_back({});
2225 reassociationMap.back().push_back(builder.getAffineDimExpr(3));
2226
2227 llvm::SmallVector<int64_t> collapseShape = {batch};
2228 if (inputH != 1)
2229 collapseShape.push_back(outputH);
2230 if (inputW != 1)
2231 collapseShape.push_back(outputW);
2232 collapseShape.push_back(channels);
2233
2234 auto collapseTy = resultTy.clone(collapseShape);
2235 Value collapse = tensor::CollapseShapeOp::create(builder, collapseTy,
2236 resize, reassociationMap);
2237
2238 // Broadcast the collapsed shape to the output result.
2239 llvm::SmallVector<Value> outputDynSize;
2240 if (inputTy.isDynamicDim(0))
2241 outputDynSize.push_back(tensor::DimOp::create(builder, input, 0));
2242 if (inputTy.isDynamicDim(3))
2243 outputDynSize.push_back(tensor::DimOp::create(builder, input, 3));
2244
2245 SmallVector<utils::IteratorType> iterators(resultTy.getRank(),
2246 utils::IteratorType::parallel);
2247 Value empty = tensor::EmptyOp::create(
2248 builder, resultTy.getShape(), resultTy.getElementType(), outputDynSize);
2249
2250 SmallVector<AffineExpr, 4> inputExprs{rewriter.getAffineDimExpr(0)};
2251 if (inputH != 1)
2252 inputExprs.push_back(rewriter.getAffineDimExpr(1));
2253 if (inputW != 1)
2254 inputExprs.push_back(rewriter.getAffineDimExpr(2));
2255 inputExprs.push_back(rewriter.getAffineDimExpr(3));
2256
2257 auto inputMap = AffineMap::get(resultTy.getRank(), /*symbolCount=*/0,
2258 inputExprs, rewriter.getContext());
2259
2260 auto outputMap = rewriter.getMultiDimIdentityMap(resultTy.getRank());
2261 rewriter.replaceOpWithNewOp<linalg::GenericOp>(
2262 op, resultTy, ValueRange{collapse}, ValueRange{empty},
2263 ArrayRef<AffineMap>{inputMap, outputMap}, iterators,
2264 [=](OpBuilder &b, Location loc, ValueRange args) {
2265 Value value = args[0];
2266 linalg::YieldOp::create(b, loc, value);
2267 });
2268
2269 return success();
2270 }
2271};
2272
2273class GenericResizeConverter : public OpRewritePattern<tosa::ResizeOp> {
2274public:
2275 using OpRewritePattern<tosa::ResizeOp>::OpRewritePattern;
2276
2277 LogicalResult matchAndRewrite(tosa::ResizeOp op,
2278 PatternRewriter &rewriter) const final {
2279 Location loc = op.getLoc();
2280 ImplicitLocOpBuilder b(loc, rewriter);
2281 auto input = op.getInput();
2282 auto inputTy = cast<ShapedType>(input.getType());
2283 auto resultTy = cast<ShapedType>(op.getType());
2284 auto resultETy = resultTy.getElementType();
2285
2286 bool floatingPointMode = isa<FloatType>(resultETy);
2287 auto floatTy = resultETy;
2288
2289 auto imageH = inputTy.getShape()[1];
2290 auto imageW = inputTy.getShape()[2];
2291
2292 auto dynamicDimsOr =
2293 checkHasDynamicBatchDims(rewriter, op, {input, op.getOutput()});
2294 if (!dynamicDimsOr.has_value())
2295 return rewriter.notifyMatchFailure(
2296 op, "unable to get dynamic dimensions of tosa.resize");
2297
2298 if (op.getMode() != ResizeMode::NEAREST_NEIGHBOR &&
2299 op.getMode() != ResizeMode::BILINEAR)
2300 return rewriter.notifyMatchFailure(
2301 op, "tosa.resize mode should be NEAREST_NEIGHBOR or BILINEAR");
2302
2303 SmallVector<AffineMap, 2> affineMaps = {
2304 rewriter.getMultiDimIdentityMap(resultTy.getRank())};
2305 auto emptyTensor = tensor::EmptyOp::create(b, resultTy.getShape(),
2306 resultETy, *dynamicDimsOr);
2307 auto genericOp = linalg::GenericOp::create(
2308 b, resultTy, ValueRange({}), ValueRange{emptyTensor}, affineMaps,
2309 getNParallelLoopsAttrs(resultTy.getRank()));
2310 Value resize = genericOp.getResult(0);
2311
2312 {
2313 OpBuilder::InsertionGuard regionGuard(b);
2314 b.createBlock(&genericOp.getRegion(), genericOp.getRegion().end(),
2315 TypeRange({resultETy}), loc);
2316 Value batch = linalg::IndexOp::create(b, 0);
2317 Value y = linalg::IndexOp::create(b, 1);
2318 Value x = linalg::IndexOp::create(b, 2);
2319 Value channel = linalg::IndexOp::create(b, 3);
2320
2321 Value zeroI32 =
2322 arith::ConstantOp::create(b, b.getZeroAttr(b.getI32Type()));
2323 Value zeroFp = arith::ConstantOp::create(b, b.getZeroAttr(floatTy));
2324 Value hMax =
2325 arith::ConstantOp::create(b, b.getI32IntegerAttr(imageH - 1));
2326 Value wMax =
2327 arith::ConstantOp::create(b, b.getI32IntegerAttr(imageW - 1));
2328
2329 Value inY = arith::IndexCastOp::create(b, b.getI32Type(), y);
2330 Value inX = arith::IndexCastOp::create(b, b.getI32Type(), x);
2331
2332 SmallVector<int64_t> scale, offset, border;
2333 if (!tosa::getConstShapeValues(op.getScale().getDefiningOp(), scale) ||
2334 !tosa::getConstShapeValues(op.getOffset().getDefiningOp(), offset) ||
2335 !tosa::getConstShapeValues(op.getBorder().getDefiningOp(), border)) {
2336 return rewriter.notifyMatchFailure(
2337 op, "tosa.resize scale/offset/border should have compile time "
2338 "constant values.");
2339 }
2340
2341 Value yScaleN, yScaleD, xScaleN, xScaleD;
2342 yScaleN = arith::ConstantOp::create(b, b.getI32IntegerAttr(scale[0]));
2343 yScaleD = arith::ConstantOp::create(b, b.getI32IntegerAttr(scale[1]));
2344 xScaleN = arith::ConstantOp::create(b, b.getI32IntegerAttr(scale[2]));
2345 xScaleD = arith::ConstantOp::create(b, b.getI32IntegerAttr(scale[3]));
2346
2347 Value yOffset, xOffset, yBorder, xBorder;
2348 yOffset = arith::ConstantOp::create(b, b.getI32IntegerAttr(offset[0]));
2349 xOffset = arith::ConstantOp::create(b, b.getI32IntegerAttr(offset[1]));
2350 yBorder = arith::ConstantOp::create(b, b.getI32IntegerAttr(border[0]));
2351 xBorder = arith::ConstantOp::create(b, b.getI32IntegerAttr(border[1]));
2352
2353 // Compute the ix and dx values for both the X and Y dimensions.
2354 auto getIndexAndDeltaFp = [&](Value &index, Value &delta, Value in,
2355 Value scaleN, Value scaleD, Value offset,
2356 int size, ImplicitLocOpBuilder &b) {
2357 if (size == 1) {
2358 index = zeroI32;
2359 delta = zeroFp;
2360 return;
2361 }
2362 // x = x * scale_d + offset;
2363 // ix = floor(x / scale_n)
2364 Value val = arith::MulIOp::create(b, in, scaleD);
2365 val = arith::AddIOp::create(b, val, offset);
2366 index = arith::FloorDivSIOp::create(b, val, scaleN);
2367
2368 // rx = x - ix * scale_n (x % scale_n, if values are positive)
2369 Value scaledIndex = arith::MulIOp::create(b, index, scaleN);
2370 Value r = arith::SubIOp::create(b, val, scaledIndex);
2371 Value rFp = arith::SIToFPOp::create(b, floatTy, r);
2372
2373 // dx = rx / scale_n
2374 Value scaleNfp = arith::UIToFPOp::create(b, floatTy, scaleN);
2375 delta = arith::DivFOp::create(b, rFp, scaleNfp);
2376 };
2377
2378 // Compute the ix and dx values for the X and Y dimensions - int case.
2379 auto getIndexAndDeltaInt = [&](Value &index, Value &delta, Value in,
2380 Value scaleN, Value scaleD, Value offset,
2381 int size, ImplicitLocOpBuilder &b) {
2382 if (size == 1) {
2383 index = zeroI32;
2384 delta = zeroI32;
2385 return;
2386 }
2387 // x = x * scale_d + offset;
2388 // ix = floor(x / scale_n)
2389 // dx = x - ix * scale_n;
2390 Value val = arith::MulIOp::create(b, in, scaleD);
2391 val = arith::AddIOp::create(b, val, offset);
2392 index = arith::FloorDivSIOp::create(b, val, scaleN);
2393 delta = arith::MulIOp::create(b, index, scaleN);
2394 delta = arith::SubIOp::create(b, val, delta);
2395 };
2396
2397 Value ix, iy, dx, dy;
2398 if (floatingPointMode) {
2399 getIndexAndDeltaFp(iy, dy, inY, yScaleN, yScaleD, yOffset, imageH, b);
2400 getIndexAndDeltaFp(ix, dx, inX, xScaleN, xScaleD, xOffset, imageW, b);
2401 } else {
2402 getIndexAndDeltaInt(iy, dy, inY, yScaleN, yScaleD, yOffset, imageH, b);
2403 getIndexAndDeltaInt(ix, dx, inX, xScaleN, xScaleD, xOffset, imageW, b);
2404 }
2405
2406 if (op.getMode() == ResizeMode::NEAREST_NEIGHBOR) {
2407 auto one = arith::ConstantOp::create(b, b.getI32IntegerAttr(1));
2408
2409 auto getNearestIndexAndClamp = [&](Value val, Value dval, Value scale,
2410 Value max, int size,
2411 ImplicitLocOpBuilder &b) -> Value {
2412 if (size == 1) {
2414 }
2415
2416 Value pred;
2417 if (floatingPointMode) {
2418 auto h =
2419 arith::ConstantOp::create(b, b.getFloatAttr(floatTy, 0.5f));
2420 pred = arith::CmpFOp::create(b, arith::CmpFPredicate::OGE, dval, h);
2421 } else {
2422 Value dvalDouble = arith::ShLIOp::create(b, dval, one);
2423 pred = arith::CmpIOp::create(b, arith::CmpIPredicate::sge,
2424 dvalDouble, scale);
2425 }
2426
2427 auto offset = arith::SelectOp::create(b, pred, one, zeroI32);
2428 val = arith::AddIOp::create(b, val, offset);
2429 val = clampIntHelper(loc, val, zeroI32, max, b, /*isUnsigned=*/false);
2430 return arith::IndexCastOp::create(b, b.getIndexType(), val);
2431 };
2432
2433 iy = getNearestIndexAndClamp(iy, dy, yScaleN, hMax, imageH, b);
2434 ix = getNearestIndexAndClamp(ix, dx, xScaleN, wMax, imageW, b);
2435
2436 Value result = tensor::ExtractOp::create(
2437 b, input, ValueRange{batch, iy, ix, channel});
2438
2439 linalg::YieldOp::create(b, result);
2440 } else {
2441 // The mode here must be BILINEAR.
2442 assert(op.getMode() == ResizeMode::BILINEAR);
2443
2444 auto oneVal = arith::ConstantOp::create(b, b.getI32IntegerAttr(1));
2445
2446 auto getClampedIdxs = [&](Value &val0, Value &val1, int size, Value in,
2447 Value max, ImplicitLocOpBuilder &b) {
2448 val0 = in;
2449 val1 = arith::AddIOp::create(b, val0, oneVal);
2450 val0 =
2451 clampIntHelper(loc, val0, zeroI32, max, b, /*isUnsigned=*/false);
2452 val1 =
2453 clampIntHelper(loc, val1, zeroI32, max, b, /*isUnsigned=*/false);
2454 val0 = arith::IndexCastOp::create(b, b.getIndexType(), val0);
2455 val1 = arith::IndexCastOp::create(b, b.getIndexType(), val1);
2456 };
2457
2458 // Linalg equivalent to the section below:
2459 // int16_t iy0 = apply_max(iy, 0);
2460 // int16_t iy1 = apply_min(iy + 1, IH - 1);
2461 // int16_t ix0 = apply_max(ix, 0);
2462 // int16_t ix1 = apply_min(ix + 1, IW - 1);
2463 Value x0, x1, y0, y1;
2464 getClampedIdxs(y0, y1, imageH, iy, hMax, b);
2465 getClampedIdxs(x0, x1, imageW, ix, wMax, b);
2466
2467 Value y0x0 = tensor::ExtractOp::create(
2468 b, input, ValueRange{batch, y0, x0, channel});
2469 Value y0x1 = tensor::ExtractOp::create(
2470 b, input, ValueRange{batch, y0, x1, channel});
2471 Value y1x0 = tensor::ExtractOp::create(
2472 b, input, ValueRange{batch, y1, x0, channel});
2473 Value y1x1 = tensor::ExtractOp::create(
2474 b, input, ValueRange{batch, y1, x1, channel});
2475
2476 if (floatingPointMode) {
2477 auto oneVal =
2478 arith::ConstantOp::create(b, b.getFloatAttr(floatTy, 1.0f));
2479 auto interpolate = [&](Value val0, Value val1, Value delta,
2480 int inputSize,
2481 ImplicitLocOpBuilder &b) -> Value {
2482 if (inputSize == 1)
2483 return val0;
2484 Value oneMinusDelta = arith::SubFOp::create(b, oneVal, delta);
2485 Value mul0 = arith::MulFOp::create(b, val0, oneMinusDelta);
2486 Value mul1 = arith::MulFOp::create(b, val1, delta);
2487 return arith::AddFOp::create(b, mul0, mul1);
2488 };
2489
2490 // Linalg equivalent to the section below:
2491 // topAcc = v00 * (unit_x - dx);
2492 // topAcc += v01 * dx;
2493 Value topAcc = interpolate(y0x0, y0x1, dx, imageW, b);
2494
2495 // Linalg equivalent to the section below:
2496 // bottomAcc = v10 * (unit_x - dx);
2497 // bottomAcc += v11 * dx;
2498 Value bottomAcc = interpolate(y1x0, y1x1, dx, imageW, b);
2499
2500 // Linalg equivalent to the section below:
2501 // result = topAcc * (unit_y - dy) + bottomAcc * dy
2502 Value result = interpolate(topAcc, bottomAcc, dy, imageH, b);
2503 linalg::YieldOp::create(b, result);
2504 } else {
2505 // Perform in quantized space.
2506 y0x0 = arith::ExtSIOp::create(b, resultETy, y0x0);
2507 y0x1 = arith::ExtSIOp::create(b, resultETy, y0x1);
2508 y1x0 = arith::ExtSIOp::create(b, resultETy, y1x0);
2509 y1x1 = arith::ExtSIOp::create(b, resultETy, y1x1);
2510
2511 const int64_t deltaBitwidth = dx.getType().getIntOrFloatBitWidth();
2512 if (resultETy.getIntOrFloatBitWidth() > deltaBitwidth) {
2513 dx = arith::ExtSIOp::create(b, resultETy, dx);
2514 dy = arith::ExtSIOp::create(b, resultETy, dy);
2515 }
2516
2517 Value yScaleNExt = yScaleN;
2518 Value xScaleNExt = xScaleN;
2519
2520 const int64_t scaleBitwidth =
2521 xScaleN.getType().getIntOrFloatBitWidth();
2522 if (resultETy.getIntOrFloatBitWidth() > scaleBitwidth) {
2523 yScaleNExt = arith::ExtSIOp::create(b, resultETy, yScaleN);
2524 xScaleNExt = arith::ExtSIOp::create(b, resultETy, xScaleN);
2525 }
2526
2527 auto interpolate = [](Value val0, Value val1, Value weight1,
2528 Value scale, int inputSize,
2529 ImplicitLocOpBuilder &b) -> Value {
2530 if (inputSize == 1)
2531 return arith::MulIOp::create(b, val0, scale);
2532 Value weight0 = arith::SubIOp::create(b, scale, weight1);
2533 Value mul0 = arith::MulIOp::create(b, val0, weight0);
2534 Value mul1 = arith::MulIOp::create(b, val1, weight1);
2535 return arith::AddIOp::create(b, mul0, mul1);
2536 };
2537
2538 Value topAcc = interpolate(y0x0, y0x1, dx, xScaleNExt, imageW, b);
2539 Value bottomAcc = interpolate(y1x0, y1x1, dx, xScaleNExt, imageW, b);
2540 Value result =
2541 interpolate(topAcc, bottomAcc, dy, yScaleNExt, imageH, b);
2542 linalg::YieldOp::create(b, result);
2543 }
2544 }
2545 }
2546
2547 rewriter.replaceOp(op, resize);
2548 return success();
2549 }
2550};
2551
2552// At the codegen level any identity operations should be removed. Any cases
2553// where identity is load-bearing (e.g. cross device computation) should be
2554// handled before lowering to codegen.
2555template <typename SrcOp>
2556class IdentityNConverter : public OpRewritePattern<SrcOp> {
2557public:
2558 using OpRewritePattern<SrcOp>::OpRewritePattern;
2559
2560 LogicalResult matchAndRewrite(SrcOp op,
2561 PatternRewriter &rewriter) const final {
2562 rewriter.replaceOp(op, op.getOperation()->getOperands());
2563 return success();
2564 }
2565};
2566
2567template <typename SrcOp>
2568class ReduceConverter : public OpRewritePattern<SrcOp> {
2569public:
2570 ReduceConverter(MLIRContext *context, bool allowNonFinites)
2571 : OpRewritePattern<SrcOp>(context), allowNonFinites(allowNonFinites) {}
2572
2573 LogicalResult matchAndRewrite(SrcOp reduceOp,
2574 PatternRewriter &rewriter) const final {
2575 return reduceMatchAndRewriteHelper(reduceOp, reduceOp.getAxis(), rewriter,
2576 allowNonFinites);
2577 }
2578
2579private:
2580 bool allowNonFinites;
2581};
2582
2583class ReverseConverter : public OpRewritePattern<tosa::ReverseOp> {
2584public:
2585 using OpRewritePattern<tosa::ReverseOp>::OpRewritePattern;
2586
2587 LogicalResult matchAndRewrite(tosa::ReverseOp op,
2588 PatternRewriter &rewriter) const final {
2589 auto loc = op.getLoc();
2590 Value input = op.getInput1();
2591 auto inputTy = cast<ShapedType>(input.getType());
2592 auto resultTy = cast<ShapedType>(op.getType());
2593 auto axis = op.getAxis();
2594
2595 SmallVector<Value> dynDims;
2596 for (int i = 0; i < inputTy.getRank(); i++) {
2597 if (inputTy.isDynamicDim(i)) {
2598 dynDims.push_back(tensor::DimOp::create(rewriter, loc, input, i));
2599 }
2600 }
2601
2602 Value axisDimSize = tensor::DimOp::create(rewriter, loc, input, axis);
2603
2604 // First fill the output buffer with the init value.
2605 auto emptyTensor = tensor::EmptyOp::create(
2606 rewriter, loc, inputTy.getShape(),
2607 inputTy.getElementType(), ArrayRef<Value>({dynDims}))
2608 .getResult();
2609 SmallVector<AffineMap, 2> affineMaps = {
2610 rewriter.getMultiDimIdentityMap(resultTy.getRank())};
2611
2612 rewriter.replaceOpWithNewOp<linalg::GenericOp>(
2613 op, resultTy, ArrayRef<Value>({}), ValueRange{emptyTensor}, affineMaps,
2614 getNParallelLoopsAttrs(resultTy.getRank()),
2615 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange args) {
2616 llvm::SmallVector<Value> indices;
2617 for (unsigned int i = 0; i < inputTy.getRank(); i++) {
2618 Value index =
2619 linalg::IndexOp::create(rewriter, nestedLoc, i).getResult();
2620 if (i == axis) {
2621 auto one = arith::ConstantIndexOp::create(rewriter, nestedLoc, 1);
2622 auto sizeMinusOne =
2623 arith::SubIOp::create(rewriter, nestedLoc, axisDimSize, one);
2624 index = arith::SubIOp::create(rewriter, nestedLoc, sizeMinusOne,
2625 index);
2626 }
2627
2628 indices.push_back(index);
2629 }
2630
2631 auto extract = tensor::ExtractOp::create(nestedBuilder, nestedLoc,
2632 input, indices);
2633 linalg::YieldOp::create(nestedBuilder, op.getLoc(),
2634 extract.getResult());
2635 });
2636 return success();
2637 }
2638};
2639
2640// This converter translate a tile operation to a reshape, broadcast, reshape.
2641// The first reshape minimally expands each tiled dimension to include a
2642// proceding size-1 dim. This dim is then broadcasted to the appropriate
2643// multiple.
2644struct TileConverter : public OpConversionPattern<tosa::TileOp> {
2645 using OpConversionPattern<tosa::TileOp>::OpConversionPattern;
2646
2647 LogicalResult
2648 matchAndRewrite(tosa::TileOp op, OpAdaptor adaptor,
2649 ConversionPatternRewriter &rewriter) const override {
2650 auto loc = op.getLoc();
2651 auto input = op.getInput1();
2652 auto inputTy = cast<ShapedType>(input.getType());
2653 auto inputShape = inputTy.getShape();
2654 auto resultTy = cast<ShapedType>(op.getType());
2655 auto elementTy = inputTy.getElementType();
2656 int64_t rank = inputTy.getRank();
2657
2658 SmallVector<int64_t> multiples;
2659 if (failed(op.getConstantMultiples(multiples)))
2660 return failure();
2661
2662 // Broadcast the newly added dimensions to their appropriate multiple.
2663 SmallVector<int64_t, 2> genericShape;
2664 for (int i = 0; i < rank; i++) {
2665 int64_t dim = multiples[i];
2666 genericShape.push_back(dim == -1 ? ShapedType::kDynamic : dim);
2667 genericShape.push_back(inputShape[i]);
2668 }
2669
2670 SmallVector<Value> dynDims;
2671 for (int i = 0; i < inputTy.getRank(); i++) {
2672 if (inputTy.isDynamicDim(i) || multiples[i] == -1) {
2673 dynDims.push_back(tensor::DimOp::create(rewriter, loc, input, i));
2674 }
2675 }
2676
2677 auto emptyTensor = tensor::EmptyOp::create(
2678 rewriter, op.getLoc(), genericShape, elementTy, dynDims);
2679
2680 // We needs to map the input shape to the non-broadcasted dimensions.
2681 SmallVector<AffineExpr, 4> dimExprs;
2682 dimExprs.reserve(rank);
2683 for (unsigned i = 0; i < rank; ++i)
2684 dimExprs.push_back(rewriter.getAffineDimExpr(i * 2 + 1));
2685
2686 auto readAffineMap =
2687 AffineMap::get(/*dimCount=*/rank * 2, /*symbolCount=*/0, dimExprs,
2688 rewriter.getContext());
2689
2690 SmallVector<AffineMap, 2> affineMaps = {
2691 readAffineMap, rewriter.getMultiDimIdentityMap(genericShape.size())};
2692
2693 auto genericOp = linalg::GenericOp::create(
2694 rewriter, loc, RankedTensorType::get(genericShape, elementTy), input,
2695 ValueRange{emptyTensor}, affineMaps,
2696 getNParallelLoopsAttrs(genericShape.size()),
2697 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange args) {
2698 linalg::YieldOp::create(nestedBuilder, op.getLoc(), *args.begin());
2699 });
2700
2701 auto shapeValue = getTosaConstShape(
2702 rewriter, loc, mlir::tosa::convertFromMlirShape(resultTy.getShape()));
2703 rewriter.replaceOpWithNewOp<tosa::ReshapeOp>(
2704 op, resultTy, genericOp.getResult(0), shapeValue);
2705 return success();
2706 }
2707};
2708
2709// Tosa argmin/argmax lowering represents the op as a linalg.generic
2710// op, producing two output buffers.
2711//
2712// The first output buffer contains the index of the found min/max value. It is
2713// initialized to 0 and is resulting integer type.
2714//
2715// The second output buffer contains the min/max value found. It is initialized
2716// to the max/min representable value of the input element type. After being
2717// populated by generic, this buffer is discarded as only the index is
2718// requested.
2719//
2720// The generic op updates both the min/max value and index if the
2721// current value is lower than/exceeds the running min/max.
2722template <typename SrcOp>
2723class ArgMinMaxConverter : public OpRewritePattern<SrcOp> {
2724public:
2725 ArgMinMaxConverter(MLIRContext *context, bool allowNonFinites)
2726 : OpRewritePattern<SrcOp>(context), allowNonFinites(allowNonFinites) {}
2727
2728 LogicalResult matchAndRewrite(SrcOp argOp,
2729 PatternRewriter &rewriter) const final {
2730 constexpr bool isMin = std::is_same_v<SrcOp, tosa::ArgMinOp>;
2731
2732 auto loc = argOp.getLoc();
2733 Value input = argOp.getInput();
2734 auto inputTy = cast<ShapedType>(input.getType());
2735 auto resultTy = cast<ShapedType>(argOp.getOutput().getType());
2736 auto inElementTy = inputTy.getElementType();
2737 auto outElementTy = resultTy.getElementType();
2738 int axis = argOp.getAxis();
2739 auto resultValueTy =
2740 RankedTensorType::get(resultTy.getShape(), inElementTy);
2741
2742 if (!isa<IntegerType>(outElementTy))
2743 return rewriter.notifyMatchFailure(argOp,
2744 "requires integer-like result type");
2745
2746 SmallVector<Value> dynDims;
2747 for (int i = 0; i < inputTy.getRank(); i++) {
2748 if (inputTy.isDynamicDim(i) && i != axis) {
2749 dynDims.push_back(tensor::DimOp::create(rewriter, loc, input, i));
2750 }
2751 }
2752
2753 // First fill the output buffer for the index.
2754 auto emptyTensorIdx =
2755 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
2756 outElementTy, dynDims)
2757 .getResult();
2758 auto fillValueIdx = arith::ConstantOp::create(
2759 rewriter, loc, rewriter.getIntegerAttr(outElementTy, 0));
2760 auto filledTensorIdx =
2761 linalg::FillOp::create(rewriter, loc, ValueRange{fillValueIdx},
2762 ValueRange{emptyTensorIdx})
2763 .result();
2764
2765 // Second fill the output buffer for the running min/max.
2766 auto emptyTensorValue =
2767 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(), inElementTy,
2768 dynDims)
2769 .getResult();
2770 auto fillValueAttr = createInitialValueForReduceOp(
2771 argOp, inElementTy, rewriter, allowNonFinites);
2772
2773 if (!fillValueAttr)
2774 return rewriter.notifyMatchFailure(argOp, "unsupported element type");
2775
2776 auto fillValue = arith::ConstantOp::create(rewriter, loc, fillValueAttr);
2777 auto filledTensorValue =
2778 linalg::FillOp::create(rewriter, loc, ValueRange{fillValue},
2779 ValueRange{emptyTensorValue})
2780 .result();
2781
2782 // We need to reduce along the specified axis, with parallel operations
2783 // along the rest.
2784 SmallVector<utils::IteratorType, 4> iteratorTypes;
2785 iteratorTypes.resize(inputTy.getRank(), utils::IteratorType::parallel);
2786 iteratorTypes[axis] = utils::IteratorType::reduction;
2787
2788 SmallVector<AffineExpr, 2> srcExprs;
2789 SmallVector<AffineExpr, 2> dstExprs;
2790 for (int i = 0, rank = inputTy.getRank(); i != rank; ++i) {
2791 srcExprs.push_back(mlir::getAffineDimExpr(i, rewriter.getContext()));
2792 if (axis != i)
2793 dstExprs.push_back(mlir::getAffineDimExpr(i, rewriter.getContext()));
2794 }
2795
2796 bool didEncounterError = false;
2797 auto maps = AffineMap::inferFromExprList({srcExprs, dstExprs, dstExprs},
2798 rewriter.getContext());
2799 auto linalgOp = linalg::GenericOp::create(
2800 rewriter, loc, ArrayRef<Type>({resultTy, resultValueTy}), input,
2801 ValueRange({filledTensorIdx, filledTensorValue}), maps, iteratorTypes,
2802 [&](OpBuilder &nestedBuilder, Location nestedLoc,
2803 ValueRange blockArgs) {
2804 auto newValue = blockArgs[0];
2805 auto oldIndex = blockArgs[1];
2806 auto oldValue = blockArgs[2];
2807
2808 Value newIndex = arith::IndexCastOp::create(
2809 rewriter, nestedLoc, oldIndex.getType(),
2810 linalg::IndexOp::create(rewriter, loc, axis));
2811
2812 Value predicate;
2813 if (isa<FloatType>(inElementTy)) {
2814 if (argOp.getNanMode() == NanPropagationMode::IGNORE) {
2815 // Only update index & value for non NaN values. If all
2816 // values are NaNs, the initial index will be return which is 0.
2817 predicate = arith::CmpFOp::create(
2818 rewriter, nestedLoc,
2819 isMin ? arith::CmpFPredicate::OLT : arith::CmpFPredicate::OGT,
2820 newValue, oldValue);
2821 } else {
2822 // Update value if either of the following is true:
2823 // - new value is smaller/bigger
2824 // - cur value is not NaN and new value is NaN
2825 Value comparison = arith::CmpFOp::create(
2826 rewriter, nestedLoc,
2827 isMin ? arith::CmpFPredicate::ULT : arith::CmpFPredicate::UGT,
2828 newValue, oldValue);
2829 Value oldNonNaN = arith::CmpFOp::create(rewriter, nestedLoc,
2830 arith::CmpFPredicate::ORD,
2831 oldValue, oldValue);
2832 predicate = arith::AndIOp::create(rewriter, nestedLoc,
2833 rewriter.getI1Type(),
2834 comparison, oldNonNaN);
2835 }
2836 } else if (isa<IntegerType>(inElementTy)) {
2837 predicate = arith::CmpIOp::create(rewriter, nestedLoc,
2838 isMin ? arith::CmpIPredicate::slt
2839 : arith::CmpIPredicate::sgt,
2840 newValue, oldValue);
2841 } else {
2842 didEncounterError = true;
2843 return;
2844 }
2845
2846 auto resultValue = arith::SelectOp::create(
2847 rewriter, nestedLoc, predicate, newValue, oldValue);
2848 auto resultIndex = arith::SelectOp::create(
2849 rewriter, nestedLoc, predicate, newIndex, oldIndex);
2850 linalg::YieldOp::create(nestedBuilder, nestedLoc,
2851 ValueRange({resultIndex, resultValue}));
2852 });
2853
2854 if (didEncounterError)
2855 return rewriter.notifyMatchFailure(argOp, "unsupported element type");
2856
2857 rewriter.replaceOp(argOp, linalgOp.getResult(0));
2858 return success();
2859 }
2860
2861private:
2862 bool allowNonFinites;
2863};
2864
2865class GatherConverter : public OpConversionPattern<tosa::GatherOp> {
2866public:
2867 using OpConversionPattern<tosa::GatherOp>::OpConversionPattern;
2868 LogicalResult
2869 matchAndRewrite(tosa::GatherOp op, OpAdaptor adaptor,
2870 ConversionPatternRewriter &rewriter) const final {
2871 auto input = adaptor.getOperands()[0];
2872 auto indices = adaptor.getOperands()[1];
2873
2874 auto valuesTy = dyn_cast<RankedTensorType>(op.getValues().getType());
2875 auto resultTy = dyn_cast<RankedTensorType>(op.getType());
2876 if (!valuesTy || !resultTy)
2877 return rewriter.notifyMatchFailure(op, "unranked tensors not supported");
2878
2879 auto dynamicDims = inferDynamicDimsForGather(
2880 rewriter, op.getLoc(), adaptor.getValues(), adaptor.getIndices());
2881
2882 auto resultElementTy = resultTy.getElementType();
2883
2884 auto loc = op.getLoc();
2885 auto emptyTensor =
2886 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
2887 resultElementTy, dynamicDims)
2888 .getResult();
2889
2890 SmallVector<AffineMap, 2> affineMaps = {
2892 /*dimCount=*/resultTy.getRank(), /*symbolCount=*/0,
2893 {rewriter.getAffineDimExpr(0), rewriter.getAffineDimExpr(1)},
2894 rewriter.getContext()),
2895 rewriter.getMultiDimIdentityMap(resultTy.getRank())};
2896
2897 auto genericOp = linalg::GenericOp::create(
2898 rewriter, loc, ArrayRef<Type>({resultTy}), ValueRange{indices},
2899 ValueRange{emptyTensor}, affineMaps,
2900 getNParallelLoopsAttrs(resultTy.getRank()),
2901 [&](OpBuilder &b, Location loc, ValueRange args) {
2902 auto indexValue = args[0];
2903 auto index0 = linalg::IndexOp::create(rewriter, loc, 0);
2904 Value index1 = arith::IndexCastOp::create(
2905 rewriter, loc, rewriter.getIndexType(), indexValue);
2906 auto index2 = linalg::IndexOp::create(rewriter, loc, 2);
2907 Value extract = tensor::ExtractOp::create(
2908 rewriter, loc, input, ValueRange{index0, index1, index2});
2909 linalg::YieldOp::create(rewriter, loc, extract);
2910 });
2911 rewriter.replaceOp(op, genericOp.getResult(0));
2912 return success();
2913 }
2914
2915 static llvm::SmallVector<Value> inferDynamicDimsForGather(OpBuilder &builder,
2916 Location loc,
2917 Value values,
2918 Value indices) {
2919 llvm::SmallVector<Value> results;
2920
2921 auto addDynamicDimension = [&](Value source, int64_t dim) {
2922 auto sz = tensor::getMixedSize(builder, loc, source, dim);
2923 if (auto dimValue = llvm::dyn_cast_if_present<Value>(sz))
2924 results.push_back(dimValue);
2925 };
2926
2927 addDynamicDimension(values, 0);
2928 addDynamicDimension(indices, 1);
2929 addDynamicDimension(values, 2);
2930 return results;
2931 }
2932};
2933
2934class RowGatherConverter : public OpConversionPattern<tosa::RowGatherOp> {
2935public:
2936 using OpConversionPattern<tosa::RowGatherOp>::OpConversionPattern;
2937 LogicalResult
2938 matchAndRewrite(tosa::RowGatherOp op, OpAdaptor adaptor,
2939 ConversionPatternRewriter &rewriter) const final {
2940 auto valuesTy = dyn_cast<RankedTensorType>(adaptor.getValues().getType());
2941 auto indicesTy = dyn_cast<RankedTensorType>(adaptor.getIndices().getType());
2942 auto rowCountTy =
2943 dyn_cast<RankedTensorType>(adaptor.getRowCount().getType());
2944 auto resultTy = dyn_cast<RankedTensorType>(op.getType());
2945 if (!valuesTy || !indicesTy || !rowCountTy || !resultTy)
2946 return rewriter.notifyMatchFailure(op, "unranked tensors not supported");
2947
2948 Location loc = op.getLoc();
2949 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
2950 Value rowCount = tensor::ExtractOp::create(
2951 rewriter, loc, adaptor.getRowCount(), ValueRange{zero});
2952 Value rowCountIndex = arith::IndexCastOp::create(
2953 rewriter, loc, rewriter.getIndexType(), rowCount);
2954
2955 SmallVector<Value> dynamicDims;
2956 if (resultTy.isDynamicDim(0))
2957 dynamicDims.push_back(
2958 tensor::DimOp::create(rewriter, loc, adaptor.getValues(), 0));
2959 if (resultTy.isDynamicDim(1)) {
2960 Value indicesWidth =
2961 tensor::DimOp::create(rewriter, loc, adaptor.getIndices(), 1);
2962 dynamicDims.push_back(
2963 arith::MulIOp::create(rewriter, loc, indicesWidth, rowCountIndex));
2964 }
2965 if (resultTy.isDynamicDim(2))
2966 dynamicDims.push_back(
2967 tensor::DimOp::create(rewriter, loc, adaptor.getValues(), 2));
2968
2969 Value emptyTensor =
2970 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
2971 resultTy.getElementType(), dynamicDims);
2972 SmallVector<AffineMap> affineMaps = {
2973 rewriter.getMultiDimIdentityMap(resultTy.getRank())};
2974
2975 auto genericOp = linalg::GenericOp::create(
2976 rewriter, loc, ArrayRef<Type>{resultTy}, ValueRange{},
2977 ValueRange{emptyTensor}, affineMaps,
2978 getNParallelLoopsAttrs(resultTy.getRank()),
2979 [&](OpBuilder &builder, Location nestedLoc, ValueRange) {
2980 Value batch = linalg::IndexOp::create(builder, nestedLoc, 0);
2981 Value outputRow = linalg::IndexOp::create(builder, nestedLoc, 1);
2982 Value channel = linalg::IndexOp::create(builder, nestedLoc, 2);
2983 Value indexSlot = arith::DivUIOp::create(builder, nestedLoc,
2984 outputRow, rowCountIndex);
2985 Value rowOffset = arith::RemUIOp::create(builder, nestedLoc,
2986 outputRow, rowCountIndex);
2987 Value index = tensor::ExtractOp::create(builder, nestedLoc,
2988 adaptor.getIndices(),
2989 ValueRange{batch, indexSlot});
2990 Value row = arith::IndexCastOp::create(builder, nestedLoc,
2991 builder.getIndexType(), index);
2992 row = arith::AddIOp::create(builder, nestedLoc, row, rowOffset);
2993 Value result =
2994 tensor::ExtractOp::create(builder, nestedLoc, adaptor.getValues(),
2995 ValueRange{batch, row, channel});
2996 linalg::YieldOp::create(builder, nestedLoc, result);
2997 });
2998 rewriter.replaceOp(op, genericOp.getResult(0));
2999 return success();
3000 }
3001};
3002
3003// Lowerings the TableOp to a series of gathers and numerica operations. This
3004// includes interpolation between the high/low values. For the I8 varient, this
3005// simplifies to a single gather operation.
3006class TableConverter : public OpRewritePattern<tosa::TableOp> {
3007public:
3008 using OpRewritePattern<tosa::TableOp>::OpRewritePattern;
3009
3010 LogicalResult matchAndRewrite(tosa::TableOp op,
3011 PatternRewriter &rewriter) const final {
3012 auto loc = op.getLoc();
3013 Value input = op.getInput1();
3014 Value table = op.getTable();
3015 auto inputTy = cast<ShapedType>(input.getType());
3016 auto tableTy = cast<ShapedType>(table.getType());
3017 auto resultTy = cast<ShapedType>(op.getType());
3018
3019 auto inputElementTy = inputTy.getElementType();
3020 auto tableElementTy = tableTy.getElementType();
3021 auto resultElementTy = resultTy.getElementType();
3022
3023 SmallVector<Value> dynDims;
3024 for (int i = 0; i < resultTy.getRank(); ++i) {
3025 if (inputTy.isDynamicDim(i)) {
3026 dynDims.push_back(
3027 tensor::DimOp::create(rewriter, loc, op.getOperand(0), i));
3028 }
3029 }
3030
3031 auto emptyTensor =
3032 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
3033 resultElementTy, dynDims)
3034 .getResult();
3035
3036 SmallVector<AffineMap, 2> affineMaps = {
3037 rewriter.getMultiDimIdentityMap(resultTy.getRank()),
3038 rewriter.getMultiDimIdentityMap(resultTy.getRank())};
3039
3040 auto genericOp = linalg::GenericOp::create(
3041 rewriter, loc, resultTy, ValueRange({input}), ValueRange{emptyTensor},
3042 affineMaps, getNParallelLoopsAttrs(resultTy.getRank()));
3043 rewriter.replaceOp(op, genericOp.getResult(0));
3044
3045 {
3046 OpBuilder::InsertionGuard regionGuard(rewriter);
3047 Block *block = rewriter.createBlock(
3048 &genericOp.getRegion(), genericOp.getRegion().end(),
3049 TypeRange({inputElementTy, resultElementTy}), {loc, loc});
3050
3051 auto inputValue = block->getArgument(0);
3052 rewriter.setInsertionPointToStart(block);
3053 if (inputElementTy.isInteger(8) && tableElementTy.isInteger(8) &&
3054 resultElementTy.isInteger(8)) {
3055 Value index = arith::IndexCastOp::create(
3056 rewriter, loc, rewriter.getIndexType(), inputValue);
3057 Value offset = arith::ConstantIndexOp::create(rewriter, loc, 128);
3058 index = arith::AddIOp::create(rewriter, loc, rewriter.getIndexType(),
3059 index, offset);
3060 Value extract =
3061 tensor::ExtractOp::create(rewriter, loc, table, ValueRange{index});
3062 linalg::YieldOp::create(rewriter, loc, extract);
3063 return success();
3064 }
3065
3066 if (inputElementTy.isInteger(16) && tableElementTy.isInteger(16) &&
3067 resultElementTy.isInteger(32)) {
3068 Value extend = arith::ExtSIOp::create(
3069 rewriter, loc, rewriter.getI32Type(), inputValue);
3070
3071 auto offset = arith::ConstantOp::create(
3072 rewriter, loc, rewriter.getI32IntegerAttr(32768));
3073 auto seven = arith::ConstantOp::create(rewriter, loc,
3074 rewriter.getI32IntegerAttr(7));
3075 auto one = arith::ConstantOp::create(rewriter, loc,
3076 rewriter.getI32IntegerAttr(1));
3077 auto b1111111 = arith::ConstantOp::create(
3078 rewriter, loc, rewriter.getI32IntegerAttr(127));
3079
3080 // Compute the index and fractional part from the input value:
3081 // value = value + 32768
3082 // index = value >> 7;
3083 // fraction = 0x01111111 & value
3084 auto extendAdd = arith::AddIOp::create(rewriter, loc, extend, offset);
3085 Value index = arith::ShRUIOp::create(rewriter, loc, extendAdd, seven);
3086 Value fraction =
3087 arith::AndIOp::create(rewriter, loc, extendAdd, b1111111);
3088
3089 // Extract the base and next values from the table.
3090 // base = (int32_t) table[index];
3091 // next = (int32_t) table[index + 1];
3092 Value indexPlusOne = arith::AddIOp::create(rewriter, loc, index, one);
3093
3094 index = arith::IndexCastOp::create(rewriter, loc,
3095 rewriter.getIndexType(), index);
3096 indexPlusOne = arith::IndexCastOp::create(
3097 rewriter, loc, rewriter.getIndexType(), indexPlusOne);
3098
3099 Value base =
3100 tensor::ExtractOp::create(rewriter, loc, table, ValueRange{index});
3101 Value next = tensor::ExtractOp::create(rewriter, loc, table,
3102 ValueRange{indexPlusOne});
3103
3104 base =
3105 arith::ExtSIOp::create(rewriter, loc, rewriter.getI32Type(), base);
3106 next =
3107 arith::ExtSIOp::create(rewriter, loc, rewriter.getI32Type(), next);
3108
3109 // Use the fractional part to interpolate between the input values:
3110 // result = (base << 7) + (next - base) * fraction
3111 Value baseScaled = arith::ShLIOp::create(rewriter, loc, base, seven);
3112 Value diff = arith::SubIOp::create(rewriter, loc, next, base);
3113 Value diffScaled = arith::MulIOp::create(rewriter, loc, diff, fraction);
3114 Value result =
3115 arith::AddIOp::create(rewriter, loc, baseScaled, diffScaled);
3116
3117 linalg::YieldOp::create(rewriter, loc, result);
3118
3119 return success();
3120 }
3121 }
3122
3123 return rewriter.notifyMatchFailure(
3124 op, "unable to create body for tosa.table op");
3125 }
3126};
3127
3128struct RFFT2dConverter final : public OpRewritePattern<RFFT2dOp> {
3129 using OpRewritePattern<RFFT2dOp>::OpRewritePattern;
3130
3131 static bool isRankedTensor(Type type) { return isa<RankedTensorType>(type); }
3132
3133 static OpFoldResult halfPlusOne(OpBuilder &builder, Location loc,
3134 OpFoldResult ofr) {
3135 auto one = arith::ConstantIndexOp::create(builder, loc, 1);
3136 auto two = arith::ConstantIndexOp::create(builder, loc, 2);
3137
3138 auto value = getValueOrCreateConstantIndexOp(builder, loc, ofr);
3139 auto divBy2 = builder.createOrFold<arith::DivUIOp>(loc, value, two);
3140 auto plusOne = builder.createOrFold<arith::AddIOp>(loc, divBy2, one);
3141 return getAsOpFoldResult(plusOne);
3142 }
3143
3144 static RankedTensorType
3145 computeOutputShape(OpBuilder &builder, Location loc, Value input,
3146 llvm::SmallVectorImpl<Value> &dynamicSizes) {
3147 // Get [N, H, W]
3148 auto dims = tensor::getMixedSizes(builder, loc, input);
3149
3150 // Set W = (W / 2) + 1 to account for the half-sized W dimension of the
3151 // output tensors.
3152 dims[2] = halfPlusOne(builder, loc, dims[2]);
3153
3154 llvm::SmallVector<int64_t, 3> staticSizes;
3155 dispatchIndexOpFoldResults(dims, dynamicSizes, staticSizes);
3156
3157 auto elementType = cast<RankedTensorType>(input.getType()).getElementType();
3158 return RankedTensorType::get(staticSizes, elementType);
3159 }
3160
3161 static Value createZeroTensor(PatternRewriter &rewriter, Location loc,
3162 RankedTensorType type,
3163 llvm::ArrayRef<Value> dynamicSizes) {
3164 auto emptyTensor =
3165 tensor::EmptyOp::create(rewriter, loc, type, dynamicSizes);
3166 auto fillValueAttr = rewriter.getZeroAttr(type.getElementType());
3167 auto fillValue = arith::ConstantOp::create(rewriter, loc, fillValueAttr);
3168 auto filledTensor =
3169 linalg::FillOp::create(rewriter, loc, ValueRange{fillValue},
3170 ValueRange{emptyTensor})
3171 .result();
3172 return filledTensor;
3173 }
3174
3175 static Value castIndexToFloat(OpBuilder &builder, Location loc,
3176 FloatType type, Value value) {
3177 auto integerVal = arith::IndexCastUIOp::create(
3178 builder, loc,
3179 type.getIntOrFloatBitWidth() > 32 ? builder.getI64Type()
3180 : builder.getI32Type(),
3181 value);
3182
3183 return arith::UIToFPOp::create(builder, loc, type, integerVal);
3184 }
3185
3186 static Value createLinalgIndex(OpBuilder &builder, Location loc,
3187 FloatType type, int64_t index) {
3188 auto indexVal = linalg::IndexOp::create(builder, loc, index);
3189 return castIndexToFloat(builder, loc, type, indexVal);
3190 }
3191
3192 template <typename... Args>
3193 static llvm::SmallVector<AffineExpr, 4> affineDimsExpr(OpBuilder &builder,
3194 Args... args) {
3195 return {builder.getAffineDimExpr(args)...};
3196 }
3197
3198 LogicalResult matchAndRewrite(RFFT2dOp rfft2d,
3199 PatternRewriter &rewriter) const override {
3200 if (!llvm::all_of(rfft2d->getOperandTypes(), isRankedTensor) ||
3201 !llvm::all_of(rfft2d->getResultTypes(), isRankedTensor)) {
3202 return rewriter.notifyMatchFailure(rfft2d,
3203 "only supports ranked tensors");
3204 }
3205
3206 auto loc = rfft2d.getLoc();
3207 auto input = rfft2d.getInputReal();
3208 auto elementType =
3209 dyn_cast<FloatType>(cast<ShapedType>(input.getType()).getElementType());
3210 if (!elementType)
3211 return rewriter.notifyMatchFailure(rfft2d,
3212 "only supports float element types");
3213
3214 // Compute the output type and set of dynamic sizes
3215 llvm::SmallVector<Value> dynamicSizes;
3216 auto outputType = computeOutputShape(rewriter, loc, input, dynamicSizes);
3217
3218 // Iterator types for the linalg.generic implementation
3219 llvm::SmallVector<utils::IteratorType, 5> iteratorTypes = {
3220 utils::IteratorType::parallel, utils::IteratorType::parallel,
3221 utils::IteratorType::parallel, utils::IteratorType::reduction,
3222 utils::IteratorType::reduction};
3223
3224 // Inputs/outputs to the linalg.generic implementation
3225 llvm::SmallVector<Value> genericOpInputs = {input};
3226 llvm::SmallVector<Value> genericOpOutputs = {
3227 createZeroTensor(rewriter, loc, outputType, dynamicSizes),
3228 createZeroTensor(rewriter, loc, outputType, dynamicSizes)};
3229
3230 // Indexing maps for input and output tensors
3231 auto indexingMaps = AffineMap::inferFromExprList(
3232 llvm::ArrayRef{affineDimsExpr(rewriter, 0, 3, 4),
3233 affineDimsExpr(rewriter, 0, 1, 2),
3234 affineDimsExpr(rewriter, 0, 1, 2)},
3235 rewriter.getContext());
3236
3237 // Width and height dimensions of the original input.
3238 auto dimH = rewriter.createOrFold<tensor::DimOp>(loc, input, 1);
3239 auto dimW = rewriter.createOrFold<tensor::DimOp>(loc, input, 2);
3240
3241 // Constants and dimension sizes
3242 auto zeroFloat = arith::ConstantOp::create(
3243 rewriter, loc, rewriter.getZeroAttr(elementType));
3244 auto twoPiAttr = rewriter.getFloatAttr(elementType, 6.283185307179586);
3245 auto twoPi = arith::ConstantOp::create(rewriter, loc, twoPiAttr);
3246
3247 auto zeroIndex = arith::ConstantIndexOp::create(rewriter, loc, 0);
3248 auto twoIndex = arith::ConstantIndexOp::create(rewriter, loc, 2);
3249
3250 auto constH = castIndexToFloat(rewriter, loc, elementType, dimH);
3251 auto constW = castIndexToFloat(rewriter, loc, elementType, dimW);
3252 auto halfH = index::DivUOp::create(rewriter, loc, dimH, twoIndex);
3253 auto halfW = index::DivUOp::create(rewriter, loc, dimW, twoIndex);
3254
3255 auto buildBody = [&](OpBuilder &builder, Location loc, ValueRange args) {
3256 Value valReal = args[0];
3257 Value sumReal = args[1];
3258 Value sumImag = args[2];
3259
3260 // Indices for angle computation
3261 Value oy = linalg::IndexOp::create(builder, loc, 1);
3262 Value ox = linalg::IndexOp::create(builder, loc, 2);
3263 Value iy = linalg::IndexOp::create(builder, loc, 3);
3264 Value ix = linalg::IndexOp::create(builder, loc, 4);
3265
3266 // Calculating angle without integer parts of components as sin/cos are
3267 // periodic: angle = 2 * pi() * ( ( (iy * oy) % H) / H + ( (ix * ox) % W )
3268 // / W);
3269 auto iyXoy = index::MulOp::create(builder, loc, iy, oy);
3270 auto ixXox = index::MulOp::create(builder, loc, ix, ox);
3271
3272 auto iyRem = index::RemUOp::create(builder, loc, iyXoy, dimH);
3273 auto ixRem = index::RemUOp::create(builder, loc, ixXox, dimW);
3274
3275 auto iyRemFloat = castIndexToFloat(builder, loc, elementType, iyRem);
3276 auto ixRemFloat = castIndexToFloat(builder, loc, elementType, ixRem);
3277
3278 auto yComponent = arith::DivFOp::create(builder, loc, iyRemFloat, constH);
3279 auto xComponent = arith::DivFOp::create(builder, loc, ixRemFloat, constW);
3280 auto sumXY = arith::AddFOp::create(builder, loc, yComponent, xComponent);
3281 auto angle = arith::MulFOp::create(builder, loc, twoPi, sumXY);
3282
3283 // We will check the indices to see if this is a position that should use
3284 // a 0.0 weight for the imaginary value computation following the TOSA
3285 // specification with `tosa_extra_multiplies=true`.
3286 //
3287 // These are the relevant locations: (0,0), (0,W/2), (H/2,0), (H/2, W/2).
3288 auto iyIs0 = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::eq,
3289 iyRem, zeroIndex);
3290 auto iyIsHalfH = arith::CmpIOp::create(
3291 builder, loc, arith::CmpIPredicate::eq, iyRem, halfH);
3292 auto ixIs0 = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::eq,
3293 ixRem, zeroIndex);
3294 auto ixIsHalfW = arith::CmpIOp::create(
3295 builder, loc, arith::CmpIPredicate::eq, ixRem, halfW);
3296
3297 auto iyIsSinSkippable =
3298 arith::OrIOp::create(builder, loc, iyIs0, iyIsHalfH);
3299 auto ixIsSinSkippable =
3300 arith::OrIOp::create(builder, loc, ixIs0, ixIsHalfW);
3301 auto shouldSkipSin = arith::AndIOp::create(builder, loc, iyIsSinSkippable,
3302 ixIsSinSkippable);
3303
3304 // realComponent = valReal * cos(angle)
3305 // imagComponent = valReal * (shouldSkipSin ? 0.0 : sin(angle))
3306 auto cosAngle = math::CosOp::create(builder, loc, angle);
3307 auto sinAngle = math::SinOp::create(builder, loc, angle);
3308 auto imagWeight = arith::SelectOp::create(builder, loc, shouldSkipSin,
3309 zeroFloat, sinAngle);
3310 auto realComponent =
3311 arith::MulFOp::create(builder, loc, valReal, cosAngle);
3312 auto imagComponent =
3313 arith::MulFOp::create(builder, loc, valReal, imagWeight);
3314
3315 // outReal = sumReal + realComponent
3316 // outImag = sumImag - imagComponent
3317 auto outReal =
3318 arith::AddFOp::create(builder, loc, sumReal, realComponent);
3319 auto outImag =
3320 arith::SubFOp::create(builder, loc, sumImag, imagComponent);
3321
3322 linalg::YieldOp::create(builder, loc, ValueRange{outReal, outImag});
3323 };
3324
3325 rewriter.replaceOpWithNewOp<linalg::GenericOp>(
3326 rfft2d, rfft2d.getResultTypes(), genericOpInputs, genericOpOutputs,
3327 indexingMaps, iteratorTypes, buildBody);
3328
3329 return success();
3330 }
3331};
3332
3333struct FFT2dConverter final : OpRewritePattern<FFT2dOp> {
3334 using Base::Base;
3335
3336 LogicalResult matchAndRewrite(FFT2dOp fft2d,
3337 PatternRewriter &rewriter) const override {
3338 if (!llvm::all_of(fft2d->getOperandTypes(),
3339 RFFT2dConverter::isRankedTensor) ||
3340 !llvm::all_of(fft2d->getResultTypes(),
3341 RFFT2dConverter::isRankedTensor)) {
3342 return rewriter.notifyMatchFailure(fft2d, "only supports ranked tensors");
3343 }
3344
3345 Location loc = fft2d.getLoc();
3346 Value input_real = fft2d.getInputReal();
3347 Value input_imag = fft2d.getInputImag();
3348 BoolAttr inverse = fft2d.getInverseAttr();
3349
3350 auto real_el_ty = cast<FloatType>(
3351 cast<ShapedType>(input_real.getType()).getElementType());
3352 [[maybe_unused]] auto imag_el_ty = cast<FloatType>(
3353 cast<ShapedType>(input_imag.getType()).getElementType());
3354
3355 assert(real_el_ty == imag_el_ty);
3356
3357 // Compute the output type and set of dynamic sizes
3358 SmallVector<Value> dynamicSizes;
3359
3360 // Get [N, H, W]
3361 auto dims = tensor::getMixedSizes(rewriter, loc, input_real);
3362
3363 SmallVector<int64_t, 3> staticSizes;
3364 dispatchIndexOpFoldResults(dims, dynamicSizes, staticSizes);
3365
3366 auto outputType = RankedTensorType::get(staticSizes, real_el_ty);
3367
3368 // Iterator types for the linalg.generic implementation
3369 SmallVector<utils::IteratorType, 5> iteratorTypes = {
3370 utils::IteratorType::parallel, utils::IteratorType::parallel,
3371 utils::IteratorType::parallel, utils::IteratorType::reduction,
3372 utils::IteratorType::reduction};
3373
3374 // Inputs/outputs to the linalg.generic implementation
3375 SmallVector<Value> genericOpInputs = {input_real, input_imag};
3376 SmallVector<Value> genericOpOutputs = {
3377 RFFT2dConverter::createZeroTensor(rewriter, loc, outputType,
3378 dynamicSizes),
3379 RFFT2dConverter::createZeroTensor(rewriter, loc, outputType,
3380 dynamicSizes)};
3381
3382 // Indexing maps for input and output tensors
3383 auto indexingMaps = AffineMap::inferFromExprList(
3384 ArrayRef{RFFT2dConverter::affineDimsExpr(rewriter, 0, 3, 4),
3385 RFFT2dConverter::affineDimsExpr(rewriter, 0, 3, 4),
3386 RFFT2dConverter::affineDimsExpr(rewriter, 0, 1, 2),
3387 RFFT2dConverter::affineDimsExpr(rewriter, 0, 1, 2)},
3388 rewriter.getContext());
3389
3390 // Width and height dimensions of the original input.
3391 auto dimH = rewriter.createOrFold<tensor::DimOp>(loc, input_real, 1);
3392 auto dimW = rewriter.createOrFold<tensor::DimOp>(loc, input_real, 2);
3393
3394 // Constants and dimension sizes
3395 auto twoPiAttr = rewriter.getFloatAttr(real_el_ty, 6.283185307179586);
3396 auto twoPi = arith::ConstantOp::create(rewriter, loc, twoPiAttr);
3397 Value constH =
3398 RFFT2dConverter::castIndexToFloat(rewriter, loc, real_el_ty, dimH);
3399 Value constW =
3400 RFFT2dConverter::castIndexToFloat(rewriter, loc, real_el_ty, dimW);
3401
3402 auto buildBody = [&](OpBuilder &builder, Location loc, ValueRange args) {
3403 Value valReal = args[0];
3404 Value valImag = args[1];
3405 Value sumReal = args[2];
3406 Value sumImag = args[3];
3407
3408 // Indices for angle computation
3409 Value oy = linalg::IndexOp::create(builder, loc, 1);
3410 Value ox = linalg::IndexOp::create(builder, loc, 2);
3411 Value iy = linalg::IndexOp::create(builder, loc, 3);
3412 Value ix = linalg::IndexOp::create(builder, loc, 4);
3413
3414 // float_t angle = sign_val * 2 * pi() * ( ( (iy * oy) % H) / H + ( (ix *
3415 // ox) % W ) / W);
3416 auto iyXoy = index::MulOp::create(builder, loc, iy, oy);
3417 auto ixXox = index::MulOp::create(builder, loc, ix, ox);
3418
3419 auto iyRem = index::RemUOp::create(builder, loc, iyXoy, dimH);
3420 auto ixRem = index::RemUOp::create(builder, loc, ixXox, dimW);
3421
3422 auto iyRemFloat =
3423 RFFT2dConverter::castIndexToFloat(builder, loc, real_el_ty, iyRem);
3424 auto ixRemFloat =
3425 RFFT2dConverter::castIndexToFloat(builder, loc, real_el_ty, ixRem);
3426
3427 auto yComponent = arith::DivFOp::create(builder, loc, iyRemFloat, constH);
3428 auto xComponent = arith::DivFOp::create(builder, loc, ixRemFloat, constW);
3429
3430 auto sumXY = arith::AddFOp::create(builder, loc, yComponent, xComponent);
3431 auto angle = arith::MulFOp::create(builder, loc, twoPi, sumXY);
3432
3433 if (inverse.getValue()) {
3434 angle = arith::MulFOp::create(
3435 builder, loc, angle,
3436 arith::ConstantOp::create(rewriter, loc,
3437 rewriter.getFloatAttr(real_el_ty, -1.0)));
3438 }
3439
3440 // realComponent = val_real * cos(a) + val_imag * sin(a);
3441 // imagComponent = -val_real * sin(a) + val_imag * cos(a);
3442 auto cosAngle = math::CosOp::create(builder, loc, angle);
3443 auto sinAngle = math::SinOp::create(builder, loc, angle);
3444
3445 auto rcos = arith::MulFOp::create(builder, loc, valReal, cosAngle);
3446 auto rsin = arith::MulFOp::create(builder, loc, valImag, sinAngle);
3447 auto realComponent = arith::AddFOp::create(builder, loc, rcos, rsin);
3448
3449 auto icos = arith::MulFOp::create(builder, loc, valImag, cosAngle);
3450 auto isin = arith::MulFOp::create(builder, loc, valReal, sinAngle);
3451
3452 auto imagComponent = arith::SubFOp::create(builder, loc, icos, isin);
3453
3454 // outReal = sumReal + realComponent
3455 // outImag = sumImag - imagComponent
3456 auto outReal =
3457 arith::AddFOp::create(builder, loc, sumReal, realComponent);
3458 auto outImag =
3459 arith::AddFOp::create(builder, loc, sumImag, imagComponent);
3460
3461 linalg::YieldOp::create(builder, loc, ValueRange{outReal, outImag});
3462 };
3463
3464 rewriter.replaceOpWithNewOp<linalg::GenericOp>(
3465 fft2d, fft2d.getResultTypes(), genericOpInputs, genericOpOutputs,
3466 indexingMaps, iteratorTypes, buildBody);
3467
3468 return success();
3469 }
3470};
3471
3472} // namespace
3473
3475 const TypeConverter &converter, RewritePatternSet *patterns,
3476 const TosaToLinalgOptions &options) {
3477
3478 // We have multiple resize coverters to handle degenerate cases.
3479 patterns->add<GenericResizeConverter>(patterns->getContext(),
3480 /*benefit=*/100);
3481 patterns->add<ResizeUnaryConverter>(patterns->getContext(),
3482 /*benefit=*/200);
3483 patterns->add<MaterializeResizeBroadcast>(patterns->getContext(),
3484 /*benefit=*/300);
3485
3486 patterns->add<
3487 // clang-format off
3488 PointwiseConverter<tosa::AddOp>,
3489 PointwiseConverter<tosa::SubOp>,
3490 PointwiseConverter<tosa::MulOp>,
3491 PointwiseConverter<tosa::IntDivOp>,
3492 PointwiseConverter<tosa::NegateOp>,
3493 PointwiseConverter<tosa::PowOp>,
3494 PointwiseConverter<tosa::ReciprocalOp>,
3495 PointwiseConverter<tosa::RsqrtOp>,
3496 PointwiseConverter<tosa::LogOp>,
3497 PointwiseConverter<tosa::ExpOp>,
3498 PointwiseConverter<tosa::AbsOp>,
3499 PointwiseConverter<tosa::SinOp>,
3500 PointwiseConverter<tosa::CosOp>,
3501 PointwiseConverter<tosa::TanhOp>,
3502 PointwiseConverter<tosa::ErfOp>,
3503 PointwiseConverter<tosa::BitwiseAndOp>,
3504 PointwiseConverter<tosa::BitwiseOrOp>,
3505 PointwiseConverter<tosa::BitwiseNotOp>,
3506 PointwiseConverter<tosa::BitwiseXorOp>,
3507 PointwiseConverter<tosa::LogicalAndOp>,
3508 PointwiseConverter<tosa::LogicalNotOp>,
3509 PointwiseConverter<tosa::LogicalOrOp>,
3510 PointwiseConverter<tosa::LogicalXorOp>,
3511 PointwiseConverter<tosa::CastOp>,
3512 PointwiseConverter<tosa::LogicalLeftShiftOp>,
3513 PointwiseConverter<tosa::LogicalRightShiftOp>,
3514 PointwiseConverter<tosa::ArithmeticRightShiftOp>,
3515 PointwiseConverter<tosa::ClzOp>,
3516 PointwiseConverter<tosa::SelectOp>,
3517 PointwiseConverter<tosa::GreaterOp>,
3518 PointwiseConverter<tosa::GreaterEqualOp>,
3519 PointwiseConverter<tosa::EqualOp>,
3520 PointwiseConverter<tosa::MaximumOp>,
3521 PointwiseConverter<tosa::MinimumOp>,
3522 PointwiseConverter<tosa::CeilOp>,
3523 PointwiseConverter<tosa::FloorOp>,
3524 PointwiseConverter<tosa::ClampOp>,
3525 PointwiseConverter<tosa::SigmoidOp>
3526 >(converter, patterns->getContext());
3527
3528 patterns->add<QuantizedMatMulTConverter>(converter, patterns->getContext());
3529
3530 patterns->add<
3531 IdentityNConverter<tosa::IdentityOp>,
3532 GatherConverter,
3533 RowGatherConverter,
3534 RescaleConverter,
3535 ReverseConverter,
3536 RFFT2dConverter,
3537 FFT2dConverter,
3538 TableConverter,
3539 TileConverter>(patterns->getContext());
3540
3541 // Reductions seeded with a float min/max identity need to know whether
3542 // non-finite values are available on the target.
3543 patterns->add<
3544 ReduceConverter<tosa::ReduceAllOp>,
3545 ReduceConverter<tosa::ReduceAnyOp>,
3546 ReduceConverter<tosa::ReduceMinOp>,
3547 ReduceConverter<tosa::ReduceMaxOp>,
3548 ReduceConverter<tosa::ReduceSumOp>,
3549 ReduceConverter<tosa::ReduceProductOp>,
3550 ArgMinMaxConverter<tosa::ArgMaxOp>,
3551 ArgMinMaxConverter<tosa::ArgMinOp>>(patterns->getContext(), options.allowNonFinites);
3552 // clang-format on
3553}
return success()
static int64_t product(ArrayRef< int64_t > vals)
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
static llvm::ManagedStatic< PassManagerOptions > options
static Value clamp(ImplicitLocOpBuilder &builder, Value value, Value lowerBound, Value upperBound)
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy, PatternRewriter &rewriter, bool allowNonFinites)
static OpFoldResult getOrFoldTensorDim(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, Value tensor, int64_t index)
static Value createLinalgBodyCalculationForReduceOp(Operation *op, ValueRange args, Type elementTy, PatternRewriter &rewriter)
static std::pair< Value, Value > normalizeMatMulBatchDimensions(PatternRewriter &rewriter, Location loc, Value a, Value b, int64_t batchRank)
static OpTy createWithDefaultProperties(OpBuilder &builder, Location loc, TypeRange resultTypes, ValueRange operands)
static Value broadcastMatMulLeadingDimensions(PatternRewriter &rewriter, Location loc, Value operand, Value other)
static Value getTensorDim(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, Value tensor, int64_t index)
static Value createIndex(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, int64_t index)
static LogicalResult reduceMatchAndRewriteHelper(OpTy op, uint64_t axis, PatternRewriter &rewriter, bool allowNonFinites)
static ValueRange getScalarOperands(Operation *operation, ValueRange operands)
static Value broadcastDynamicDimensions(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, Value operand, ArrayRef< OpFoldResult > targetShape, ArrayRef< Value > masterOperands)
static Value broadcastDynamicDimension(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, Value operand, int64_t dim, OpFoldResult targetSize, Value masterOperand)
static LogicalResult elementwiseMatchAndRewriteHelper(Operation *operation, ValueRange operands, ConversionPatternRewriter &rewriter, const TypeConverter &converter)
static Value createLinalgBodyCalculationForElementwiseOp(Operation *op, ValueRange args, ArrayRef< Type > resultTypes, ConversionPatternRewriter &rewriter)
static LogicalResult emitElementwiseComputation(ConversionPatternRewriter &rewriter, Location loc, Operation *operation, ValueRange operands, ValueRange scalarOperands, ArrayRef< OpFoldResult > targetShape, const TypeConverter &converter)
static ValueRange getBroadcastableOperands(Operation *operation, ValueRange operands)
static Value materializeBinaryNanCheckIfRequired(OpTy op, PatternRewriter &rewriter, Value lhs, Value rhs, Value result)
static std::pair< SmallVector< OpFoldResult >, SmallVector< Value > > computeTargetShape(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, ValueRange operands)
static bool operandsAndResultsRanked(Operation *operation)
static Value broadcastMatMulSingletonDimension(PatternRewriter &rewriter, Location loc, Value operand, int64_t dim, OpFoldResult size)
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
static const llvm::fltSemantics * getFloatSemantics(TruncfSrcElemTypes etype)
Float semantics the element type attributes of xevm.truncf and xevm.extf stand for.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
BlockArgument getArgument(unsigned i)
Definition Block.h:154
bool getValue() const
Return the boolean value of this attribute.
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IntegerAttr getI32IntegerAttr(int32_t value)
Definition Builders.cpp:208
FloatType getF32Type()
Definition Builders.cpp:51
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:396
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
AffineExpr getAffineConstantExpr(int64_t constant)
Definition Builders.cpp:381
IntegerType getI64Type()
Definition Builders.cpp:73
IntegerType getI32Type()
Definition Builders.cpp:71
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
Ty getType(Args &&...args)
Get or construct an instance of the type Ty with provided arguments.
Definition Builders.h:94
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
MLIRContext * getContext() const
Definition Builders.h:56
IntegerAttr getI8IntegerAttr(int8_t value)
Definition Builders.cpp:230
An attribute that represents a reference to a dense vector or tensor object.
auto getValues() const
Return the held element values as a range of the given type.
An attribute that represents a reference to a dense integer vector or tensor object.
static DenseIntElementsAttr get(const ShapedType &type, Arg &&arg)
Get an instance of a DenseIntElementsAttr with the given arguments.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
This class represents a single result from folding an operation.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:377
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
unsigned getNumOperands()
Definition Operation.h:373
result_type_range getResultTypes()
Definition Operation.h:455
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:405
result_range getResults()
Definition Operation.h:442
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:431
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isUnsignedInteger() const
Return true if this is an unsigned integer type (with the specified width).
Definition Types.cpp:90
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
type_range getType() const
Type front()
Return first type in the range.
Definition TypeRange.h:164
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:333
InFlightDiagnostic & next(InFlightDiagnostic &diag)
Starts a new message part in an in-flight diagnostic.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
Value createCanonicalRankReducingExtractSliceOp(OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType)
Create a rank-reducing ExtractSliceOp @[0 .
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:82
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
Value clampFloatHelper(Location loc, Value arg, Value min, Value max, OpBuilder &rewriter)
llvm::DenseMap< int64_t, Value > IndexPool
Caches the arith.constant index values created for a set of dimension indices so that broadcasting he...
SmallVector< utils::IteratorType > getNParallelLoopsAttrs(unsigned nParallelLoops)
SmallVector< Value > condenseValues(const SmallVector< Value > &values)
std::optional< SmallVector< Value > > checkHasDynamicBatchDims(PatternRewriter &rewriter, Op op, ArrayRef< Value > params)
MatMulTOperands prepareMatMulOperands(PatternRewriter &rewriter, Location loc, Value a, Value b, RankedTensorType outputType, bool transposeB)
Broadcast the batch dimensions of the ranked operands and create a zero-filled result tensor for eith...
std::pair< OpFoldResult, Value > computeTargetSize(PatternRewriter &rewriter, Location loc, IndexPool &indexPool, ValueRange operands, int64_t dim)
Compute the broadcasted runtime size of dimension dim of an elementwise operation whose operands all ...
APFloat getFloatMinMaxIdentity(const llvm::fltSemantics &semantics, bool negative, bool allowNonFinites)
void populateTosaToLinalgConversionPatterns(const TypeConverter &converter, RewritePatternSet *patterns, const TosaToLinalgOptions &options=TosaToLinalgOptions())
Populates conversion passes from TOSA dialect to Linalg dialect.
Value getTosaConstShape(ImplicitLocOpBuilder &builder, llvm::ArrayRef< int64_t > shape)
SmallVector< int64_t > convertFromMlirShape(ArrayRef< int64_t > shape)
Value clampIntHelper(Location loc, Value arg, Value min, Value max, OpBuilder &rewriter, bool isUnsigned)
bool getConstShapeValues(Operation *op, llvm::SmallVector< int64_t > &result_shape)
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...