30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/StringExtras.h"
32#include "llvm/ADT/TypeSwitch.h"
33#include "llvm/Support/FormatVariadic.h"
37#define GEN_PASS_DEF_TOSAVALIDATION
38#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
49 for (
const auto index : operandIndices) {
52 return op->
emitOpError(
"expected compile time resolvable constant, but "
53 "got variable value for operand #")
60static LogicalResult checkConstantOperandMul(
Operation *op,
62 if (!env.
allows(Extension::dynamic) && isa<tosa::MulOp>(op)) {
64 return checkConstantOperands(op, {2});
69static LogicalResult checkConstantOperandTable(
Operation *op,
71 if (!env.
allows(Extension::dynamic) && isa<tosa::TableOp>(op)) {
73 return checkConstantOperands(op, {1});
78static LogicalResult checkConstantOperandPad(
Operation *op,
80 if (
auto padOp = dyn_cast<tosa::PadOp>(op)) {
82 if (!env.
allows(Extension::dynamic) && padOp.getPadConst())
85 return checkConstantOperands(op, {2});
90static LogicalResult checkConstantOperandRescale(
Operation *op,
92 if (!env.
allows(Extension::dynamic) && isa<tosa::RescaleOp>(op)) {
94 return checkConstantOperands(op, {1, 2, 3, 4});
100static LogicalResult checkConstantOperandConvOps(
Operation *op,
102 if (!env.
allows(Extension::dynamic) && isa<T>(op)) {
104 return checkConstantOperands(op, {3, 4});
109static LogicalResult checkConstantOperandMatMul(
Operation *op,
111 if (!env.
allows(Extension::dynamic) &&
112 isa<tosa::MatMulOp, tosa::MatMulTOp>(op)) {
114 return checkConstantOperands(op, {2, 3});
121 if (!env.
allows(Extension::dynamic) &&
122 isa<tosa::RowGatherBlockScaledOp>(op)) {
123 auto rowGatherOp = cast<tosa::RowGatherBlockScaledOp>(op);
124 const unsigned rowCountIndex = rowGatherOp.getValues().size() + 1;
125 return checkConstantOperands(op, {rowCountIndex});
130static LogicalResult checkConstantOperandRowGather(
Operation *op,
132 if (!env.
allows(Extension::dynamic) && isa<tosa::RowGatherOp>(op)) {
134 return checkConstantOperands(op, {2});
139static LogicalResult checkConstantOperandAvgPool2d(
Operation *op,
141 if (!env.
allows(Extension::dynamic) && isa<tosa::AvgPool2dOp>(op)) {
143 return checkConstantOperands(op, {1, 2});
150 if (!env.
allows(Extension::dynamic) && isa<tosa::AvgPool2dAdaptiveOp>(op)) {
154 return checkConstantOperands(op, {1, 2});
159static LogicalResult checkConstantOperandNegate(
Operation *op,
161 if (!env.
allows(Extension::dynamic) && isa<tosa::NegateOp>(op)) {
163 return checkConstantOperands(op, {1, 2});
168static LogicalResult checkConstantOperandSilceShape(
Operation *op,
170 if (!env.
allows(Extension::dynamic) && isa<tosa::SliceShapeOp>(op)) {
172 return checkConstantOperands(op, {1, 2});
183 explicit TosaValidation() { populateConstantOperandChecks(); }
185 explicit TosaValidation(
const TosaValidationOptions &
options)
187 this->strictOpSpecAlignment =
options.strictOpSpecAlignment;
188 this->allowInvalidOpDatatypeCombinations =
189 options.allowInvalidOpDatatypeCombinations;
190 this->validateFunctionSignature =
options.validateFunctionSignature;
192 void runOnOperation() final;
194 LogicalResult applyConstantOperandCheck(Operation *op) {
195 for (
auto &checker : constCheckers) {
196 if (
failed(checker(op, targetEnv)))
202 LogicalResult applyFunctionSignatureCheck(func::FuncOp op);
203 LogicalResult applyLevelCheck(Operation *op);
204 LogicalResult applyAttributeCheck(Operation *op);
207 LogicalResult applyVariableCheck(Operation *op);
210 LogicalResult applyErrorIfCheck(Operation *op);
213 void populateConstantOperandChecks() {
214 constCheckers.emplace_back(checkConstantOperandMul);
215 constCheckers.emplace_back(checkConstantOperandTable);
216 constCheckers.emplace_back(checkConstantOperandPad);
217 constCheckers.emplace_back(checkConstantOperandRescale);
218 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv2DOp>);
219 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv3DOp>);
220 constCheckers.emplace_back(
221 checkConstantOperandConvOps<tosa::DepthwiseConv2DOp>);
222 constCheckers.emplace_back(
223 checkConstantOperandConvOps<tosa::TransposeConv2DOp>);
224 constCheckers.emplace_back(checkConstantOperandMatMul);
225 constCheckers.emplace_back(checkConstantOperandRowGather);
226 constCheckers.emplace_back(checkConstantOperandRowGatherBlockScaled);
227 constCheckers.emplace_back(checkConstantOperandAvgPool2d);
228 constCheckers.emplace_back(checkConstantOperandAvgPool2dAdaptive);
229 constCheckers.emplace_back(checkConstantOperandNegate);
230 constCheckers.emplace_back(checkConstantOperandSilceShape);
233 LogicalResult levelCheck(Operation *op,
const int32_t calculatedValue,
234 const int32_t maxLevel,
const StringRef inputName,
235 const StringRef levelName) {
236 if (calculatedValue > maxLevel)
238 <<
"failed level check: " << inputName <<
" <= " << levelName
239 <<
" (" << maxLevel <<
"), got " << calculatedValue;
243 LogicalResult levelCheckKernel(Operation *op, int32_t v,
244 const StringRef inputName) {
245 return levelCheck(op, v, targetEnv.getLevel().MAX_KERNEL, inputName,
249 LogicalResult levelCheckStride(Operation *op, int32_t v,
250 const StringRef inputName) {
251 return levelCheck(op, v, targetEnv.getLevel().MAX_STRIDE, inputName,
255 LogicalResult levelCheckScale(Operation *op, int32_t v,
256 const StringRef inputName) {
257 return levelCheck(op, v, targetEnv.getLevel().MAX_SCALE, inputName,
261 LogicalResult levelCheckListSize(Operation *op, int32_t v,
262 const StringRef inputName) {
263 const std::string inputDesc =
264 llvm::formatv(
"length(tensor_list_shape({0}))", inputName);
265 return levelCheck(op, v, targetEnv.getLevel().MAX_TENSOR_LIST_SIZE,
266 inputDesc,
"MAX_TENSOR_LIST_SIZE");
270 LogicalResult levelCheckRank(Operation *op,
const Type typeToCheck,
271 const StringRef operandOrResult,
272 int32_t highest_rank) {
273 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
275 return op->
emitOpError() <<
"failed level check: unranked tensor";
276 if (type.getRank() > highest_rank)
277 return op->
emitOpError() <<
"failed level check: " << operandOrResult
278 <<
" rank(shape) <= MAX_RANK";
284 LogicalResult levelCheckRank(Operation *op,
const Value &v,
285 const StringRef operandOrResult,
286 int32_t highest_rank) {
287 return levelCheckRank(op, v.
getType(), operandOrResult, highest_rank);
291 LogicalResult levelCheckSize(Operation *op,
const Type &typeToCheck,
292 const StringRef operandOrResult);
295 LogicalResult levelCheckSize(Operation *op,
const Value &v,
296 const StringRef operandOrResult) {
297 return levelCheckSize(op, v.
getType(), operandOrResult);
301 LogicalResult levelCheckShapeLength(Operation *op,
const Type typeToCheck,
302 const StringRef operandOrResult) {
303 if (tosa::shapeType shapeType = dyn_cast<tosa::shapeType>(typeToCheck)) {
304 if (shapeType.getRank() > targetEnv.getLevel().MAX_SHAPE_LEN)
306 <<
"failed shape type level check: " << typeToCheck
307 <<
" exceeds MAX_SHAPE_LEN";
313 template <
typename T>
314 LogicalResult levelCheckSizes(T tosaOp) {
315 auto op = tosaOp.getOperation();
317 if (
failed(levelCheckSize(op, v,
"operand")))
322 if (
failed(levelCheckSize(op, v,
"result")))
329 template <
typename T>
330 LogicalResult levelCheckRanks(T tosaOp) {
331 auto op = tosaOp.getOperation();
332 const TosaLevel tosaLevel = targetEnv.getLevel();
346 template <
typename T>
347 LogicalResult levelCheckShapeLengths(T tosaOp) {
348 for (
const auto &v : tosaOp->getOperands()) {
349 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"operand")))
352 for (
const auto &v : tosaOp->getResults()) {
353 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"result")))
361 LogicalResult levelCheckRanksAndSizes(Operation *op);
364 template <
typename T>
365 LogicalResult levelCheckPool(Operation *op) {
366 if (
auto poolOp = dyn_cast<T>(op)) {
367 for (
auto k : poolOp.getKernel()) {
368 if (
failed(levelCheckKernel(op, k,
"kernel"))) {
372 for (
auto s : poolOp.getStride()) {
373 if (
failed(levelCheckStride(op, s,
"stride"))) {
377 for (
auto p : poolOp.getPad()) {
378 if (
failed(levelCheckKernel(op, p,
"pad"))) {
386 template <
typename T>
387 static constexpr bool IsSupportedAdaptivePoolOp =
388 std::is_same_v<T, tosa::AvgPool2dAdaptiveOp> ||
389 std::is_same_v<T, tosa::MaxPool2dAdaptiveOp>;
391 template <
typename T,
typename std::enable_if<IsSupportedAdaptivePoolOp<T>,
393 LogicalResult levelCheckAdaptivePool(Operation *op) {
394 auto poolOp = dyn_cast<T>(op);
398 SmallVector<int64_t> kernelValues;
401 for (
const auto k : kernelValues)
402 if (
failed(levelCheckKernel(op, k,
"kernel")))
406 SmallVector<int64_t> strideValues;
409 for (
const auto s : strideValues)
410 if (
failed(levelCheckStride(op, s,
"stride")))
414 SmallVector<int64_t> padValues;
416 for (
const auto p : padValues)
417 if (
failed(levelCheckKernel(op, p,
"pad")))
425 template <
typename T>
426 LogicalResult levelCheckConv(Operation *op) {
427 if (
auto convOp = dyn_cast<T>(op)) {
429 for (
auto k : convOp.getDilation()) {
430 if (
failed(levelCheckKernel(op, k,
"dilation"))) {
434 for (
auto p : convOp.getPad()) {
435 if (
failed(levelCheckKernel(op, p,
"pad"))) {
439 for (
auto s : convOp.getStride()) {
440 if (
failed(levelCheckStride(op, s,
"stride"))) {
444 auto dilation = convOp.getDilation();
445 if (ShapedType weightType =
447 auto shape = weightType.getShape();
448 if (isa<tosa::Conv2DOp>(op)) {
449 assert(shape.size() == 4);
450 assert(dilation.size() == 2);
451 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
452 "dilation_y * KH")) ||
453 failed(levelCheckKernel(op, dilation[1] * shape[2],
456 }
else if (isa<tosa::Conv3DOp>(op)) {
457 assert(shape.size() == 5);
458 assert(dilation.size() == 3);
459 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
460 "dilation_d * KD")) ||
461 failed(levelCheckKernel(op, dilation[1] * shape[2],
462 "dilation_y * KH")) ||
463 failed(levelCheckKernel(op, dilation[2] * shape[3],
466 }
else if (isa<tosa::DepthwiseConv2DOp>(op)) {
467 assert(shape.size() == 4);
468 assert(dilation.size() == 2);
469 if (
failed(levelCheckKernel(op, dilation[0] * shape[0],
470 "dilation_y * KH")) ||
471 failed(levelCheckKernel(op, dilation[1] * shape[1],
480 LogicalResult levelCheckConv2DBlockScaled(Operation *op) {
481 auto convOp = dyn_cast<Conv2DBlockScaledOp>(op);
485 SmallVector<int64_t> padValues;
487 for (
const auto p : padValues)
488 if (
failed(levelCheckKernel(op, p,
"pad <= MAX_KERNEL")))
492 SmallVector<int64_t> strideValues;
495 for (
const auto s : strideValues)
496 if (
failed(levelCheckKernel(op, s,
"stride <= MAX_KERNEL")))
500 SmallVector<int64_t> dilationValues;
503 int64_t KH = ShapedType::kDynamic;
504 int64_t KW = ShapedType::kDynamic;
505 const ShapeAdaptor weightDataShape(convOp.getWeightData().getType());
506 KH = weightDataShape.getDimSize(1);
507 KW = weightDataShape.getDimSize(2);
508 const ShapeAdaptor weightScaleShape(convOp.getWeightScale().getType());
509 KH = ShapedType::isDynamic(KH) ? weightScaleShape.getDimSize(1) : KH;
510 KW = ShapedType::isDynamic(KW) ? weightScaleShape.getDimSize(2) : KW;
512 if (!ShapedType::isDynamic(KH) &&
513 failed(levelCheckKernel(op, dilationValues[0] * KH,
514 "dilation_y * KH <= MAX_KERNEL)")))
517 if (!ShapedType::isDynamic(KW) &&
518 failed(levelCheckKernel(op, dilationValues[1] * KW,
519 "dilation_x * KW <= MAX_KERNEL)")))
527 template <
typename T>
528 LogicalResult levelCheckFFT(Operation *op) {
531 if (ShapedType type = dyn_cast<ShapedType>(v.getType())) {
532 auto shape = type.getShape();
533 assert(shape.size() == 3);
534 if (
failed(levelCheckKernel(op, shape[1],
"H")) ||
535 failed(levelCheckKernel(op, shape[2],
"W"))) {
545 LogicalResult levelCheckTransposeConv2d(Operation *op) {
546 if (
auto transpose = dyn_cast<tosa::TransposeConv2DOp>(op)) {
547 if (ShapedType filterType =
548 dyn_cast<ShapedType>(transpose.getWeight().getType())) {
549 auto shape = filterType.getShape();
550 assert(shape.size() == 4);
552 if (
failed(levelCheckKernel(op, shape[1],
"KH")) ||
553 failed(levelCheckKernel(op, shape[2],
"KW"))) {
557 for (
auto p : transpose.getOutPad()) {
558 if (
failed(levelCheckKernel(op, p,
"pad"))) {
562 for (
auto s : transpose.getStride()) {
563 if (
failed(levelCheckStride(op, s,
"stride"))) {
572 LogicalResult levelCheckResize(Operation *op) {
573 if (
auto resize = dyn_cast<tosa::ResizeOp>(op)) {
574 SmallVector<int64_t> scale;
579 const int64_t scaleYN = scale[0];
580 const int64_t scaleYD = scale[1];
581 const int64_t scaleXN = scale[2];
582 const int64_t scaleXD = scale[3];
584 levelCheckScale(op, scaleYN / scaleYD,
"scale_y_n/scale_y_d")) ||
586 levelCheckScale(op, scaleXN / scaleXD,
"scale_x_n/scale_x_d"))) {
597 static void getMaxNestedDepth(Operation *op, int32_t &depth) {
598 if (isa<mlir::func::FuncOp>(op) || isa<ModuleOp>(op))
606 getMaxNestedDepth(op, depth);
609 LogicalResult levelCheckMaxNesting(Operation *op) {
610 int32_t maxNestedDepth = 0;
611 getMaxNestedDepth(op, maxNestedDepth);
613 const int32_t maxNestingLevel = targetEnv.getLevel().MAX_NESTING;
614 if (maxNestedDepth >= maxNestingLevel)
616 <<
"failed level check: tosa_nesting_depth < MAX_NESTING" <<
" ("
617 << maxNestingLevel <<
"), got " << maxNestedDepth;
621 LogicalResult levelCheckListSize(Operation *op) {
622 if (
auto concat = dyn_cast<tosa::ConcatOp>(op)) {
623 return levelCheckListSize(op,
concat.getInput1().size(),
"input1");
625 if (
auto custom = dyn_cast<tosa::CustomOp>(op)) {
626 if (
failed(levelCheckListSize(op, custom.getInputList().size(),
628 failed(levelCheckListSize(op, custom.getOutputList().size(),
633 if (
auto condIf = dyn_cast<tosa::IfOp>(op)) {
635 levelCheckListSize(op, condIf.getInputList().size(),
"inputs")) ||
636 failed(levelCheckListSize(op, condIf.getOutputList().size(),
641 if (
auto w = dyn_cast<tosa::WhileOp>(op)) {
642 if (
failed(levelCheckListSize(op, w.getInputList().size(),
"inputs")) ||
643 failed(levelCheckListSize(op, w.getOutputList().size(),
"outputs"))) {
647 if (
auto concat_shape = dyn_cast<tosa::ConcatShapeOp>(op))
648 return levelCheckListSize(op, concat_shape.getInput().size(),
"input");
652 LogicalResult attributeCheckRescale(Operation *op) {
653 if (
auto rescale = dyn_cast<tosa::RescaleOp>(op)) {
654 if (rescale.getRoundingMode() == RoundingMode::DOUBLE_ROUND &&
655 !targetEnv.allows(Extension::doubleround)) {
657 <<
"failed attribute check: rounding_mode = DOUBLE_ROUND "
658 <<
"requires extension [doubleround]";
661 if (rescale.getRoundingMode() == RoundingMode::INEXACT_ROUND &&
662 !targetEnv.allows(Extension::inexactround)) {
664 <<
"failed attribute check: rounding_mode = INEXACT_ROUND "
665 <<
"requires extension [inexactround]";
672 LogicalResult attributeCheckCast(Operation *op) {
673 if (
auto cast = dyn_cast<tosa::CastOp>(op)) {
674 const TosaSpecificationVersion targetVersion = targetEnv.getSpecVersion();
675 const TosaSpecificationVersion minRequiredVersion(1, 1,
true);
676 if (cast.getInputUnsigned() &&
679 <<
"failed attribute check: CAST attribute input_unsigned "
680 <<
"requires version 1.1.draft"
686 LogicalResult CheckVariable(Operation *op);
687 LogicalResult CheckVariableReadOrWrite(Operation *op);
688 LogicalResult validateValidElementType(Operation *op, Type type,
689 bool allowUnsigned =
false);
690 LogicalResult validateOperationElementTypes(TosaOp op,
691 bool allowUnsigned =
false);
692 LogicalResult validateOperationElementTypes(func::FuncOp op,
693 bool allowUnsigned =
false);
696 std::function<LogicalResult(Operation *,
const tosa::TargetEnv &)>>
699 TosaProfileCompliance profileComp;
700 tosa::TargetEnv targetEnv;
704LogicalResult TosaValidation::levelCheckRanks(tosa::ArgMaxOp tosaOp) {
705 auto *op = tosaOp.getOperation();
706 if (
failed(levelCheckRank(op, tosaOp.getInput(),
"operand",
711 if (
failed(levelCheckRank(op, tosaOp.getOutput(),
"result",
719LogicalResult TosaValidation::levelCheckRanks(tosa::IfOp tosaOp) {
720 auto *op = tosaOp.getOperation();
723 if (
failed(levelCheckRank(op, tosaOp.getCondition(),
"operand",
731LogicalResult TosaValidation::levelCheckRanks(tosa::VariableOp tosaOp) {
732 auto *op = tosaOp.getOperation();
734 if (
failed(levelCheckRank(op, variableType,
"variable type",
742LogicalResult TosaValidation::levelCheckSizes(tosa::VariableOp tosaOp) {
743 auto *op = tosaOp.getOperation();
745 if (
failed(levelCheckSize(op, variableType,
"variable type")))
751LogicalResult TosaValidation::levelCheckRanksAndSizes(Operation *op) {
752#define CHECK_RANKS_AND_SIZES(tosaOp) \
753 if (isa<tosa::tosaOp##Op>(op)) { \
754 if (failed(levelCheckRanks(cast<tosa::tosaOp##Op>(op)))) \
756 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
760#define CHECK_SIZES(tosaOp) \
761 if (isa<tosa::tosaOp##Op>(op)) { \
762 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
766#define CHECK_SHAPE_LEN(tosaOp) \
767 if (isa<tosa::tosaOp##Op>(op)) { \
768 if (failed(levelCheckShapeLengths(cast<tosa::tosaOp##Op>(op)))) \
900#undef CHECK_RANKS_AND_SIZES
902#undef CHECK_SHAPE_LEN
907LogicalResult TosaValidation::levelCheckSize(Operation *op,
908 const Type &typeToCheck,
909 const StringRef operandOrResult) {
910 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
912 return op->
emitOpError() <<
"failed level check: unranked tensor";
913 auto shape = type.getShape();
914 for (
auto dim : shape) {
915 const bool dimIsDynamic = mlir::ShapedType::isDynamic(dim);
916 const TosaSpecificationVersion targetVersion = targetEnv.
getSpecVersion();
917 const TosaSpecificationVersion minRequiredVersion(1, 1,
true);
927 return op->
emitOpError() <<
"failed level check: " << operandOrResult
928 <<
" shape dimension cannot be dynamic when"
929 <<
" targeting TOSA specification version 1.0"
934 int64_t elementBytes = std::max(INT64_C(1), elementBits / 8);
935 int64_t size = elementBytes * type.getNumElements();
942 const int64_t maxSize =
946 <<
"failed level check: " << operandOrResult
947 <<
" tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)";
952LogicalResult TosaValidation::applyLevelCheck(Operation *op) {
959 if (
failed(levelCheckRanksAndSizes(op)))
962 if (
failed(levelCheckPool<tosa::AvgPool2dOp>(op)) ||
963 failed(levelCheckAdaptivePool<tosa::AvgPool2dAdaptiveOp>(op)) ||
964 failed(levelCheckConv<tosa::Conv2DOp>(op)) ||
965 failed(levelCheckConv<tosa::Conv3DOp>(op)) ||
966 failed(levelCheckConv<tosa::DepthwiseConv2DOp>(op)) ||
967 failed(levelCheckFFT<tosa::FFT2dOp>(op)) ||
968 failed(levelCheckPool<tosa::MaxPool2dOp>(op)) ||
969 failed(levelCheckAdaptivePool<tosa::MaxPool2dAdaptiveOp>(op)) ||
970 failed(levelCheckFFT<tosa::RFFT2dOp>(op)) ||
971 failed(levelCheckTransposeConv2d(op)) ||
failed(levelCheckResize(op)) ||
972 failed(levelCheckConv2DBlockScaled(op))) {
977 if (
failed(levelCheckListSize(op))) {
981 if (isa<tosa::IfOp>(op) || isa<tosa::WhileOp>(op)) {
982 if (
failed(levelCheckMaxNesting(op))) {
990LogicalResult TosaValidation::applyAttributeCheck(Operation *op) {
991 if (
failed(attributeCheckRescale(op)))
993 if (
failed(attributeCheckCast(op)))
998inline bool CompatibleTypes(
const mlir::Type &type,
999 const mlir::Type &declaredType) {
1001 return type == declaredType;
1004LogicalResult TosaValidation::CheckVariable(Operation *op) {
1005 if (
auto variableOp = dyn_cast<mlir::tosa::VariableOp>(op)) {
1006 mlir::StringAttr nameAttr = variableOp.getNameAttr();
1008 if (variablesMap.count(nameAttr))
1009 return op->
emitOpError() <<
"name has already been declared";
1011 auto elementType = variableOp.getType();
1012 DenseIntElementsAttr varShapeAttr = variableOp.getVarShape();
1013 SmallVector<int64_t> shape = to_vector(varShapeAttr.getValues<int64_t>());
1014 RankedTensorType variableType =
1015 RankedTensorType::get(ArrayRef<int64_t>(shape), elementType);
1017 variablesMap[nameAttr] = variableType;
1023LogicalResult TosaValidation::CheckVariableReadOrWrite(Operation *op) {
1024 if (isa<mlir::tosa::VariableReadOp>(op) ||
1025 isa<mlir::tosa::VariableWriteOp>(op)) {
1026 mlir::StringAttr nameAttr =
1028 .Case<mlir::tosa::VariableReadOp, mlir::tosa::VariableWriteOp>(
1029 [](
auto variableOp) {
return variableOp.getNameAttr(); });
1030 if (!variablesMap.count(nameAttr))
1031 return op->
emitOpError() <<
"name has not been declared";
1033 auto varType = variablesMap[nameAttr];
1036 auto type = v.getType();
1037 if (!CompatibleTypes(type, varType))
1038 return op->
emitOpError() <<
"operand type does not equal variable type";
1042 auto type = v.getType();
1043 if (!CompatibleTypes(type, varType))
1044 return op->
emitOpError() <<
"result type does not equal variable type";
1051LogicalResult TosaValidation::applyVariableCheck(Operation *op) {
1052 if (
failed(CheckVariable(op)) ||
failed(CheckVariableReadOrWrite(op)))
1057LogicalResult checkErrorIfResize(Operation *op) {
1058 auto resize = dyn_cast<tosa::ResizeOp>(op);
1062 const Value input = resize.getInput();
1063 const Value output = resize.getOutput();
1064 const RankedTensorType inputType =
1065 llvm::dyn_cast<RankedTensorType>(input.
getType());
1066 const RankedTensorType outputType =
1067 llvm::dyn_cast<RankedTensorType>(output.
getType());
1069 if (!inputType || !outputType)
1070 return op->
emitOpError(
"expect ranked input/output tensor");
1074 if (inputType.hasStaticShape() && outputType.hasStaticShape()) {
1075 const SmallVector<int64_t, 4> sizes = {
1076 outputType.getDimSize(1), outputType.getDimSize(2),
1077 inputType.getDimSize(1), inputType.getDimSize(2)};
1078 const int64_t *maxDim = llvm::max_element(sizes);
1079 if (maxDim != sizes.end() && *maxDim >= 16384)
1081 "expect input/output height/width dims to be < 16384, ")
1082 <<
"got [OH, OW, IH, IW] = " << sizes;
1085 SmallVector<int64_t> scale;
1089 const int64_t scaleYN = scale[0];
1090 const int64_t scaleYD = scale[1];
1091 const int64_t scaleXN = scale[2];
1092 const int64_t scaleXD = scale[3];
1095 if (scaleYN > (1 << 11) || scaleXN > (1 << 11))
1097 "expect all scale numerator values to be <= (1 << 11), "
1099 << scaleYN <<
", scale_x_n=" << scaleXN;
1101 if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN)
1102 return op->
emitOpError(
"expect a downscale ratio larger than 1/16, got y=")
1103 << scaleYN <<
"/" << scaleYD <<
", x=" << scaleXN <<
"/" << scaleXD;
1105 SmallVector<int64_t> offset;
1106 SmallVector<int64_t> border;
1111 const int64_t offsetY = offset[0];
1112 const int64_t offsetX = offset[1];
1115 if (offsetY < -scaleYN || offsetY >= 16 * scaleYN)
1117 "expect offsetY / scaleYNumerator to be in range [-1, 16), got ")
1118 << offsetY <<
"/" << scaleYN;
1119 if (offsetX < -scaleXN || offsetX >= 16 * scaleXN)
1121 "expect offsetX / scaleXNumerator to be in range [-1, 16), got ")
1122 << offsetX <<
"/" << scaleXN;
1124 const int64_t borderY = border[0];
1125 const int64_t borderX = border[1];
1126 if (borderY < -16 * scaleYN || borderY >= scaleYN)
1128 "expect borderY / scaleYNumerator to be in range [-16, 1), got ")
1129 << borderY <<
"/" << scaleYN;
1130 if (borderX < -16 * scaleXN || borderX >= scaleXN)
1132 "expect borderX / scaleXNumerator to be in range [-16, 1), got ")
1133 << borderX <<
"/" << scaleXN;
1146 const int64_t
rhs) -> std::optional<int64_t> {
1148 return std::nullopt;
1152 const int64_t oh = outputType.getDimSize(1);
1153 const int64_t ow = outputType.getDimSize(2);
1154 const int64_t ih = inputType.getDimSize(1);
1155 const int64_t iw = inputType.getDimSize(2);
1157 if (ih != ShapedType::kDynamic) {
1158 const std::optional<int64_t> calculatedOutHeightMinusOne =
1159 idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD);
1160 if (!calculatedOutHeightMinusOne.has_value())
1162 "expected (input_height - 1) * scale_y_n - offset_y + "
1164 <<
"to be wholly divisible by scale_y_d, got ((" << ih
1165 <<
" - 1) * " << scaleYN <<
" - " << offsetY <<
" + " << borderY
1166 <<
") / " << scaleYD;
1167 const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1;
1168 if (oh != ShapedType::kDynamic && calculatedOutHeight != oh)
1170 "calculated output height did not match expected: ")
1171 <<
"calculated=" << calculatedOutHeight <<
", expected=" << oh;
1174 if (iw != ShapedType::kDynamic) {
1175 const std::optional<int64_t> calculatedOutWidthMinusOne =
1176 idivCheck((iw - 1) * scaleXN - offsetX + borderX, scaleXD);
1177 if (!calculatedOutWidthMinusOne.has_value())
1179 "expected (input_width - 1) * scale_x_n - offset_x + "
1181 <<
"to be wholly divisible by scale_x_d, got ((" << iw
1182 <<
" - 1) * " << scaleXN <<
" - " << offsetX <<
" + " << borderX
1183 <<
") / " << scaleXD;
1184 const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1;
1185 if (ow != ShapedType::kDynamic && calculatedOutWidth != ow)
1186 return op->
emitOpError(
"calculated output width did not match expected: ")
1187 <<
"calculated=" << calculatedOutWidth <<
", expected=" << ow;
1193LogicalResult checkErrorIfMul(Operation *op) {
1194 auto mul = dyn_cast<tosa::MulOp>(op);
1200 ElementsAttr shift_elem;
1203 int32_t shift = shift_elem.getValues<IntegerAttr>()[0].getInt();
1205 if (inputElemType.isInteger(32)) {
1207 if (shift < 0 || shift > 63)
1209 <<
"requires 0 <= shift && shift <= 63, but got: " << shift;
1214 <<
"requires shift = 0 for all input data types that "
1215 "are not int32_t, but got: "
1222LogicalResult checkErrorIfTable(Operation *op) {
1223 auto table = dyn_cast<tosa::TableOp>(op);
1229 const int tableSize = inputElemType.isInteger(8) ? 256 : 513;
1231 const ShapeAdaptor tableShape(table.getTable().getType());
1232 if (tableShape.hasStaticShape()) {
1233 const auto numElements = tableShape.getNumElements();
1234 if (numElements != tableSize)
1235 return op->
emitOpError() <<
"requires table size of " << tableSize
1236 <<
", got " << numElements;
1242LogicalResult checkErrorIfRescale(Operation *op) {
1243 auto rescale = dyn_cast<tosa::RescaleOp>(op);
1247 auto inputType = llvm::dyn_cast<ShapedType>(rescale.getInput().getType());
1248 auto outputType = llvm::dyn_cast<ShapedType>(rescale.getOutput().getType());
1249 if (!inputType || !outputType || !inputType.getElementType().isInteger() ||
1250 !outputType.getElementType().isInteger())
1253 auto inElemType = inputType.getElementType();
1254 auto outElemType = outputType.getElementType();
1255 auto inWidth = inElemType.getIntOrFloatBitWidth();
1256 auto outWidth = outElemType.getIntOrFloatBitWidth();
1258 bool inputUnsigned = rescale.getInputUnsigned();
1259 bool outputUnsigned = rescale.getOutputUnsigned();
1261 bool scale32 = rescale.getScale32();
1262 auto roundingMode = rescale.getRoundingMode();
1265 if (scale32 && inWidth == 48)
1266 return op->
emitOpError() <<
"scale32 is not allowed with 48-bit input.";
1269 if (!scale32 && roundingMode == RoundingMode::DOUBLE_ROUND)
1271 <<
"DOUBLE_ROUND is only allowed with scale32=true.";
1274 if (inputUnsigned && outputUnsigned)
1275 return op->
emitOpError() <<
"input and output cannot be both unsigned.";
1278 if (outWidth == 32 && inputUnsigned)
1280 <<
"i32 output type is not allowed with unsigned input.";
1283 if (inWidth == 32 && outputUnsigned)
1285 <<
"i32 input type is not allowed with unsigned output.";
1288 if (inWidth == 48 && outputUnsigned)
1290 <<
"i48 input type is not allowed with unsigned output.";
1293 if (inWidth == 48 && inputUnsigned)
1297 if (inWidth == 32 && inputUnsigned)
1298 return op->
emitOpError() <<
"i32 input type cannot be unsigned.";
1301 if (outWidth == 32 && outputUnsigned)
1307LogicalResult checkErrorIfPad(
Operation *op) {
1308 auto pad = dyn_cast<tosa::PadOp>(op);
1317 for (
const APInt &val : paddingAttr.getValues<APInt>()) {
1318 if (val.getSExtValue() < 0)
1319 return op->
emitOpError() <<
"padding value must all be non-negative, got "
1320 << val.getSExtValue();
1326LogicalResult checkErrorIfReshape(
Operation *op) {
1327 auto reshapeOp = dyn_cast<tosa::ReshapeOp>(op);
1337 return op->
emitOpError(
"shape input contains inferable dimension (")
1340 "which does not conform to the TOSA specification";
1345LogicalResult checkErrorIfSlice(Operation *op) {
1346 auto sliceOp = dyn_cast<tosa::SliceOp>(op);
1350 SmallVector<int64_t> startValues;
1351 SmallVector<int64_t> sizeValues;
1353 sliceOp.getStart().getDefiningOp(), startValues);
1354 const bool hasSizeValues =
1358 return op->
emitOpError(
"start input contains inferable dimension (")
1360 <<
") which does not conform to the TOSA specification";
1362 return op->
emitOpError(
"size input contains inferable dimension (")
1365 "does not conform to the TOSA specification";
1370static bool isOpIsolatedWithinRegion(Operation *op, Region *region) {
1371 return llvm::all_of(op->
getOperands(), [&](
auto operand) {
1372 Region *operandRegion = operand.getParentRegion();
1373 return operandRegion && region->isAncestor(operandRegion);
1377static LogicalResult isRegionIsolatedFromAbove(Region ®ionToCheck) {
1378 bool noLiveInValue =
true;
1379 regionToCheck.
walk([&noLiveInValue, ®ionToCheck](Operation *op) {
1380 if (!isOpIsolatedWithinRegion(op, ®ionToCheck)) {
1381 noLiveInValue =
false;
1386 return noLiveInValue ?
success() : failure();
1389LogicalResult checkIsolatedRegion(Operation *op, Region ®ionToCheck,
1390 StringRef regionName) {
1391 if (succeeded(isRegionIsolatedFromAbove(regionToCheck)))
1394 <<
"is not conformant to the TOSA specification. It requires the '"
1395 << regionName <<
"' region is isolated from above.\n";
1398LogicalResult checkErrorIfCondIf(Operation *op) {
1399 auto ifOp = dyn_cast<tosa::IfOp>(op);
1432 if (
failed(checkIsolatedRegion(op, ifOp.getThenGraph(),
"then")) ||
1433 failed(checkIsolatedRegion(op, ifOp.getElseGraph(),
"else")))
1438LogicalResult checkErrorIfWhileLoop(Operation *op) {
1439 auto whileOp = dyn_cast<tosa::WhileOp>(op);
1443 if (
failed(checkIsolatedRegion(op, whileOp.getCondGraph(),
"cond")) ||
1444 failed(checkIsolatedRegion(op, whileOp.getBodyGraph(),
"body")))
1449LogicalResult checkErrorIfScatter(Operation *op) {
1450 auto scatterOp = dyn_cast<tosa::ScatterOp>(op);
1455 DenseIntElementsAttr indicesAttr;
1459 auto const indicesType =
1460 dyn_cast<ShapedType>(scatterOp.getIndices().getType());
1461 if (!indicesType || !indicesType.hasRank()) {
1467 op->
emitOpError(
"indices values contain duplicates");
1474LogicalResult TosaValidation::applyErrorIfCheck(Operation *op) {
1475 if (
failed(checkErrorIfResize(op)) ||
failed(checkErrorIfMul(op)) ||
1476 failed(checkErrorIfTable(op)) ||
failed(checkErrorIfRescale(op)) ||
1477 failed(checkErrorIfPad(op)) ||
failed(checkErrorIfReshape(op)) ||
1478 failed(checkErrorIfSlice(op)) ||
failed(checkErrorIfCondIf(op)) ||
1479 failed(checkErrorIfWhileLoop(op)) ||
failed(checkErrorIfScatter(op)))
1484LogicalResult TosaValidation::applyFunctionSignatureCheck(func::FuncOp op) {
1486 const auto isTensorType = [](Type type) {
return isa<TensorType>(type); };
1487 if (!llvm::all_of(op.getArgumentTypes(), isTensorType))
1488 return op.emitOpError()
1489 <<
"Function argument types must be a tensor type to be TOSA "
1490 "compliant, got !tosa.shape type";
1491 if (!llvm::all_of(op.getResultTypes(), isTensorType))
1492 return op.emitOpError()
1493 <<
"Function return types must be a tensor type to be TOSA "
1494 "compliant, got !tosa.shape type";
1497 if (
failed(validateOperationElementTypes(op, !strictOpSpecAlignment)))
1501 const TosaLevel tosaLevel = targetEnv.
getLevel();
1502 for (
const auto &[idx, argType] : llvm::enumerate(op.getArgumentTypes())) {
1503 const std::string inputDesc = llvm::formatv(
"input argument {0}", idx);
1504 if (
failed(levelCheckRank(op, argType, inputDesc, tosaLevel.
MAX_RANK)))
1506 if (
failed(levelCheckSize(op, argType, inputDesc)))
1509 for (
const auto &[idx, resultType] : llvm::enumerate(op.getResultTypes())) {
1510 const std::string resultDesc = llvm::formatv(
"return value {0}", idx);
1511 if (
failed(levelCheckRank(op, resultType, resultDesc, tosaLevel.
MAX_RANK)))
1513 if (
failed(levelCheckSize(op, resultType, resultDesc)))
1520 for (
const Type &argType :
1521 llvm::concat<const Type>(op.getArgumentTypes(), op.getResultTypes())) {
1522 if (
auto shapedType = dyn_cast<ShapedType>(argType)) {
1523 if (llvm::any_of(shapedType.getShape(),
1524 [](int64_t dim) { return dim == 0; }))
1525 return op.emitOpError() <<
"Function argument or return types must not "
1526 "have zero dimensions";
1533LogicalResult TosaValidation::validateValidElementType(Operation *op, Type type,
1534 bool allowUnsigned) {
1535 if (isa<FloatType>(type)) {
1536 if (isa<Float32Type, Float16Type, BFloat16Type, Float8E4M3FNType,
1537 Float8E5M2Type, Float4E2M1FNType, Float6E2M3FNType,
1538 Float6E3M2FNType, Float8E8M0FNUType>(type))
1540 }
else if (
auto intTy = dyn_cast<IntegerType>(type)) {
1541 if (intTy.isSignless()) {
1542 switch (intTy.getWidth()) {
1552 }
else if (allowUnsigned && intTy.isUnsigned()) {
1553 switch (intTy.getWidth()) {
1560 }
else if (isa<tosa::shapeType>(type))
1562 else if (isa<tosa::mxint8Type, tosa::BlockScaledType>(type))
1565 return op->
emitOpError() <<
"is not profile-aligned: element type " << type
1570TosaValidation::validateOperationElementTypes(TosaOp op,
bool allowUnsigned) {
1571 for (Value operand : op->getOperands()) {
1573 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1577 for (Type resultTy : op->getResultTypes()) {
1579 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1583 if (
auto variableOp = dyn_cast<tosa::VariableOp>(*op)) {
1585 validateValidElementType(op, variableOp.getType(), allowUnsigned)))
1592TosaValidation::validateOperationElementTypes(func::FuncOp op,
1593 bool allowUnsigned) {
1594 for (
const Type &argType :
1595 llvm::concat<const Type>(op.getArgumentTypes(), op.getResultTypes())) {
1597 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1604void TosaValidation::runOnOperation() {
1605 ModuleOp modOp = getOperation();
1606 TosaDialect *tosaDialect =
getContext().getLoadedDialect<TosaDialect>();
1611 const auto maybeTargetEnv =
1613 if (
failed(maybeTargetEnv))
1614 return signalPassFailure();
1615 targetEnv = *maybeTargetEnv;
1617 const auto functions = modOp.getOps<func::FuncOp>();
1618 if (validateFunctionSignature &&
1619 llvm::any_of(functions, [&](func::FuncOp func) {
1620 return failed(applyFunctionSignatureCheck(func));
1622 return signalPassFailure();
1624 modOp.walk([&](TosaOp op) {
1630 const bool allowUnsigned =
1631 !strictOpSpecAlignment && isa<tosa::RescaleOp>(op);
1632 if (
failed(validateOperationElementTypes(op, allowUnsigned)))
1633 return signalPassFailure();
1635 if (strictOpSpecAlignment &&
1637 return signalPassFailure();
1639 if (strictOpSpecAlignment &&
1641 return signalPassFailure();
1643 if (!allowInvalidOpDatatypeCombinations &&
1645 return signalPassFailure();
1649 if (
failed(applyConstantOperandCheck(op)))
1650 signalPassFailure();
1653 if (
failed(applyLevelCheck(op)))
1654 signalPassFailure();
1657 if (
failed(applyAttributeCheck(op)))
1658 signalPassFailure();
1661 if (
failed(applyVariableCheck(op)))
1662 signalPassFailure();
1665 if (strictOpSpecAlignment &&
failed(applyErrorIfCheck(op)))
1666 signalPassFailure();
static llvm::ManagedStatic< PassManagerOptions > options
static std::optional< int64_t > idivCheck(const int64_t lhs, const int64_t rhs)
#define CHECK_RANKS_AND_SIZES(tosaOp)
#define CHECK_SIZES(tosaOp)
#define CHECK_SHAPE_LEN(tosaOp)
LogicalResult checkProfile(Operation *op, const tosa::TargetEnv &targetEnv)
LogicalResult checkExtension(Operation *op, const tosa::TargetEnv &targetEnv)
LogicalResult checkInvalid(Operation *op)
Attributes are known-constant values of operations.
An attribute that represents a reference to a dense integer vector or tensor object.
Operation is the basic unit of execution within MLIR.
Value getOperand(unsigned idx)
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
operand_range getOperands()
Returns an iterator on the underlying Value's.
result_range getResults()
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Type getType() const
Return the type of this value.
static WalkResult advance()
static WalkResult interrupt()
This class represents the capability enabled in the target implementation such as profile,...
TosaLevel getLevel() const
static FailureOr< TargetEnv > createTargetEnvFromAttr(TargetEnvAttr targetAttr, Location targetEnvAttrLoc)
bool allows(Profile prof) const
TosaSpecificationVersion getSpecVersion() const
bool isBackwardsCompatibleWith(TosaSpecificationVersion baseVersion) const
SmallVector< AffineExpr, 4 > concat(ArrayRef< AffineExpr > a, ArrayRef< AffineExpr > b)
Return the vector that is the concatenation of a and b.
llvm::SmallString< 4 > stringifyVersion(TosaSpecificationVersion version)
RankedTensorType getVariableType(VariableOp variableOp)
static constexpr TosaLevel TOSA_LEVEL_NONE
bool hasUniqueConstantScatterIndices(ShapedType indicesType, DenseIntElementsAttr indicesAttr)
constexpr int64_t kInferableDimSize
Represents a dimension in the shape of a tensor that can be inferred based on the other provided dime...
unsigned getBitWidth(Type type)
TargetEnvAttr lookupTargetEnvOrDefault(Operation *op)
Queries the target environment recursively from enclosing symbol table ops containing the given op or...
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.
@ Mul
RHS of mul is always a constant or a symbolic expression.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
llvm::TypeSwitch< T, ResultT > TypeSwitch
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.