30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/StringExtras.h"
32#include "llvm/Support/FormatVariadic.h"
36#define GEN_PASS_DEF_TOSAVALIDATION
37#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
48 for (
const auto index : operandIndices) {
51 return op->
emitOpError(
"expected compile time resolvable constant, but "
52 "got variable value for operand #")
59static LogicalResult checkConstantOperandMul(
Operation *op,
61 if (!env.
allows(Extension::dynamic) && isa<tosa::MulOp>(op)) {
63 return checkConstantOperands(op, {2});
68static LogicalResult checkConstantOperandTable(
Operation *op,
70 if (!env.
allows(Extension::dynamic) && isa<tosa::TableOp>(op)) {
72 return checkConstantOperands(op, {1});
77static LogicalResult checkConstantOperandPad(
Operation *op,
79 if (
auto padOp = dyn_cast<tosa::PadOp>(op)) {
81 if (!env.
allows(Extension::dynamic) && padOp.getPadConst())
84 return checkConstantOperands(op, {2});
89static LogicalResult checkConstantOperandRescale(
Operation *op,
91 if (!env.
allows(Extension::dynamic) && isa<tosa::RescaleOp>(op)) {
93 return checkConstantOperands(op, {1, 2, 3, 4});
99static LogicalResult checkConstantOperandConvOps(
Operation *op,
101 if (!env.
allows(Extension::dynamic) && isa<T>(op)) {
103 return checkConstantOperands(op, {3, 4});
108static LogicalResult checkConstantOperandMatMul(
Operation *op,
110 if (!env.
allows(Extension::dynamic) &&
111 isa<tosa::MatMulOp, tosa::MatMulTOp>(op)) {
113 return checkConstantOperands(op, {2, 3});
120 if (!env.
allows(Extension::dynamic) &&
121 isa<tosa::RowGatherBlockScaledOp>(op)) {
122 auto rowGatherOp = cast<tosa::RowGatherBlockScaledOp>(op);
123 const unsigned rowCountIndex = rowGatherOp.getValues().size() + 1;
124 return checkConstantOperands(op, {rowCountIndex});
129static LogicalResult checkConstantOperandRowGather(
Operation *op,
131 if (!env.
allows(Extension::dynamic) && isa<tosa::RowGatherOp>(op)) {
133 return checkConstantOperands(op, {2});
138static LogicalResult checkConstantOperandAvgPool2d(
Operation *op,
140 if (!env.
allows(Extension::dynamic) && isa<tosa::AvgPool2dOp>(op)) {
142 return checkConstantOperands(op, {1, 2});
149 if (!env.
allows(Extension::dynamic) && isa<tosa::AvgPool2dAdaptiveOp>(op)) {
153 return checkConstantOperands(op, {1, 2});
158static LogicalResult checkConstantOperandNegate(
Operation *op,
160 if (!env.
allows(Extension::dynamic) && isa<tosa::NegateOp>(op)) {
162 return checkConstantOperands(op, {1, 2});
167static LogicalResult checkConstantOperandSilceShape(
Operation *op,
169 if (!env.
allows(Extension::dynamic) && isa<tosa::SliceShapeOp>(op)) {
171 return checkConstantOperands(op, {1, 2});
182 explicit TosaValidation() { populateConstantOperandChecks(); }
184 explicit TosaValidation(
const TosaValidationOptions &
options)
186 this->strictOpSpecAlignment =
options.strictOpSpecAlignment;
187 this->allowInvalidOpDatatypeCombinations =
188 options.allowInvalidOpDatatypeCombinations;
189 this->validateFunctionSignature =
options.validateFunctionSignature;
191 void runOnOperation() final;
193 LogicalResult applyConstantOperandCheck(Operation *op) {
194 for (
auto &checker : constCheckers) {
195 if (
failed(checker(op, targetEnv)))
201 LogicalResult applyFunctionSignatureCheck(func::FuncOp op);
202 LogicalResult applyLevelCheck(Operation *op);
203 LogicalResult applyAttributeCheck(Operation *op);
206 LogicalResult applyVariableCheck(Operation *op);
209 LogicalResult applyErrorIfCheck(Operation *op);
212 void populateConstantOperandChecks() {
213 constCheckers.emplace_back(checkConstantOperandMul);
214 constCheckers.emplace_back(checkConstantOperandTable);
215 constCheckers.emplace_back(checkConstantOperandPad);
216 constCheckers.emplace_back(checkConstantOperandRescale);
217 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv2DOp>);
218 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv3DOp>);
219 constCheckers.emplace_back(
220 checkConstantOperandConvOps<tosa::DepthwiseConv2DOp>);
221 constCheckers.emplace_back(
222 checkConstantOperandConvOps<tosa::TransposeConv2DOp>);
223 constCheckers.emplace_back(checkConstantOperandMatMul);
224 constCheckers.emplace_back(checkConstantOperandRowGather);
225 constCheckers.emplace_back(checkConstantOperandRowGatherBlockScaled);
226 constCheckers.emplace_back(checkConstantOperandAvgPool2d);
227 constCheckers.emplace_back(checkConstantOperandAvgPool2dAdaptive);
228 constCheckers.emplace_back(checkConstantOperandNegate);
229 constCheckers.emplace_back(checkConstantOperandSilceShape);
232 LogicalResult levelCheck(Operation *op,
const int32_t calculatedValue,
233 const int32_t maxLevel,
const StringRef inputName,
234 const StringRef levelName) {
235 if (calculatedValue > maxLevel)
237 <<
"failed level check: " << inputName <<
" <= " << levelName
238 <<
" (" << maxLevel <<
"), got " << calculatedValue;
242 LogicalResult levelCheckKernel(Operation *op, int32_t v,
243 const StringRef inputName) {
244 return levelCheck(op, v, targetEnv.getLevel().MAX_KERNEL, inputName,
248 LogicalResult levelCheckStride(Operation *op, int32_t v,
249 const StringRef inputName) {
250 return levelCheck(op, v, targetEnv.getLevel().MAX_STRIDE, inputName,
254 LogicalResult levelCheckScale(Operation *op, int32_t v,
255 const StringRef inputName) {
256 return levelCheck(op, v, targetEnv.getLevel().MAX_SCALE, inputName,
260 LogicalResult levelCheckListSize(Operation *op, int32_t v,
261 const StringRef inputName) {
262 const std::string inputDesc =
263 llvm::formatv(
"length(tensor_list_shape({0}))", inputName);
264 return levelCheck(op, v, targetEnv.getLevel().MAX_TENSOR_LIST_SIZE,
265 inputDesc,
"MAX_TENSOR_LIST_SIZE");
269 LogicalResult levelCheckRank(Operation *op,
const Type typeToCheck,
270 const StringRef operandOrResult,
271 int32_t highest_rank) {
272 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
274 return op->
emitOpError() <<
"failed level check: unranked tensor";
275 if (type.getRank() > highest_rank)
276 return op->
emitOpError() <<
"failed level check: " << operandOrResult
277 <<
" rank(shape) <= MAX_RANK";
283 LogicalResult levelCheckRank(Operation *op,
const Value &v,
284 const StringRef operandOrResult,
285 int32_t highest_rank) {
286 return levelCheckRank(op, v.
getType(), operandOrResult, highest_rank);
290 LogicalResult levelCheckSize(Operation *op,
const Type &typeToCheck,
291 const StringRef operandOrResult);
294 LogicalResult levelCheckSize(Operation *op,
const Value &v,
295 const StringRef operandOrResult) {
296 return levelCheckSize(op, v.
getType(), operandOrResult);
300 LogicalResult levelCheckShapeLength(Operation *op,
const Type typeToCheck,
301 const StringRef operandOrResult) {
302 if (tosa::shapeType shapeType = dyn_cast<tosa::shapeType>(typeToCheck)) {
303 if (shapeType.getRank() > targetEnv.getLevel().MAX_SHAPE_LEN)
305 <<
"failed shape type level check: " << typeToCheck
306 <<
" exceeds MAX_SHAPE_LEN";
312 template <
typename T>
313 LogicalResult levelCheckSizes(T tosaOp) {
314 auto op = tosaOp.getOperation();
316 if (
failed(levelCheckSize(op, v,
"operand")))
321 if (
failed(levelCheckSize(op, v,
"result")))
328 template <
typename T>
329 LogicalResult levelCheckRanks(T tosaOp) {
330 auto op = tosaOp.getOperation();
331 const TosaLevel tosaLevel = targetEnv.getLevel();
345 template <
typename T>
346 LogicalResult levelCheckShapeLengths(T tosaOp) {
347 for (
const auto &v : tosaOp->getOperands()) {
348 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"operand")))
351 for (
const auto &v : tosaOp->getResults()) {
352 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"result")))
360 LogicalResult levelCheckRanksAndSizes(Operation *op);
363 template <
typename T>
364 LogicalResult levelCheckPool(Operation *op) {
365 if (
auto poolOp = dyn_cast<T>(op)) {
366 for (
auto k : poolOp.getKernel()) {
367 if (
failed(levelCheckKernel(op, k,
"kernel"))) {
371 for (
auto s : poolOp.getStride()) {
372 if (
failed(levelCheckStride(op, s,
"stride"))) {
376 for (
auto p : poolOp.getPad()) {
377 if (
failed(levelCheckKernel(op, p,
"pad"))) {
385 template <
typename T>
386 static constexpr bool IsSupportedAdaptivePoolOp =
387 std::is_same_v<T, tosa::AvgPool2dAdaptiveOp> ||
388 std::is_same_v<T, tosa::MaxPool2dAdaptiveOp>;
390 template <
typename T,
typename std::enable_if<IsSupportedAdaptivePoolOp<T>,
392 LogicalResult levelCheckAdaptivePool(Operation *op) {
393 auto poolOp = dyn_cast<T>(op);
397 SmallVector<int64_t> kernelValues;
400 for (
const auto k : kernelValues)
401 if (
failed(levelCheckKernel(op, k,
"kernel")))
405 SmallVector<int64_t> strideValues;
408 for (
const auto s : strideValues)
409 if (
failed(levelCheckStride(op, s,
"stride")))
413 SmallVector<int64_t> padValues;
415 for (
const auto p : padValues)
416 if (
failed(levelCheckKernel(op, p,
"pad")))
424 template <
typename T>
425 LogicalResult levelCheckConv(Operation *op) {
426 if (
auto convOp = dyn_cast<T>(op)) {
428 for (
auto k : convOp.getDilation()) {
429 if (
failed(levelCheckKernel(op, k,
"dilation"))) {
433 for (
auto p : convOp.getPad()) {
434 if (
failed(levelCheckKernel(op, p,
"pad"))) {
438 for (
auto s : convOp.getStride()) {
439 if (
failed(levelCheckStride(op, s,
"stride"))) {
443 auto dilation = convOp.getDilation();
444 if (ShapedType weightType =
446 auto shape = weightType.getShape();
447 if (isa<tosa::Conv2DOp>(op)) {
448 assert(shape.size() == 4);
449 assert(dilation.size() == 2);
450 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
451 "dilation_y * KH")) ||
452 failed(levelCheckKernel(op, dilation[1] * shape[2],
455 }
else if (isa<tosa::Conv3DOp>(op)) {
456 assert(shape.size() == 5);
457 assert(dilation.size() == 3);
458 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
459 "dilation_d * KD")) ||
460 failed(levelCheckKernel(op, dilation[1] * shape[2],
461 "dilation_y * KH")) ||
462 failed(levelCheckKernel(op, dilation[2] * shape[3],
465 }
else if (isa<tosa::DepthwiseConv2DOp>(op)) {
466 assert(shape.size() == 4);
467 assert(dilation.size() == 2);
468 if (
failed(levelCheckKernel(op, dilation[0] * shape[0],
469 "dilation_y * KH")) ||
470 failed(levelCheckKernel(op, dilation[1] * shape[1],
479 LogicalResult levelCheckConv2DBlockScaled(Operation *op) {
480 auto convOp = dyn_cast<Conv2DBlockScaledOp>(op);
484 SmallVector<int64_t> padValues;
486 for (
const auto p : padValues)
487 if (
failed(levelCheckKernel(op, p,
"pad <= MAX_KERNEL")))
491 SmallVector<int64_t> strideValues;
494 for (
const auto s : strideValues)
495 if (
failed(levelCheckKernel(op, s,
"stride <= MAX_KERNEL")))
499 SmallVector<int64_t> dilationValues;
502 int64_t KH = ShapedType::kDynamic;
503 int64_t KW = ShapedType::kDynamic;
504 const ShapeAdaptor weightDataShape(convOp.getWeightData().getType());
505 KH = weightDataShape.getDimSize(1);
506 KW = weightDataShape.getDimSize(2);
507 const ShapeAdaptor weightScaleShape(convOp.getWeightScale().getType());
508 KH = ShapedType::isDynamic(KH) ? weightScaleShape.getDimSize(1) : KH;
509 KW = ShapedType::isDynamic(KW) ? weightScaleShape.getDimSize(2) : KW;
511 if (!ShapedType::isDynamic(KH) &&
512 failed(levelCheckKernel(op, dilationValues[0] * KH,
513 "dilation_y * KH <= MAX_KERNEL)")))
516 if (!ShapedType::isDynamic(KW) &&
517 failed(levelCheckKernel(op, dilationValues[1] * KW,
518 "dilation_x * KW <= MAX_KERNEL)")))
526 template <
typename T>
527 LogicalResult levelCheckFFT(Operation *op) {
530 if (ShapedType type = dyn_cast<ShapedType>(v.getType())) {
531 auto shape = type.getShape();
532 assert(shape.size() == 3);
533 if (
failed(levelCheckKernel(op, shape[1],
"H")) ||
534 failed(levelCheckKernel(op, shape[2],
"W"))) {
544 LogicalResult levelCheckTransposeConv2d(Operation *op) {
545 if (
auto transpose = dyn_cast<tosa::TransposeConv2DOp>(op)) {
546 if (ShapedType filterType =
547 dyn_cast<ShapedType>(transpose.getWeight().getType())) {
548 auto shape = filterType.getShape();
549 assert(shape.size() == 4);
551 if (
failed(levelCheckKernel(op, shape[1],
"KH")) ||
552 failed(levelCheckKernel(op, shape[2],
"KW"))) {
556 for (
auto p : transpose.getOutPad()) {
557 if (
failed(levelCheckKernel(op, p,
"pad"))) {
561 for (
auto s : transpose.getStride()) {
562 if (
failed(levelCheckStride(op, s,
"stride"))) {
571 LogicalResult levelCheckResize(Operation *op) {
572 if (
auto resize = dyn_cast<tosa::ResizeOp>(op)) {
573 SmallVector<int64_t> scale;
578 const int64_t scaleYN = scale[0];
579 const int64_t scaleYD = scale[1];
580 const int64_t scaleXN = scale[2];
581 const int64_t scaleXD = scale[3];
583 levelCheckScale(op, scaleYN / scaleYD,
"scale_y_n/scale_y_d")) ||
585 levelCheckScale(op, scaleXN / scaleXD,
"scale_x_n/scale_x_d"))) {
596 static void getMaxNestedDepth(Operation *op, int32_t &depth) {
597 if (isa<mlir::func::FuncOp>(op) || isa<ModuleOp>(op))
605 getMaxNestedDepth(op, depth);
608 LogicalResult levelCheckMaxNesting(Operation *op) {
609 int32_t maxNestedDepth = 0;
610 getMaxNestedDepth(op, maxNestedDepth);
612 const int32_t maxNestingLevel = targetEnv.getLevel().MAX_NESTING;
613 if (maxNestedDepth >= maxNestingLevel)
615 <<
"failed level check: tosa_nesting_depth < MAX_NESTING" <<
" ("
616 << maxNestingLevel <<
"), got " << maxNestedDepth;
620 LogicalResult levelCheckListSize(Operation *op) {
621 if (
auto concat = dyn_cast<tosa::ConcatOp>(op)) {
622 return levelCheckListSize(op,
concat.getInput1().size(),
"input1");
624 if (
auto custom = dyn_cast<tosa::CustomOp>(op)) {
625 if (
failed(levelCheckListSize(op, custom.getInputList().size(),
627 failed(levelCheckListSize(op, custom.getOutputList().size(),
632 if (
auto condIf = dyn_cast<tosa::IfOp>(op)) {
634 levelCheckListSize(op, condIf.getInputList().size(),
"inputs")) ||
635 failed(levelCheckListSize(op, condIf.getOutputList().size(),
640 if (
auto w = dyn_cast<tosa::WhileOp>(op)) {
641 if (
failed(levelCheckListSize(op, w.getInputList().size(),
"inputs")) ||
642 failed(levelCheckListSize(op, w.getOutputList().size(),
"outputs"))) {
646 if (
auto concat_shape = dyn_cast<tosa::ConcatShapeOp>(op))
647 return levelCheckListSize(op, concat_shape.getInput().size(),
"input");
651 LogicalResult attributeCheckRescale(Operation *op) {
652 if (
auto rescale = dyn_cast<tosa::RescaleOp>(op)) {
653 if (rescale.getRoundingMode() == RoundingMode::DOUBLE_ROUND &&
654 !targetEnv.allows(Extension::doubleround)) {
656 <<
"failed attribute check: rounding_mode = DOUBLE_ROUND "
657 <<
"requires extension [doubleround]";
660 if (rescale.getRoundingMode() == RoundingMode::INEXACT_ROUND &&
661 !targetEnv.allows(Extension::inexactround)) {
663 <<
"failed attribute check: rounding_mode = INEXACT_ROUND "
664 <<
"requires extension [inexactround]";
671 LogicalResult attributeCheckCast(Operation *op) {
672 if (
auto cast = dyn_cast<tosa::CastOp>(op)) {
673 const TosaSpecificationVersion targetVersion = targetEnv.getSpecVersion();
674 const TosaSpecificationVersion minRequiredVersion(1, 1,
true);
675 if (cast.getInputUnsigned() &&
678 <<
"failed attribute check: CAST attribute input_unsigned "
679 <<
"requires version 1.1.draft"
685 LogicalResult CheckVariable(Operation *op);
686 LogicalResult CheckVariableReadOrWrite(Operation *op);
687 LogicalResult validateValidElementType(Operation *op, Type type,
688 bool allowUnsigned =
false);
689 LogicalResult validateOperationElementTypes(TosaOp op,
690 bool allowUnsigned =
false);
691 LogicalResult validateOperationElementTypes(func::FuncOp op,
692 bool allowUnsigned =
false);
695 std::function<LogicalResult(Operation *,
const tosa::TargetEnv &)>>
698 TosaProfileCompliance profileComp;
699 tosa::TargetEnv targetEnv;
703LogicalResult TosaValidation::levelCheckRanks(tosa::ArgMaxOp tosaOp) {
704 auto *op = tosaOp.getOperation();
705 if (
failed(levelCheckRank(op, tosaOp.getInput(),
"operand",
710 if (
failed(levelCheckRank(op, tosaOp.getOutput(),
"result",
718LogicalResult TosaValidation::levelCheckRanks(tosa::IfOp tosaOp) {
719 auto *op = tosaOp.getOperation();
722 if (
failed(levelCheckRank(op, tosaOp.getCondition(),
"operand",
730LogicalResult TosaValidation::levelCheckRanks(tosa::VariableOp tosaOp) {
731 auto *op = tosaOp.getOperation();
733 if (
failed(levelCheckRank(op, variableType,
"variable type",
741LogicalResult TosaValidation::levelCheckSizes(tosa::VariableOp tosaOp) {
742 auto *op = tosaOp.getOperation();
744 if (
failed(levelCheckSize(op, variableType,
"variable type")))
750LogicalResult TosaValidation::levelCheckRanksAndSizes(Operation *op) {
751#define CHECK_RANKS_AND_SIZES(tosaOp) \
752 if (isa<tosa::tosaOp##Op>(op)) { \
753 if (failed(levelCheckRanks(cast<tosa::tosaOp##Op>(op)))) \
755 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
759#define CHECK_SIZES(tosaOp) \
760 if (isa<tosa::tosaOp##Op>(op)) { \
761 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
765#define CHECK_SHAPE_LEN(tosaOp) \
766 if (isa<tosa::tosaOp##Op>(op)) { \
767 if (failed(levelCheckShapeLengths(cast<tosa::tosaOp##Op>(op)))) \
899#undef CHECK_RANKS_AND_SIZES
901#undef CHECK_SHAPE_LEN
906LogicalResult TosaValidation::levelCheckSize(Operation *op,
907 const Type &typeToCheck,
908 const StringRef operandOrResult) {
909 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
911 return op->
emitOpError() <<
"failed level check: unranked tensor";
912 auto shape = type.getShape();
913 for (
auto dim : shape) {
914 const bool dimIsDynamic = mlir::ShapedType::isDynamic(dim);
915 const TosaSpecificationVersion targetVersion = targetEnv.
getSpecVersion();
916 const TosaSpecificationVersion minRequiredVersion(1, 1,
true);
926 return op->
emitOpError() <<
"failed level check: " << operandOrResult
927 <<
" shape dimension cannot be dynamic when"
928 <<
" targeting TOSA specification version 1.0"
933 int64_t elementBytes = std::max(INT64_C(1), elementBits / 8);
934 int64_t size = elementBytes * type.getNumElements();
941 const int64_t maxSize =
945 <<
"failed level check: " << operandOrResult
946 <<
" tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)";
951LogicalResult TosaValidation::applyLevelCheck(Operation *op) {
958 if (
failed(levelCheckRanksAndSizes(op)))
961 if (
failed(levelCheckPool<tosa::AvgPool2dOp>(op)) ||
962 failed(levelCheckAdaptivePool<tosa::AvgPool2dAdaptiveOp>(op)) ||
963 failed(levelCheckConv<tosa::Conv2DOp>(op)) ||
964 failed(levelCheckConv<tosa::Conv3DOp>(op)) ||
965 failed(levelCheckConv<tosa::DepthwiseConv2DOp>(op)) ||
966 failed(levelCheckFFT<tosa::FFT2dOp>(op)) ||
967 failed(levelCheckPool<tosa::MaxPool2dOp>(op)) ||
968 failed(levelCheckAdaptivePool<tosa::MaxPool2dAdaptiveOp>(op)) ||
969 failed(levelCheckFFT<tosa::RFFT2dOp>(op)) ||
970 failed(levelCheckTransposeConv2d(op)) ||
failed(levelCheckResize(op)) ||
971 failed(levelCheckConv2DBlockScaled(op))) {
976 if (
failed(levelCheckListSize(op))) {
980 if (isa<tosa::IfOp>(op) || isa<tosa::WhileOp>(op)) {
981 if (
failed(levelCheckMaxNesting(op))) {
989LogicalResult TosaValidation::applyAttributeCheck(Operation *op) {
990 if (
failed(attributeCheckRescale(op)))
992 if (
failed(attributeCheckCast(op)))
997inline bool CompatibleTypes(
const mlir::Type &type,
998 const mlir::Type &declaredType) {
1000 return type == declaredType;
1003LogicalResult TosaValidation::CheckVariable(Operation *op) {
1004 if (
auto variableOp = dyn_cast<mlir::tosa::VariableOp>(op)) {
1005 mlir::StringAttr nameAttr = variableOp.getNameAttr();
1007 if (variablesMap.count(nameAttr))
1008 return op->
emitOpError() <<
"name has already been declared";
1010 auto elementType = variableOp.getType();
1011 DenseIntElementsAttr varShapeAttr = variableOp.getVarShape();
1012 SmallVector<int64_t> shape = to_vector(varShapeAttr.getValues<int64_t>());
1013 RankedTensorType variableType =
1014 RankedTensorType::get(ArrayRef<int64_t>(shape), elementType);
1016 variablesMap[nameAttr] = variableType;
1022LogicalResult TosaValidation::CheckVariableReadOrWrite(Operation *op) {
1023 if (isa<mlir::tosa::VariableReadOp>(op) ||
1024 isa<mlir::tosa::VariableWriteOp>(op)) {
1025 mlir::StringAttr nameAttr = cast<mlir::StringAttr>(op->
getAttr(
"name"));
1026 if (!variablesMap.count(nameAttr))
1027 return op->
emitOpError() <<
"name has not been declared";
1029 auto varType = variablesMap[nameAttr];
1032 auto type = v.getType();
1033 if (!CompatibleTypes(type, varType))
1034 return op->
emitOpError() <<
"operand type does not equal variable type";
1038 auto type = v.getType();
1039 if (!CompatibleTypes(type, varType))
1040 return op->
emitOpError() <<
"result type does not equal variable type";
1047LogicalResult TosaValidation::applyVariableCheck(Operation *op) {
1048 if (
failed(CheckVariable(op)) ||
failed(CheckVariableReadOrWrite(op)))
1053LogicalResult checkErrorIfResize(Operation *op) {
1054 auto resize = dyn_cast<tosa::ResizeOp>(op);
1058 const Value input = resize.getInput();
1059 const Value output = resize.getOutput();
1060 const RankedTensorType inputType =
1061 llvm::dyn_cast<RankedTensorType>(input.
getType());
1062 const RankedTensorType outputType =
1063 llvm::dyn_cast<RankedTensorType>(output.
getType());
1065 if (!inputType || !outputType)
1066 return op->
emitOpError(
"expect ranked input/output tensor");
1070 if (inputType.hasStaticShape() && outputType.hasStaticShape()) {
1072 outputType.getDimSize(1), outputType.getDimSize(2),
1073 inputType.getDimSize(1), inputType.getDimSize(2)};
1074 const int64_t *maxDim = llvm::max_element(sizes);
1075 if (maxDim != sizes.end() && *maxDim >= 16384)
1077 "expect input/output height/width dims to be < 16384, ")
1078 <<
"got [OH, OW, IH, IW] = " << sizes;
1085 const int64_t scaleYN = scale[0];
1087 const int64_t scaleXN = scale[2];
1091 if (scaleYN > (1 << 11) || scaleXN > (1 << 11))
1093 "expect all scale numerator values to be <= (1 << 11), "
1095 << scaleYN <<
", scale_x_n=" << scaleXN;
1097 if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN)
1098 return op->
emitOpError(
"expect a downscale ratio larger than 1/16, got y=")
1099 << scaleYN <<
"/" << scaleYD <<
", x=" << scaleXN <<
"/" << scaleXD;
1108 const int64_t offsetX = offset[1];
1111 if (offsetY < -scaleYN || offsetY >= 16 * scaleYN)
1113 "expect offsetY / scaleYNumerator to be in range [-1, 16), got ")
1114 << offsetY <<
"/" << scaleYN;
1115 if (offsetX < -scaleXN || offsetX >= 16 * scaleXN)
1117 "expect offsetX / scaleXNumerator to be in range [-1, 16), got ")
1118 << offsetX <<
"/" << scaleXN;
1120 const int64_t borderY = border[0];
1121 const int64_t borderX = border[1];
1122 if (borderY < -16 * scaleYN || borderY >= scaleYN)
1124 "expect borderY / scaleYNumerator to be in range [-16, 1), got ")
1125 << borderY <<
"/" << scaleYN;
1126 if (borderX < -16 * scaleXN || borderX >= scaleXN)
1128 "expect borderX / scaleXNumerator to be in range [-16, 1), got ")
1129 << borderX <<
"/" << scaleXN;
1142 const int64_t rhs) -> std::optional<int64_t> {
1144 return std::nullopt;
1148 const int64_t oh = outputType.getDimSize(1);
1149 const int64_t ow = outputType.getDimSize(2);
1150 const int64_t ih = inputType.getDimSize(1);
1151 const int64_t iw = inputType.getDimSize(2);
1153 if (ih != ShapedType::kDynamic) {
1154 const std::optional<int64_t> calculatedOutHeightMinusOne =
1155 idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD);
1156 if (!calculatedOutHeightMinusOne.has_value())
1158 "expected (input_height - 1) * scale_y_n - offset_y + "
1160 <<
"to be wholly divisible by scale_y_d, got ((" << ih
1161 <<
" - 1) * " << scaleYN <<
" - " << offsetY <<
" + " << borderY
1162 <<
") / " << scaleYD;
1163 const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1;
1164 if (oh != ShapedType::kDynamic && calculatedOutHeight != oh)
1166 "calculated output height did not match expected: ")
1167 <<
"calculated=" << calculatedOutHeight <<
", expected=" << oh;
1170 if (iw != ShapedType::kDynamic) {
1171 const std::optional<int64_t> calculatedOutWidthMinusOne =
1172 idivCheck((iw - 1) * scaleXN - offsetX + borderX, scaleXD);
1173 if (!calculatedOutWidthMinusOne.has_value())
1175 "expected (input_width - 1) * scale_x_n - offset_x + "
1177 <<
"to be wholly divisible by scale_x_d, got ((" << iw
1178 <<
" - 1) * " << scaleXN <<
" - " << offsetX <<
" + " << borderX
1179 <<
") / " << scaleXD;
1180 const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1;
1181 if (ow != ShapedType::kDynamic && calculatedOutWidth != ow)
1182 return op->
emitOpError(
"calculated output width did not match expected: ")
1183 <<
"calculated=" << calculatedOutWidth <<
", expected=" << ow;
1189LogicalResult checkErrorIfMul(Operation *op) {
1190 auto mul = dyn_cast<tosa::MulOp>(op);
1196 ElementsAttr shift_elem;
1199 int32_t shift = shift_elem.getValues<IntegerAttr>()[0].getInt();
1201 if (inputElemType.isInteger(32)) {
1203 if (shift < 0 || shift > 63)
1205 <<
"requires 0 <= shift && shift <= 63, but got: " << shift;
1210 <<
"requires shift = 0 for all input data types that "
1211 "are not int32_t, but got: "
1218LogicalResult checkErrorIfTable(Operation *op) {
1219 auto table = dyn_cast<tosa::TableOp>(op);
1225 const int tableSize = inputElemType.isInteger(8) ? 256 : 513;
1227 const ShapeAdaptor tableShape(table.getTable().getType());
1228 if (tableShape.hasStaticShape()) {
1229 const auto numElements = tableShape.getNumElements();
1230 if (numElements != tableSize)
1231 return op->
emitOpError() <<
"requires table size of " << tableSize
1232 <<
", got " << numElements;
1238LogicalResult checkErrorIfRescale(Operation *op) {
1239 auto rescale = dyn_cast<tosa::RescaleOp>(op);
1243 auto inputType = llvm::dyn_cast<ShapedType>(rescale.getInput().getType());
1244 auto outputType = llvm::dyn_cast<ShapedType>(rescale.getOutput().getType());
1245 if (!inputType || !outputType || !inputType.getElementType().isInteger() ||
1246 !outputType.getElementType().isInteger())
1249 auto inElemType = inputType.getElementType();
1250 auto outElemType = outputType.getElementType();
1251 auto inWidth = inElemType.getIntOrFloatBitWidth();
1252 auto outWidth = outElemType.getIntOrFloatBitWidth();
1254 bool inputUnsigned = rescale.getInputUnsigned();
1255 bool outputUnsigned = rescale.getOutputUnsigned();
1257 bool scale32 = rescale.getScale32();
1258 auto roundingMode = rescale.getRoundingMode();
1261 if (scale32 && inWidth == 48)
1262 return op->
emitOpError() <<
"scale32 is not allowed with 48-bit input.";
1265 if (!scale32 && roundingMode == RoundingMode::DOUBLE_ROUND)
1267 <<
"DOUBLE_ROUND is only allowed with scale32=true.";
1270 if (inputUnsigned && outputUnsigned)
1271 return op->
emitOpError() <<
"input and output cannot be both unsigned.";
1274 if (outWidth == 32 && inputUnsigned)
1276 <<
"i32 output type is not allowed with unsigned input.";
1279 if (inWidth == 32 && outputUnsigned)
1281 <<
"i32 input type is not allowed with unsigned output.";
1284 if (inWidth == 48 && outputUnsigned)
1286 <<
"i48 input type is not allowed with unsigned output.";
1289 if (inWidth == 48 && inputUnsigned)
1290 return op->
emitOpError() <<
"i48 input type cannot be unsigned.";
1293 if (inWidth == 32 && inputUnsigned)
1294 return op->
emitOpError() <<
"i32 input type cannot be unsigned.";
1297 if (outWidth == 32 && outputUnsigned)
1298 return op->
emitOpError() <<
"i32 output type cannot be unsigned.";
1303LogicalResult checkErrorIfPad(Operation *op) {
1304 auto pad = dyn_cast<tosa::PadOp>(op);
1308 DenseIntElementsAttr paddingAttr;
1313 for (
const APInt &val : paddingAttr.getValues<APInt>()) {
1314 if (val.getSExtValue() < 0)
1315 return op->
emitOpError() <<
"padding value must all be non-negative, got "
1316 << val.getSExtValue();
1322LogicalResult checkErrorIfReshape(Operation *op) {
1323 auto reshapeOp = dyn_cast<tosa::ReshapeOp>(op);
1327 SmallVector<int64_t> shapeValues;
1333 return op->
emitOpError(
"shape input contains inferable dimension (")
1336 "which does not conform to the TOSA specification";
1341LogicalResult checkErrorIfSlice(Operation *op) {
1342 auto sliceOp = dyn_cast<tosa::SliceOp>(op);
1346 SmallVector<int64_t> startValues;
1347 SmallVector<int64_t> sizeValues;
1349 sliceOp.getStart().getDefiningOp(), startValues);
1350 const bool hasSizeValues =
1354 return op->
emitOpError(
"start input contains inferable dimension (")
1356 <<
") which does not conform to the TOSA specification";
1358 return op->
emitOpError(
"size input contains inferable dimension (")
1361 "does not conform to the TOSA specification";
1366static bool isOpIsolatedWithinRegion(Operation *op, Region *region) {
1367 return llvm::all_of(op->
getOperands(), [&](
auto operand) {
1368 Region *operandRegion = operand.getParentRegion();
1369 return operandRegion && region->isAncestor(operandRegion);
1373static LogicalResult isRegionIsolatedFromAbove(Region ®ionToCheck) {
1374 bool noLiveInValue =
true;
1375 regionToCheck.
walk([&noLiveInValue, ®ionToCheck](Operation *op) {
1376 if (!isOpIsolatedWithinRegion(op, ®ionToCheck)) {
1377 noLiveInValue =
false;
1382 return noLiveInValue ?
success() : failure();
1385LogicalResult checkIsolatedRegion(Operation *op, Region ®ionToCheck,
1386 StringRef regionName) {
1387 if (succeeded(isRegionIsolatedFromAbove(regionToCheck)))
1390 <<
"is not conformant to the TOSA specification. It requires the '"
1391 << regionName <<
"' region is isolated from above.\n";
1394LogicalResult checkErrorIfCondIf(Operation *op) {
1395 auto ifOp = dyn_cast<tosa::IfOp>(op);
1428 if (
failed(checkIsolatedRegion(op, ifOp.getThenGraph(),
"then")) ||
1429 failed(checkIsolatedRegion(op, ifOp.getElseGraph(),
"else")))
1434LogicalResult checkErrorIfWhileLoop(Operation *op) {
1435 auto whileOp = dyn_cast<tosa::WhileOp>(op);
1439 if (
failed(checkIsolatedRegion(op, whileOp.getCondGraph(),
"cond")) ||
1440 failed(checkIsolatedRegion(op, whileOp.getBodyGraph(),
"body")))
1445LogicalResult checkErrorIfScatter(Operation *op) {
1446 auto scatterOp = dyn_cast<tosa::ScatterOp>(op);
1451 DenseIntElementsAttr indicesAttr;
1455 auto const indicesType =
1456 dyn_cast<ShapedType>(scatterOp.getIndices().getType());
1457 if (!indicesType || !indicesType.hasRank()) {
1463 op->
emitOpError(
"indices values contain duplicates");
1470LogicalResult TosaValidation::applyErrorIfCheck(Operation *op) {
1471 if (
failed(checkErrorIfResize(op)) ||
failed(checkErrorIfMul(op)) ||
1472 failed(checkErrorIfTable(op)) ||
failed(checkErrorIfRescale(op)) ||
1473 failed(checkErrorIfPad(op)) ||
failed(checkErrorIfReshape(op)) ||
1474 failed(checkErrorIfSlice(op)) ||
failed(checkErrorIfCondIf(op)) ||
1475 failed(checkErrorIfWhileLoop(op)) ||
failed(checkErrorIfScatter(op)))
1480LogicalResult TosaValidation::applyFunctionSignatureCheck(func::FuncOp op) {
1482 const auto isTensorType = [](Type type) {
return isa<TensorType>(type); };
1483 if (!llvm::all_of(op.getArgumentTypes(), isTensorType))
1484 return op.emitOpError()
1485 <<
"Function argument types must be a tensor type to be TOSA "
1486 "compliant, got !tosa.shape type";
1487 if (!llvm::all_of(op.getResultTypes(), isTensorType))
1488 return op.emitOpError()
1489 <<
"Function return types must be a tensor type to be TOSA "
1490 "compliant, got !tosa.shape type";
1493 if (
failed(validateOperationElementTypes(op, !strictOpSpecAlignment)))
1497 const TosaLevel tosaLevel = targetEnv.
getLevel();
1498 for (
const auto &[idx, argType] : llvm::enumerate(op.getArgumentTypes())) {
1499 const std::string inputDesc = llvm::formatv(
"input argument {0}", idx);
1500 if (
failed(levelCheckRank(op, argType, inputDesc, tosaLevel.
MAX_RANK)))
1502 if (
failed(levelCheckSize(op, argType, inputDesc)))
1505 for (
const auto &[idx, resultType] : llvm::enumerate(op.getResultTypes())) {
1506 const std::string resultDesc = llvm::formatv(
"return value {0}", idx);
1507 if (
failed(levelCheckRank(op, resultType, resultDesc, tosaLevel.
MAX_RANK)))
1509 if (
failed(levelCheckSize(op, resultType, resultDesc)))
1516 for (
const Type &argType :
1517 llvm::concat<const Type>(op.getArgumentTypes(), op.getResultTypes())) {
1518 if (
auto shapedType = dyn_cast<ShapedType>(argType)) {
1519 if (llvm::any_of(shapedType.getShape(),
1520 [](int64_t dim) { return dim == 0; }))
1521 return op.emitOpError() <<
"Function argument or return types must not "
1522 "have zero dimensions";
1529LogicalResult TosaValidation::validateValidElementType(Operation *op, Type type,
1530 bool allowUnsigned) {
1531 if (isa<FloatType>(type)) {
1532 if (isa<Float32Type, Float16Type, BFloat16Type, Float8E4M3FNType,
1533 Float8E5M2Type, Float4E2M1FNType, Float6E2M3FNType,
1534 Float6E3M2FNType, Float8E8M0FNUType>(type))
1536 }
else if (
auto intTy = dyn_cast<IntegerType>(type)) {
1537 if (intTy.isSignless()) {
1538 switch (intTy.getWidth()) {
1548 }
else if (allowUnsigned && intTy.isUnsigned()) {
1549 switch (intTy.getWidth()) {
1556 }
else if (isa<tosa::shapeType>(type))
1558 else if (isa<tosa::mxint8Type, tosa::BlockScaledType>(type))
1561 return op->
emitOpError() <<
"is not profile-aligned: element type " << type
1566TosaValidation::validateOperationElementTypes(TosaOp op,
bool allowUnsigned) {
1567 for (Value operand : op->getOperands()) {
1569 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1573 for (Type resultTy : op->getResultTypes()) {
1575 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1579 if (
auto variableOp = dyn_cast<tosa::VariableOp>(*op)) {
1581 validateValidElementType(op, variableOp.getType(), allowUnsigned)))
1588TosaValidation::validateOperationElementTypes(func::FuncOp op,
1589 bool allowUnsigned) {
1590 for (
const Type &argType :
1591 llvm::concat<const Type>(op.getArgumentTypes(), op.getResultTypes())) {
1593 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1600void TosaValidation::runOnOperation() {
1601 ModuleOp modOp = getOperation();
1602 TosaDialect *tosaDialect =
getContext().getLoadedDialect<TosaDialect>();
1607 const auto maybeTargetEnv =
1609 if (
failed(maybeTargetEnv))
1610 return signalPassFailure();
1611 targetEnv = *maybeTargetEnv;
1613 const auto functions = modOp.getOps<func::FuncOp>();
1614 if (validateFunctionSignature &&
1615 llvm::any_of(functions, [&](func::FuncOp func) {
1616 return failed(applyFunctionSignatureCheck(func));
1618 return signalPassFailure();
1620 modOp.walk([&](TosaOp op) {
1626 const bool allowUnsigned =
1627 !strictOpSpecAlignment && isa<tosa::RescaleOp>(op);
1628 if (
failed(validateOperationElementTypes(op, allowUnsigned)))
1629 return signalPassFailure();
1631 if (strictOpSpecAlignment &&
1633 return signalPassFailure();
1635 if (strictOpSpecAlignment &&
1637 return signalPassFailure();
1639 if (!allowInvalidOpDatatypeCombinations &&
1641 return signalPassFailure();
1645 if (
failed(applyConstantOperandCheck(op)))
1646 signalPassFailure();
1649 if (
failed(applyLevelCheck(op)))
1650 signalPassFailure();
1653 if (
failed(applyAttributeCheck(op)))
1654 signalPassFailure();
1657 if (
failed(applyVariableCheck(op)))
1658 signalPassFailure();
1661 if (strictOpSpecAlignment &&
failed(applyErrorIfCheck(op)))
1662 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.
Operation is the basic unit of execution within MLIR.
Value getOperand(unsigned idx)
Attribute getAttr(StringAttr name)
Return the specified attribute if present, null otherwise.
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::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.