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