MLIR 24.0.0git
TosaNarrowTypes.cpp
Go to the documentation of this file.
1//===- TosaNarrowTypes.cpp ------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the TOSA narrowing passes that rewrite tensor element
10// types to narrower equivalents (i64 -> i32, f64 -> f32, ...).
11//
12//===----------------------------------------------------------------------===//
13
15
16#include "llvm/ADT/APFloat.h"
17
18#include <algorithm>
19#include <limits>
20#include <type_traits>
21#include <utility>
22
29#include "mlir/IR/Verifier.h"
30#include "mlir/Pass/Pass.h"
31
32namespace mlir {
33namespace tosa {
34#define GEN_PASS_DEF_TOSANARROWI64TOI32PASS
35#define GEN_PASS_DEF_TOSANARROWF64TOF32PASS
36#define GEN_PASS_DEF_TOSANARROWF32TOF16PASS
37#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
38} // namespace tosa
39} // namespace mlir
40
41using namespace mlir;
42using namespace mlir::tosa;
43
44namespace {
45
46// Narrowing mode for this pass.
47enum class TosaNarrowKind { Int64ToInt32, Float64ToFloat32, Float32ToFloat16 };
48
49// ---------------------------------------------------------------------------
50// Shared helpers
51// ---------------------------------------------------------------------------
52
53template <TosaNarrowKind Kind>
54bool isSourceInteger(IntegerType type) {
55 if constexpr (Kind == TosaNarrowKind::Int64ToInt32)
56 return type.isInteger(64);
57 return false;
58}
59
60template <TosaNarrowKind Kind>
61bool isSourceFloat(FloatType type) {
62 if constexpr (Kind == TosaNarrowKind::Float64ToFloat32)
63 return type.isF64();
64 if constexpr (Kind == TosaNarrowKind::Float32ToFloat16)
65 return type.isF32();
66 return false;
67}
68
69template <TosaNarrowKind Kind>
70Type convertInteger(IntegerType type) {
71 if (!isSourceInteger<Kind>(type))
72 return type;
73 if constexpr (Kind == TosaNarrowKind::Int64ToInt32)
74 return IntegerType::get(type.getContext(), 32);
75 return type;
76}
77
78template <TosaNarrowKind Kind>
79Type convertFloat(FloatType type) {
80 if (!isSourceFloat<Kind>(type))
81 return type;
82 if constexpr (Kind == TosaNarrowKind::Float64ToFloat32)
83 return Float32Type::get(type.getContext());
84 if constexpr (Kind == TosaNarrowKind::Float32ToFloat16)
85 return Float16Type::get(type.getContext());
86 return type;
87}
88
89template <TosaNarrowKind Kind>
90bool isSourceElement(Type type) {
91 if (auto intTy = dyn_cast<IntegerType>(type))
92 return isSourceInteger<Kind>(intTy);
93 if (auto floatTy = dyn_cast<FloatType>(type))
94 return isSourceFloat<Kind>(floatTy);
95 return false;
96}
97
98template <TosaNarrowKind Kind>
99bool typeNeedsConversion(Type type) {
100 if (auto shaped = dyn_cast<ShapedType>(type))
101 return isSourceElement<Kind>(shaped.getElementType());
102 return isSourceElement<Kind>(type);
103}
104
105FailureOr<APInt> convertIntegerConstant(IntegerType targetType,
106 const APInt &value,
107 bool allowLossyConversion) {
108 const unsigned targetWidth = targetType.getWidth();
109 if (!allowLossyConversion && !value.isSignedIntN(targetWidth))
110 return failure();
111
112 if (allowLossyConversion)
113 return value.truncSSat(targetWidth);
114 return value.sextOrTrunc(targetWidth);
115}
116
117FailureOr<APFloat> convertFloatConstant(FloatType targetType,
118 const APFloat &value,
119 bool allowLossyConversion) {
120 APFloat converted(value);
121 bool losesInfo = false;
122 converted.convert(targetType.getFloatSemantics(),
123 APFloat::rmNearestTiesToEven, &losesInfo);
124 if (!allowLossyConversion && losesInfo)
125 return failure();
126 return converted;
127}
128
129// Narrows scalar constant attributes so they keep matching the converted
130// element types.
131template <TosaNarrowKind Kind>
132FailureOr<Attribute> tryConvertScalarAttribute(Attribute attribute,
133 bool allowLossyConversion) {
134 if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
135 if (const auto intAttr = dyn_cast<IntegerAttr>(attribute)) {
136 if (const auto intType = dyn_cast<IntegerType>(intAttr.getType());
137 intType && isSourceInteger<Kind>(intType)) {
138 const auto convertedType =
139 cast<IntegerType>(convertInteger<Kind>(intType));
140 FailureOr<APInt> convertedValue = convertIntegerConstant(
141 convertedType, intAttr.getValue(), allowLossyConversion);
142 if (failed(convertedValue))
143 return failure();
144 return IntegerAttr::get(convertedType, convertedValue.value());
145 }
146 }
147 } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
148 Kind == TosaNarrowKind::Float32ToFloat16) {
149 if (const auto floatAttr = dyn_cast<FloatAttr>(attribute)) {
150 if (const auto floatType = dyn_cast<FloatType>(floatAttr.getType());
151 floatType && isSourceFloat<Kind>(floatType)) {
152 const auto convertedType =
153 cast<FloatType>(convertFloat<Kind>(floatType));
154 FailureOr<APFloat> convertedValue = convertFloatConstant(
155 convertedType, floatAttr.getValue(), allowLossyConversion);
156 if (failed(convertedValue))
157 return failure();
158 return FloatAttr::get(convertedType, convertedValue.value());
159 }
160 }
161 }
162
163 return attribute;
164}
165
166template <TosaNarrowKind Kind>
167FailureOr<Attribute>
168convertDenseIntElementsAttr(ShapedType type, DenseIntElementsAttr attr,
169 const TypeConverter &typeConverter,
170 bool allowLossyConversion) {
171 if constexpr (Kind != TosaNarrowKind::Int64ToInt32)
172 return attr;
173
174 const auto oldElementType = dyn_cast<IntegerType>(type.getElementType());
175 if (!oldElementType || !isSourceInteger<Kind>(oldElementType))
176 return attr;
177
178 const auto newType =
179 dyn_cast_or_null<ShapedType>(typeConverter.convertType(type));
180 if (!newType)
181 return failure();
182
183 const auto newElementType = dyn_cast<IntegerType>(newType.getElementType());
184 if (!newElementType)
185 return failure();
186
187 if (!allowLossyConversion) {
188 for (APInt value : attr.getValues<APInt>())
189 if (failed(convertIntegerConstant(newElementType, value,
190 /*allowLossyConversion=*/false)))
191 return failure();
192 }
193
194 Attribute convertedAttr =
195 attr.mapValues(newElementType, [&](const APInt &value) -> APInt {
196 return convertIntegerConstant(newElementType, value,
197 /*allowLossyConversion=*/true)
198 .value();
199 });
200 return convertedAttr;
201}
202
203template <TosaNarrowKind Kind>
204FailureOr<Attribute>
205convertDenseFPElementsAttr(ShapedType type, DenseFPElementsAttr attr,
206 const TypeConverter &typeConverter,
207 bool allowLossyConversion) {
208 if constexpr (Kind != TosaNarrowKind::Float64ToFloat32 &&
209 Kind != TosaNarrowKind::Float32ToFloat16)
210 return attr;
211
212 const auto oldElementType = dyn_cast<FloatType>(type.getElementType());
213 if (!oldElementType || !isSourceFloat<Kind>(oldElementType))
214 return attr;
215
216 const auto newType =
217 dyn_cast_or_null<ShapedType>(typeConverter.convertType(type));
218 if (!newType)
219 return failure();
220
221 const auto newElementType = dyn_cast<FloatType>(newType.getElementType());
222 if (!newElementType)
223 return failure();
224
225 if (!allowLossyConversion) {
226 for (APFloat value : attr.getValues<APFloat>())
227 if (failed(convertFloatConstant(newElementType, value,
228 /*allowLossyConversion=*/false)))
229 return failure();
230 }
231
232 Attribute convertedAttr =
233 attr.mapValues(newElementType, [&](const APFloat &value) -> APInt {
234 APFloat converted = convertFloatConstant(newElementType, value,
235 /*allowLossyConversion=*/true)
236 .value();
237 // DenseFPElementsAttr stores each float as raw bits, so emit the APInt
238 // representation that MLIR expects in the underlying buffer.
239 return converted.bitcastToAPInt();
240 });
241 return convertedAttr;
242}
243
244template <TosaNarrowKind Kind>
245FailureOr<Attribute> convertDenseResourceElementsAttr(
246 ShapedType type, DenseResourceElementsAttr attr,
247 const TypeConverter &typeConverter, bool allowLossyConversion) {
248 static_assert(Kind == TosaNarrowKind::Int64ToInt32 ||
249 Kind == TosaNarrowKind::Float64ToFloat32 ||
250 Kind == TosaNarrowKind::Float32ToFloat16);
251 using From = std::conditional_t<
252 Kind == TosaNarrowKind::Int64ToInt32, int64_t,
253 std::conditional_t<Kind == TosaNarrowKind::Float64ToFloat32, double,
254 float>>;
255 using To = std::conditional_t<
256 Kind == TosaNarrowKind::Int64ToInt32, int32_t,
257 std::conditional_t<Kind == TosaNarrowKind::Float64ToFloat32, float,
258 uint16_t>>;
259
260 if (Kind == TosaNarrowKind::Int64ToInt32 &&
261 !isa<DenseI64ResourceElementsAttr>(attr)) {
262 return attr;
263 }
264
265 if (Kind == TosaNarrowKind::Float64ToFloat32 &&
266 !isa<DenseF64ResourceElementsAttr>(attr)) {
267 return attr;
268 }
269
270 if (Kind == TosaNarrowKind::Float32ToFloat16 &&
271 !isa<DenseF32ResourceElementsAttr>(attr)) {
272 return attr;
273 }
274
275 const auto newType =
276 dyn_cast_or_null<ShapedType>(typeConverter.convertType(type));
277 if (!newType)
278 return failure();
279
280 const auto newElementType = dyn_cast<FloatType>(newType.getElementType());
281
282 auto narrow = [&](From value) -> FailureOr<To> {
283 if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
284 From clamped = std::clamp<From>(value, std::numeric_limits<To>::min(),
285 std::numeric_limits<To>::max());
286 if (!allowLossyConversion && clamped != value)
287 return failure();
288 return static_cast<To>(clamped);
289 } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32) {
290 To converted = static_cast<To>(value);
291 if (!allowLossyConversion && converted != value)
292 return failure();
293 return converted;
294 } else {
295 FailureOr<APFloat> converted = convertFloatConstant(
296 newElementType, APFloat(value), allowLossyConversion);
297 if (failed(converted))
298 return failure();
299 // Resource blobs require a trivially copyable storage type. Serialize
300 // the converted APFloat only at this boundary.
301 return static_cast<To>(converted->bitcastToAPInt().getZExtValue());
302 }
303 };
304
305 const std::optional<ArrayRef<From>> values =
307 if (!values) {
308 return failure();
309 }
310
311 SmallVector<To> newValues;
312 newValues.reserve(values->size());
313 for (From value : *values) {
314 FailureOr<To> convertedValue = narrow(value);
315 if (failed(convertedValue))
316 return failure();
317 newValues.push_back(*convertedValue);
318 }
319
321 ArrayRef<To>(newValues.data(), newValues.size()));
322
323 auto resourceManager =
325 resourceManager.getBlobManager().update(attr.getRawHandle().getKey(),
326 std::move(blob));
327
328 return DenseResourceElementsAttr::get(newType, attr.getRawHandle());
329}
330
331template <TosaNarrowKind Kind, typename AttrT>
332FailureOr<Attribute>
333convertAttributeWithTypeConverter(AttrT attr, Type type,
334 const TypeConverter *typeConverter) {
335 if (!typeNeedsConversion<Kind>(type))
336 return attr;
337
338 const std::optional<Attribute> convertedAttribute =
339 typeConverter->convertTypeAttribute(type, attr);
340 if (!convertedAttribute)
341 return failure();
342
343 return convertedAttribute.value();
344}
345
346// Rejects cast rewrites that would lose precision (unless aggressive mode is
347// enabled).
348template <TosaNarrowKind Kind>
349LogicalResult
350verifyCastDoesNotLosePrecision(Operation *op, ShapedType inputType,
351 ShapedType resultType,
352 ConversionPatternRewriter &rewriter) {
353 if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
354 const auto elementInputIntType =
355 dyn_cast<IntegerType>(inputType.getElementType());
356 const auto elementResultIntType =
357 dyn_cast<IntegerType>(resultType.getElementType());
358 if (elementInputIntType && elementResultIntType &&
359 elementInputIntType.getWidth() > elementResultIntType.getWidth())
360 return rewriter.notifyMatchFailure(
361 op, "Narrowing cast may lead to data loss.");
362 } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
363 Kind == TosaNarrowKind::Float32ToFloat16) {
364 const auto elementInputFloatType =
365 dyn_cast<FloatType>(inputType.getElementType());
366 const auto elementResultFloatType =
367 dyn_cast<FloatType>(resultType.getElementType());
368 if (elementInputFloatType && elementResultFloatType &&
369 elementInputFloatType.getIntOrFloatBitWidth() >
370 elementResultFloatType.getIntOrFloatBitWidth())
371 return rewriter.notifyMatchFailure(
372 op, "Narrowing cast may lead to data loss.");
373 }
374
375 return success();
376}
377
378// ---------------------------------------------------------------------------
379// Conversion patterns
380// ---------------------------------------------------------------------------
381
382// Applies the narrowing TypeConverter to a single TOSA op, including its
383// attributes and nested regions.
384template <TosaNarrowKind Kind>
385LogicalResult convertGenericOp(Operation *op, ValueRange operands,
386 ConversionPatternRewriter &rewriter,
387 const TypeConverter *typeConverter,
388 bool allowLossyConversion,
389 bool convertAccumulatorType = false) {
390 SmallVector<Type, 4> newResults;
391 if (failed(typeConverter->convertTypes(op->getResultTypes(), newResults)))
392 return failure();
393
394 OperationState state(op->getLoc(), op->getName().getStringRef(), operands,
395 newResults, {}, op->getSuccessors());
396
397 // Keep attribute payloads consistent with the converted element types.
398 NamedAttrList sourceAttrs(op->getDiscardableAttrDictionary().getValue());
399 op->getName().walkInherentAttrs(op, [&](StringRef name, Attribute &attr) {
400 sourceAttrs.append(name, attr);
401 });
402 for (const NamedAttribute &namedAttribute : sourceAttrs) {
403 const Attribute attribute = namedAttribute.getValue();
404
405 if (isa<IntegerAttr>(attribute) || isa<FloatAttr>(attribute)) {
406 FailureOr<Attribute> convertedAttr =
407 tryConvertScalarAttribute<Kind>(attribute, allowLossyConversion);
408 if (failed(convertedAttr))
409 return rewriter.notifyMatchFailure(
410 op, "Scalar attribute narrowing would lose precision; enable "
411 "aggressive rewrite to override.");
412 state.addAttribute(namedAttribute.getName(), convertedAttr.value());
413 continue;
414 }
415
416 if (const auto typeAttr = dyn_cast<TypeAttr>(attribute)) {
417 if (!convertAccumulatorType &&
418 namedAttribute.getName().getValue() == "acc_type") {
419 state.addAttribute(namedAttribute.getName(), attribute);
420 continue;
421 }
422 if (!typeNeedsConversion<Kind>(typeAttr.getValue())) {
423 state.addAttribute(namedAttribute.getName(), attribute);
424 continue;
425 }
426 Type convertedType = typeConverter->convertType(typeAttr.getValue());
427 if (!convertedType)
428 return rewriter.notifyMatchFailure(op,
429 "Failed to convert type attribute.");
430 state.addAttribute(namedAttribute.getName(),
431 TypeAttr::get(convertedType));
432 continue;
433 }
434
435 if (const auto denseElementsAttr = dyn_cast<DenseElementsAttr>(attribute)) {
436 FailureOr<Attribute> convertedAttr =
437 convertAttributeWithTypeConverter<Kind>(
438 denseElementsAttr, denseElementsAttr.getType(), typeConverter);
439 if (failed(convertedAttr))
440 return rewriter.notifyMatchFailure(
441 op, "Failed to convert dense elements attribute without precision "
442 "loss; enable aggressive rewrite to override.");
443 state.addAttribute(namedAttribute.getName(), convertedAttr.value());
444 continue;
445 }
446
447 if (const auto denseResourceElementsAttr =
448 dyn_cast<DenseResourceElementsAttr>(attribute)) {
449 FailureOr<Attribute> convertedAttr =
450 convertAttributeWithTypeConverter<Kind>(
451 denseResourceElementsAttr, denseResourceElementsAttr.getType(),
452 typeConverter);
453 if (failed(convertedAttr))
454 return rewriter.notifyMatchFailure(
455 op, "Failed to convert dense resource elements attribute without "
456 "precision loss; enable aggressive rewrite to override.");
457 state.addAttribute(namedAttribute.getName(), convertedAttr.value());
458 continue;
459 }
460
461 state.addAttribute(namedAttribute.getName(), attribute);
462 }
463
464 for (Region &region : op->getRegions()) {
465 if (failed(rewriter.convertRegionTypes(&region, *typeConverter)))
466 return failure();
467 Region *newRegion = state.addRegion();
468 rewriter.inlineRegionBefore(region, *newRegion, newRegion->begin());
469 }
470
471 Operation *newOp = rewriter.create(state);
472 rewriter.replaceOp(op, newOp->getResults());
473 return success();
474}
475
476template <TosaNarrowKind Kind>
477class ConvertGenericOp : public ConversionPattern {
478public:
479 ConvertGenericOp(TypeConverter &typeConverter, MLIRContext *context,
480 bool allowLossyConversion, bool convertAccumulatorType)
481 : ConversionPattern(typeConverter, MatchAnyOpTypeTag{}, 0, context),
482 allowLossyConversion(allowLossyConversion),
483 convertAccumulatorType(convertAccumulatorType) {}
484
485 LogicalResult
486 matchAndRewrite(Operation *op, ArrayRef<Value> operands,
487 ConversionPatternRewriter &rewriter) const final {
488 if (!isa<tosa::TosaOp>(op))
489 return rewriter.notifyMatchFailure(
490 op,
491 "Support for operations other than TOSA has not been implemented.");
492
493 return convertGenericOp<Kind>(op, operands, rewriter, typeConverter,
494 allowLossyConversion, convertAccumulatorType);
495 }
496
497private:
498 const bool allowLossyConversion;
499 const bool convertAccumulatorType;
500};
501
502template <TosaNarrowKind Kind>
503class ConvertAccumulatorTypeOp : public ConversionPattern {
504public:
505 ConvertAccumulatorTypeOp(TypeConverter &typeConverter, MLIRContext *context)
506 : ConversionPattern(typeConverter, MatchAnyOpTypeTag{}, 1, context) {}
507
508 LogicalResult
509 matchAndRewrite(Operation *op, ArrayRef<Value> /*operands*/,
510 ConversionPatternRewriter &rewriter) const final {
511 if (!isa<tosa::TosaOp>(op))
512 return failure();
513
514 const auto accumulatorType = op->getAttrOfType<TypeAttr>("acc_type");
515 if (!accumulatorType ||
516 !typeNeedsConversion<Kind>(accumulatorType.getValue()))
517 return failure();
518
519 Type convertedType = typeConverter->convertType(accumulatorType.getValue());
520 if (!convertedType)
521 return failure();
522
523 rewriter.modifyOpInPlace(
524 op, [&] { op->setAttr("acc_type", TypeAttr::get(convertedType)); });
525 return success();
526 }
527};
528
529template <typename OpTy, TosaNarrowKind Kind>
530class ConvertTypedOp : public OpConversionPattern<OpTy> {
531public:
532 ConvertTypedOp(TypeConverter &typeConverter, MLIRContext *context)
533 : OpConversionPattern<OpTy>(typeConverter, context) {}
534
535 LogicalResult
536 matchAndRewrite(OpTy op, typename OpTy::Adaptor adaptor,
537 ConversionPatternRewriter &rewriter) const final {
538 return convertGenericOp<Kind>(op, adaptor.getOperands(), rewriter,
539 this->getTypeConverter(),
540 /*allowLossyConversion=*/false);
541 }
542};
543
544// ---------------------------------------------------------------------------
545// Kind-specific helpers and patterns
546// ---------------------------------------------------------------------------
547
548// Casts get extra checking so we only narrow when it is probably safe.
549template <TosaNarrowKind Kind>
550class ConvertCastOpWithBoundsChecking
551 : public OpConversionPattern<tosa::CastOp> {
552 using OpConversionPattern<tosa::CastOp>::OpConversionPattern;
553
554 LogicalResult
555 matchAndRewrite(tosa::CastOp op, typename tosa::CastOp::Adaptor adaptor,
556 ConversionPatternRewriter &rewriter) const final {
557 const auto inputType = dyn_cast<ShapedType>(adaptor.getInput().getType());
558 const auto resultType = dyn_cast<ShapedType>(op.getResult().getType());
559 if (!inputType || !resultType)
560 return failure();
561
562 const TypeConverter *typeConverter = this->getTypeConverter();
563 if (failed(verifyCastDoesNotLosePrecision<Kind>(op, inputType, resultType,
564 rewriter)))
565 return failure();
566
567 rewriter.replaceOpWithNewOp<tosa::CastOp>(
568 op, TypeRange{typeConverter->convertType(resultType)},
569 ValueRange{adaptor.getInput()}, op.getProperties(),
570 op->getDiscardableAttrDictionary().getValue());
571 return success();
572 }
573};
574
575// ArgMax indices must fit the axis dimension, so we guard the integer rewrite.
576class ConvertArgMaxOpWithBoundsChecking
577 : public OpConversionPattern<tosa::ArgMaxOp> {
578 using OpConversionPattern::OpConversionPattern;
579
580 LogicalResult
581 matchAndRewrite(tosa::ArgMaxOp op, typename tosa::ArgMaxOp::Adaptor adaptor,
582 ConversionPatternRewriter &rewriter) const final {
583 const int32_t axis = op.getAxis();
584 const auto inputType = dyn_cast<ShapedType>(adaptor.getInput().getType());
585 if (!inputType || !inputType.isStaticDim(axis))
586 return rewriter.notifyMatchFailure(
587 op, "Requires a static axis dimension for bounds checking.");
588 const int64_t axisDim = inputType.getDimSize(axis);
589 if (axisDim >= std::numeric_limits<int32_t>::max())
590 return rewriter.notifyMatchFailure(
591 op, "Axis dimension is too large to narrow safely.");
592
593 const Type resultType = op.getOutput().getType();
594 const Type newResultType =
595 this->getTypeConverter()->convertType(resultType);
596 rewriter.replaceOpWithNewOp<tosa::ArgMaxOp>(op, newResultType,
597 adaptor.getInput(), axis);
598 return success();
599 }
600};
601
602template <TosaNarrowKind Kind>
603class ConvertClampOpWithBoundsChecking
604 : public OpConversionPattern<tosa::ClampOp> {
605 static_assert(Kind == TosaNarrowKind::Int64ToInt32,
606 "Clamp bounds checking only supported for integer narrowing");
607 using OpConversionPattern<tosa::ClampOp>::OpConversionPattern;
608
609 LogicalResult
610 matchAndRewrite(tosa::ClampOp op, typename tosa::ClampOp::Adaptor adaptor,
611 ConversionPatternRewriter &rewriter) const final {
612 auto minAttr = dyn_cast<IntegerAttr>(op.getMinValAttr());
613 auto maxAttr = dyn_cast<IntegerAttr>(op.getMaxValAttr());
614 if (!minAttr || !maxAttr)
615 return rewriter.notifyMatchFailure(
616 op, "Clamp attributes must be integer constants.");
617
618 const int64_t min = minAttr.getInt();
619 const int64_t max = maxAttr.getInt();
620 if (min < std::numeric_limits<int32_t>::min() ||
621 max > std::numeric_limits<int32_t>::max())
622 return rewriter.notifyMatchFailure(
623 op, "Clamp bounds exceed int32 range. Narrowing may lose data.");
624
625 const Type resultType = op.getOutput().getType();
626 const Type newResultType =
627 this->getTypeConverter()->convertType(resultType);
628 const auto newResultShaped = dyn_cast<ShapedType>(newResultType);
629 if (!newResultShaped)
630 return failure();
631 const auto newElementType =
632 dyn_cast<IntegerType>(newResultShaped.getElementType());
633 if (!newElementType)
634 return failure();
635
636 const IntegerAttr newMinAttr = IntegerAttr::get(newElementType, min);
637 const IntegerAttr newMaxAttr = IntegerAttr::get(newElementType, max);
638
639 rewriter.replaceOpWithNewOp<tosa::ClampOp>(op, newResultType,
640 adaptor.getInput(), newMinAttr,
641 newMaxAttr, op.getNanModeAttr());
642 return success();
643 }
644};
645
646// Shared implementation for the narrowing passes; the mode decides which
647// element types and attribute payloads participate.
648template <TosaNarrowKind Kind>
649LogicalResult runTosaNarrowing(Operation *op, bool aggressiveRewrite,
650 bool convertFunctionBoundaries,
651 bool convertAccumulatorType = false) {
652 MLIRContext *context = op->getContext();
653 const bool allowLossyConversion = aggressiveRewrite;
654
655 TypeConverter typeConverter;
656 typeConverter.addConversion([](Type type) -> Type { return type; });
657
658 typeConverter.addConversion(
659 [](IntegerType type) -> Type { return convertInteger<Kind>(type); });
660 typeConverter.addConversion(
661 [](FloatType type) -> Type { return convertFloat<Kind>(type); });
662 typeConverter.addConversion([&typeConverter](RankedTensorType type) -> Type {
663 Type elementType = type.getElementType();
664 if (!isSourceElement<Kind>(elementType))
665 return type;
666 Type converted = typeConverter.convertType(elementType);
667 if (!converted || converted == elementType)
668 return type;
669 return RankedTensorType::get(type.getShape(), converted,
670 type.getEncoding());
671 });
672 typeConverter.addConversion(
673 [&typeConverter](UnrankedTensorType type) -> Type {
674 Type elementType = type.getElementType();
675 if (!isSourceElement<Kind>(elementType))
676 return type;
677 Type converted = typeConverter.convertType(elementType);
678 if (!converted || converted == elementType)
679 return type;
680 return UnrankedTensorType::get(converted);
681 });
682
683 const auto materializeCast = [](OpBuilder &builder, Type resultType,
684 ValueRange inputs, Location loc) -> Value {
685 if (inputs.size() != 1)
686 return Value();
687 return tosa::CastOp::create(
688 builder, loc, resultType, inputs.front(),
689 getStorageElementTypeOrSelf(inputs.front().getType())
690 .isUnsignedInteger());
691 };
692 typeConverter.addSourceMaterialization(materializeCast);
693 typeConverter.addTargetMaterialization(materializeCast);
694
695 typeConverter.addTypeAttributeConversion(
696 [&typeConverter, allowLossyConversion](ShapedType type,
697 DenseResourceElementsAttr attr)
698 -> TypeConverter::AttributeConversionResult {
699 FailureOr<Attribute> converted = convertDenseResourceElementsAttr<Kind>(
700 type, attr, typeConverter, allowLossyConversion);
701 if (failed(converted))
702 return TypeConverter::AttributeConversionResult::abort();
703 return TypeConverter::AttributeConversionResult::result(
704 converted.value());
705 });
706
707 if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
708 typeConverter.addTypeAttributeConversion(
709 [allowLossyConversion](IntegerType /*type*/, IntegerAttr attribute)
710 -> TypeConverter::AttributeConversionResult {
711 FailureOr<Attribute> converted =
712 tryConvertScalarAttribute<Kind>(attribute, allowLossyConversion);
713 if (failed(converted))
714 return TypeConverter::AttributeConversionResult::abort();
715 return TypeConverter::AttributeConversionResult::result(
716 converted.value());
717 });
718 typeConverter.addTypeAttributeConversion(
719 [&typeConverter, allowLossyConversion](ShapedType type,
721 -> TypeConverter::AttributeConversionResult {
722 FailureOr<Attribute> converted = convertDenseIntElementsAttr<Kind>(
723 type, attr, typeConverter, allowLossyConversion);
724 if (failed(converted))
725 return TypeConverter::AttributeConversionResult::abort();
726 return TypeConverter::AttributeConversionResult::result(
727 converted.value());
728 });
729 } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
730 Kind == TosaNarrowKind::Float32ToFloat16) {
731 typeConverter.addTypeAttributeConversion(
732 [allowLossyConversion](FloatType /*type*/, FloatAttr attribute)
733 -> TypeConverter::AttributeConversionResult {
734 FailureOr<Attribute> converted =
735 tryConvertScalarAttribute<Kind>(attribute, allowLossyConversion);
736 if (failed(converted))
737 return TypeConverter::AttributeConversionResult::abort();
738 return TypeConverter::AttributeConversionResult::result(
739 converted.value());
740 });
741 typeConverter.addTypeAttributeConversion(
742 [&typeConverter, allowLossyConversion](ShapedType type,
744 -> TypeConverter::AttributeConversionResult {
745 FailureOr<Attribute> converted = convertDenseFPElementsAttr<Kind>(
746 type, attr, typeConverter, allowLossyConversion);
747 if (failed(converted))
748 return TypeConverter::AttributeConversionResult::abort();
749 return TypeConverter::AttributeConversionResult::result(
750 converted.value());
751 });
752 }
753
754 ConversionTarget target(*context);
755 target.addDynamicallyLegalDialect<tosa::TosaDialect>(
756 [&typeConverter, convertAccumulatorType](Operation *op) {
757 if (!typeConverter.isLegal(op->getResultTypes()) ||
758 !typeConverter.isLegal(op->getOperandTypes()))
759 return false;
760 if (!convertAccumulatorType)
761 return true;
762 const auto accumulatorType = op->getAttrOfType<TypeAttr>("acc_type");
763 return !accumulatorType ||
764 !typeNeedsConversion<Kind>(accumulatorType.getValue());
765 });
766 if (convertFunctionBoundaries) {
767 target.addDynamicallyLegalOp<func::FuncOp>(
768 [&typeConverter](func::FuncOp op) {
769 return typeConverter.isSignatureLegal(op.getFunctionType()) &&
770 typeConverter.isLegal(&op.getBody());
771 });
772 target.addDynamicallyLegalOp<func::ReturnOp>([](func::ReturnOp op) {
773 const FunctionType funcType =
774 op->getParentOfType<func::FuncOp>().getFunctionType();
775 return llvm::equal(op.getOperandTypes(), funcType.getResults());
776 });
777 } else {
778 target.addDynamicallyLegalOp<func::FuncOp>(
779 [](func::FuncOp) { return true; });
780 target.addDynamicallyLegalOp<func::ReturnOp>(
781 [](func::ReturnOp) { return true; });
782 }
783
784 RewritePatternSet patterns(context);
785 if (convertFunctionBoundaries) {
786 populateFunctionOpInterfaceTypeConversionPattern<func::FuncOp>(
787 patterns, typeConverter);
788 populateReturnOpTypeConversionPattern(patterns, typeConverter);
789 }
790 if (convertAccumulatorType && !aggressiveRewrite)
791 patterns.add<ConvertAccumulatorTypeOp<Kind>>(typeConverter, context);
792 if (aggressiveRewrite) {
793 patterns.add<ConvertGenericOp<Kind>>(
794 typeConverter, context, allowLossyConversion, convertAccumulatorType);
795 } else {
796 if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
797 patterns.add<ConvertArgMaxOpWithBoundsChecking>(typeConverter, context);
798 patterns.add<ConvertClampOpWithBoundsChecking<Kind>>(typeConverter,
799 context);
800 }
801 patterns.add<ConvertTypedOp<tosa::ConstOp, Kind>>(typeConverter, context);
802 patterns.add<ConvertTypedOp<tosa::ConcatOp, Kind>>(typeConverter, context);
803 patterns.add<ConvertTypedOp<tosa::PadOp, Kind>>(typeConverter, context);
804 patterns.add<ConvertTypedOp<tosa::ReshapeOp, Kind>>(typeConverter, context);
805 patterns.add<ConvertTypedOp<tosa::ReverseOp, Kind>>(typeConverter, context);
806 patterns.add<ConvertTypedOp<tosa::SliceOp, Kind>>(typeConverter, context);
807 patterns.add<ConvertTypedOp<tosa::TileOp, Kind>>(typeConverter, context);
808 patterns.add<ConvertTypedOp<tosa::TransposeOp, Kind>>(typeConverter,
809 context);
810 patterns.add<ConvertTypedOp<tosa::IdentityOp, Kind>>(typeConverter,
811 context);
812 patterns.add<ConvertCastOpWithBoundsChecking<Kind>>(typeConverter, context);
813 patterns.add<ConvertTypedOp<tosa::IfOp, Kind>>(typeConverter, context);
814 patterns.add<ConvertTypedOp<tosa::WhileOp, Kind>>(typeConverter, context);
815 patterns.add<ConvertTypedOp<tosa::YieldOp, Kind>>(typeConverter, context);
816 }
818 if (failed(applyFullConversion(op, target, std::move(patterns))))
819 return failure();
820 return success();
821}
823// ---------------------------------------------------------------------------
824// Pass adapters that forward to the shared implementation
825// ---------------------------------------------------------------------------
826
827struct TosaNarrowI64ToI32
831 TosaNarrowI64ToI32() = default;
832
833 explicit TosaNarrowI64ToI32(const TosaNarrowI64ToI32PassOptions &options) {
834 this->aggressiveRewrite = options.aggressiveRewrite;
835 this->convertFunctionBoundaries = options.convertFunctionBoundaries;
836 }
837
838 void runOnOperation() override {
839 if (failed(runTosaNarrowing<TosaNarrowKind::Int64ToInt32>(
840 getOperation(), this->aggressiveRewrite,
841 this->convertFunctionBoundaries)))
843 }
844};
845
846struct TosaNarrowF64ToF32
847 : public tosa::impl::TosaNarrowF64ToF32PassBase<TosaNarrowF64ToF32> {
849
850 TosaNarrowF64ToF32() = default;
851
852 explicit TosaNarrowF64ToF32(const TosaNarrowF64ToF32PassOptions &options) {
853 this->aggressiveRewrite = options.aggressiveRewrite;
854 this->convertFunctionBoundaries = options.convertFunctionBoundaries;
856
857 void runOnOperation() override {
858 if (failed(runTosaNarrowing<TosaNarrowKind::Float64ToFloat32>(
859 getOperation(), this->aggressiveRewrite,
860 this->convertFunctionBoundaries)))
863};
864
865struct TosaNarrowF32ToF16
866 : public tosa::impl::TosaNarrowF32ToF16PassBase<TosaNarrowF32ToF16> {
867 TosaNarrowF32ToF16() = default;
869 explicit TosaNarrowF32ToF16(const TosaNarrowF32ToF16PassOptions &options) {
870 this->aggressiveRewrite = options.aggressiveRewrite;
871 this->convertFunctionBoundaries = options.convertFunctionBoundaries;
872 this->convertAccumulatorType = options.convertAccumulatorType;
873 }
874
875 void runOnOperation() override {
876 if (failed(runTosaNarrowing<TosaNarrowKind::Float32ToFloat16>(
877 getOperation(), this->aggressiveRewrite,
878 this->convertFunctionBoundaries, this->convertAccumulatorType)))
879 signalPassFailure();
880 }
881};
882
883} // namespace
return success()
static llvm::Constant * convertDenseResourceElementsAttr(Location loc, DenseResourceElementsAttr denseResourceAttr, llvm::Type *llvmType, const ModuleTranslation &moduleTranslation)
Convert a dense resource elements attribute to an LLVM IR constant using its raw data storage if poss...
static llvm::ManagedStatic< PassManagerOptions > options
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
This class represents a processed binary blob of data.
Definition AsmState.h:91
Attributes are known-constant values of operations.
Definition Attributes.h:25
An attribute that represents a reference to a dense float vector or tensor object.
DenseElementsAttr mapValues(Type newElementType, function_ref< APInt(const APFloat &)> mapping) const
Generates a new DenseElementsAttr by mapping each value attribute, and constructing the DenseElements...
An attribute that represents a reference to a dense integer vector or tensor object.
DenseElementsAttr mapValues(Type newElementType, function_ref< APInt(const APInt &)> mapping) const
Generates a new DenseElementsAttr by mapping each value attribute, and constructing the DenseElements...
static AsmResourceBlob allocateAndCopyInferAlign(ArrayRef< T > data, bool dataIsMutable=true)
Definition AsmState.h:212
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
This class helps build Operations.
Definition Builders.h:210
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
void walkInherentAttrs(Operation *op, InherentAttrVisitor visitor) const
Visit the inherent attributes stored in the properties of op.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
AttrClass getAttrOfType(StringAttr name)
Definition Operation.h:602
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
void setAttr(StringAttr name, Attribute value)
If the an attribute exists with the specified name, change it to the new value.
Definition Operation.h:634
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
operand_type_range getOperandTypes()
Definition Operation.h:422
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_type_range getResultTypes()
Definition Operation.h:453
SuccessorRange getSuccessors()
Definition Operation.h:755
result_range getResults()
Definition Operation.h:440
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
virtual void runOnOperation()=0
The polymorphic API that runs the pass over the currently held operation.
void signalPassFailure()
Signal that some invariant was broken when running.
Definition Pass.h:226
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
iterator begin()
Definition Region.h:55
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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
Kind
An enumeration of the kinds of predicates.
Definition Predicate.h:44
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
Type getStorageElementTypeOrSelf(Type type)
Definition TosaOps.cpp:587
std::optional< ArrayRef< T > > tryGetDenseResourceValues(ElementsAttr attr)
Include the generated interface declarations.
void populateReturnOpTypeConversionPattern(RewritePatternSet &patterns, const TypeConverter &converter, PatternBenefit benefit=1)
Add a pattern to the given pattern list to rewrite return ops to use operands that have been legalize...
static ManagerInterface & getManagerInterface(MLIRContext *ctx)
This represents an operation in an abstracted form, suitable for use with the builder APIs.