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;
190 void runOnOperation() final;
192 LogicalResult applyConstantOperandCheck(Operation *op) {
193 for (
auto &checker : constCheckers) {
194 if (
failed(checker(op, targetEnv)))
200 LogicalResult applyFunctionSignatureCheck(func::FuncOp op);
201 LogicalResult applyLevelCheck(Operation *op);
202 LogicalResult applyAttributeCheck(Operation *op);
205 LogicalResult applyVariableCheck(Operation *op);
208 LogicalResult applyErrorIfCheck(Operation *op);
211 void populateConstantOperandChecks() {
212 constCheckers.emplace_back(checkConstantOperandMul);
213 constCheckers.emplace_back(checkConstantOperandTable);
214 constCheckers.emplace_back(checkConstantOperandPad);
215 constCheckers.emplace_back(checkConstantOperandRescale);
216 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv2DOp>);
217 constCheckers.emplace_back(checkConstantOperandConvOps<tosa::Conv3DOp>);
218 constCheckers.emplace_back(
219 checkConstantOperandConvOps<tosa::DepthwiseConv2DOp>);
220 constCheckers.emplace_back(
221 checkConstantOperandConvOps<tosa::TransposeConv2DOp>);
222 constCheckers.emplace_back(checkConstantOperandMatMul);
223 constCheckers.emplace_back(checkConstantOperandRowGather);
224 constCheckers.emplace_back(checkConstantOperandRowGatherBlockScaled);
225 constCheckers.emplace_back(checkConstantOperandAvgPool2d);
226 constCheckers.emplace_back(checkConstantOperandAvgPool2dAdaptive);
227 constCheckers.emplace_back(checkConstantOperandNegate);
228 constCheckers.emplace_back(checkConstantOperandSilceShape);
231 LogicalResult levelCheck(Operation *op,
const int32_t calculatedValue,
232 const int32_t maxLevel,
const StringRef inputName,
233 const StringRef levelName) {
234 if (calculatedValue > maxLevel)
236 <<
"failed level check: " << inputName <<
" <= " << levelName
237 <<
" (" << maxLevel <<
"), got " << calculatedValue;
241 LogicalResult levelCheckKernel(Operation *op, int32_t v,
242 const StringRef inputName) {
243 return levelCheck(op, v, targetEnv.getLevel().MAX_KERNEL, inputName,
247 LogicalResult levelCheckStride(Operation *op, int32_t v,
248 const StringRef inputName) {
249 return levelCheck(op, v, targetEnv.getLevel().MAX_STRIDE, inputName,
253 LogicalResult levelCheckScale(Operation *op, int32_t v,
254 const StringRef inputName) {
255 return levelCheck(op, v, targetEnv.getLevel().MAX_SCALE, inputName,
259 LogicalResult levelCheckListSize(Operation *op, int32_t v,
260 const StringRef inputName) {
261 const std::string inputDesc =
262 llvm::formatv(
"length(tensor_list_shape({0}))", inputName);
263 return levelCheck(op, v, targetEnv.getLevel().MAX_TENSOR_LIST_SIZE,
264 inputDesc,
"MAX_TENSOR_LIST_SIZE");
268 LogicalResult levelCheckRank(Operation *op,
const Type typeToCheck,
269 const StringRef operandOrResult,
270 int32_t highest_rank) {
271 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
273 return op->
emitOpError() <<
"failed level check: unranked tensor";
274 if (type.getRank() > highest_rank)
275 return op->
emitOpError() <<
"failed level check: " << operandOrResult
276 <<
" rank(shape) <= MAX_RANK";
282 LogicalResult levelCheckRank(Operation *op,
const Value &v,
283 const StringRef operandOrResult,
284 int32_t highest_rank) {
285 return levelCheckRank(op, v.
getType(), operandOrResult, highest_rank);
289 LogicalResult levelCheckSize(Operation *op,
const Type &typeToCheck,
290 const StringRef operandOrResult);
293 LogicalResult levelCheckSize(Operation *op,
const Value &v,
294 const StringRef operandOrResult) {
295 return levelCheckSize(op, v.
getType(), operandOrResult);
299 LogicalResult levelCheckShapeLength(Operation *op,
const Type typeToCheck,
300 const StringRef operandOrResult) {
301 if (tosa::shapeType shapeType = dyn_cast<tosa::shapeType>(typeToCheck)) {
302 if (shapeType.getRank() > targetEnv.getLevel().MAX_SHAPE_LEN)
304 <<
"failed shape type level check: " << typeToCheck
305 <<
" exceeds MAX_SHAPE_LEN";
311 template <
typename T>
312 LogicalResult levelCheckSizes(T tosaOp) {
313 auto op = tosaOp.getOperation();
315 if (
failed(levelCheckSize(op, v,
"operand")))
320 if (
failed(levelCheckSize(op, v,
"result")))
327 template <
typename T>
328 LogicalResult levelCheckRanks(T tosaOp) {
329 auto op = tosaOp.getOperation();
330 const TosaLevel tosaLevel = targetEnv.getLevel();
344 template <
typename T>
345 LogicalResult levelCheckShapeLengths(T tosaOp) {
346 for (
const auto &v : tosaOp->getOperands()) {
347 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"operand")))
350 for (
const auto &v : tosaOp->getResults()) {
351 if (
failed(levelCheckShapeLength(tosaOp, v.getType(),
"result")))
359 LogicalResult levelCheckRanksAndSizes(Operation *op);
362 template <
typename T>
363 LogicalResult levelCheckPool(Operation *op) {
364 if (
auto poolOp = dyn_cast<T>(op)) {
365 for (
auto k : poolOp.getKernel()) {
366 if (
failed(levelCheckKernel(op, k,
"kernel"))) {
370 for (
auto s : poolOp.getStride()) {
371 if (
failed(levelCheckStride(op, s,
"stride"))) {
375 for (
auto p : poolOp.getPad()) {
376 if (
failed(levelCheckKernel(op, p,
"pad"))) {
384 template <
typename T>
385 static constexpr bool IsSupportedAdaptivePoolOp =
386 std::is_same_v<T, tosa::AvgPool2dAdaptiveOp> ||
387 std::is_same_v<T, tosa::MaxPool2dAdaptiveOp>;
389 template <
typename T,
typename std::enable_if<IsSupportedAdaptivePoolOp<T>,
391 LogicalResult levelCheckAdaptivePool(Operation *op) {
392 auto poolOp = dyn_cast<T>(op);
396 SmallVector<int64_t> kernelValues;
399 for (
const auto k : kernelValues)
400 if (
failed(levelCheckKernel(op, k,
"kernel")))
404 SmallVector<int64_t> strideValues;
407 for (
const auto s : strideValues)
408 if (
failed(levelCheckStride(op, s,
"stride")))
412 SmallVector<int64_t> padValues;
414 for (
const auto p : padValues)
415 if (
failed(levelCheckKernel(op, p,
"pad")))
423 template <
typename T>
424 LogicalResult levelCheckConv(Operation *op) {
425 if (
auto convOp = dyn_cast<T>(op)) {
427 for (
auto k : convOp.getDilation()) {
428 if (
failed(levelCheckKernel(op, k,
"dilation"))) {
432 for (
auto p : convOp.getPad()) {
433 if (
failed(levelCheckKernel(op, p,
"pad"))) {
437 for (
auto s : convOp.getStride()) {
438 if (
failed(levelCheckStride(op, s,
"stride"))) {
442 auto dilation = convOp.getDilation();
443 if (ShapedType weightType =
445 auto shape = weightType.getShape();
446 if (isa<tosa::Conv2DOp>(op)) {
447 assert(shape.size() == 4);
448 assert(dilation.size() == 2);
449 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
450 "dilation_y * KH")) ||
451 failed(levelCheckKernel(op, dilation[1] * shape[2],
454 }
else if (isa<tosa::Conv3DOp>(op)) {
455 assert(shape.size() == 5);
456 assert(dilation.size() == 3);
457 if (
failed(levelCheckKernel(op, dilation[0] * shape[1],
458 "dilation_d * KD")) ||
459 failed(levelCheckKernel(op, dilation[1] * shape[2],
460 "dilation_y * KH")) ||
461 failed(levelCheckKernel(op, dilation[2] * shape[3],
464 }
else if (isa<tosa::DepthwiseConv2DOp>(op)) {
465 assert(shape.size() == 4);
466 assert(dilation.size() == 2);
467 if (
failed(levelCheckKernel(op, dilation[0] * shape[0],
468 "dilation_y * KH")) ||
469 failed(levelCheckKernel(op, dilation[1] * shape[1],
478 LogicalResult levelCheckConv2DBlockScaled(Operation *op) {
479 auto convOp = dyn_cast<Conv2DBlockScaledOp>(op);
483 SmallVector<int64_t> padValues;
485 for (
const auto p : padValues)
486 if (
failed(levelCheckKernel(op, p,
"pad <= MAX_KERNEL")))
490 SmallVector<int64_t> strideValues;
493 for (
const auto s : strideValues)
494 if (
failed(levelCheckKernel(op, s,
"stride <= MAX_KERNEL")))
498 SmallVector<int64_t> dilationValues;
501 int64_t KH = ShapedType::kDynamic;
502 int64_t KW = ShapedType::kDynamic;
503 const ShapeAdaptor weightDataShape(convOp.getWeightData().getType());
504 KH = weightDataShape.getDimSize(1);
505 KW = weightDataShape.getDimSize(2);
506 const ShapeAdaptor weightScaleShape(convOp.getWeightScale().getType());
507 KH = ShapedType::isDynamic(KH) ? weightScaleShape.getDimSize(1) : KH;
508 KW = ShapedType::isDynamic(KW) ? weightScaleShape.getDimSize(2) : KW;
510 if (!ShapedType::isDynamic(KH) &&
511 failed(levelCheckKernel(op, dilationValues[0] * KH,
512 "dilation_y * KH <= MAX_KERNEL)")))
515 if (!ShapedType::isDynamic(KW) &&
516 failed(levelCheckKernel(op, dilationValues[1] * KW,
517 "dilation_x * KW <= MAX_KERNEL)")))
525 template <
typename T>
526 LogicalResult levelCheckFFT(Operation *op) {
529 if (ShapedType type = dyn_cast<ShapedType>(v.getType())) {
530 auto shape = type.getShape();
531 assert(shape.size() == 3);
532 if (
failed(levelCheckKernel(op, shape[1],
"H")) ||
533 failed(levelCheckKernel(op, shape[2],
"W"))) {
543 LogicalResult levelCheckTransposeConv2d(Operation *op) {
544 if (
auto transpose = dyn_cast<tosa::TransposeConv2DOp>(op)) {
545 if (ShapedType filterType =
546 dyn_cast<ShapedType>(transpose.getWeight().getType())) {
547 auto shape = filterType.getShape();
548 assert(shape.size() == 4);
550 if (
failed(levelCheckKernel(op, shape[1],
"KH")) ||
551 failed(levelCheckKernel(op, shape[2],
"KW"))) {
555 for (
auto p : transpose.getOutPad()) {
556 if (
failed(levelCheckKernel(op, p,
"pad"))) {
560 for (
auto s : transpose.getStride()) {
561 if (
failed(levelCheckStride(op, s,
"stride"))) {
570 LogicalResult levelCheckResize(Operation *op) {
571 if (
auto resize = dyn_cast<tosa::ResizeOp>(op)) {
572 SmallVector<int64_t> scale;
577 const int64_t scaleYN = scale[0];
578 const int64_t scaleYD = scale[1];
579 const int64_t scaleXN = scale[2];
580 const int64_t scaleXD = scale[3];
582 levelCheckScale(op, scaleYN / scaleYD,
"scale_y_n/scale_y_d")) ||
584 levelCheckScale(op, scaleXN / scaleXD,
"scale_x_n/scale_x_d"))) {
595 static void getMaxNestedDepth(Operation *op, int32_t &depth) {
596 if (isa<mlir::func::FuncOp>(op) || isa<ModuleOp>(op))
604 getMaxNestedDepth(op, depth);
607 LogicalResult levelCheckMaxNesting(Operation *op) {
608 int32_t maxNestedDepth = 0;
609 getMaxNestedDepth(op, maxNestedDepth);
611 const int32_t maxNestingLevel = targetEnv.getLevel().MAX_NESTING;
612 if (maxNestedDepth >= maxNestingLevel)
614 <<
"failed level check: tosa_nesting_depth < MAX_NESTING" <<
" ("
615 << maxNestingLevel <<
"), got " << maxNestedDepth;
619 LogicalResult levelCheckListSize(Operation *op) {
620 if (
auto concat = dyn_cast<tosa::ConcatOp>(op)) {
621 return levelCheckListSize(op,
concat.getInput1().size(),
"input1");
623 if (
auto custom = dyn_cast<tosa::CustomOp>(op)) {
624 if (
failed(levelCheckListSize(op, custom.getInputList().size(),
626 failed(levelCheckListSize(op, custom.getOutputList().size(),
631 if (
auto condIf = dyn_cast<tosa::IfOp>(op)) {
633 levelCheckListSize(op, condIf.getInputList().size(),
"inputs")) ||
634 failed(levelCheckListSize(op, condIf.getOutputList().size(),
639 if (
auto w = dyn_cast<tosa::WhileOp>(op)) {
640 if (
failed(levelCheckListSize(op, w.getInputList().size(),
"inputs")) ||
641 failed(levelCheckListSize(op, w.getOutputList().size(),
"outputs"))) {
645 if (
auto concat_shape = dyn_cast<tosa::ConcatShapeOp>(op))
646 return levelCheckListSize(op, concat_shape.getInput().size(),
"input");
650 LogicalResult attributeCheckRescale(Operation *op) {
651 if (
auto rescale = dyn_cast<tosa::RescaleOp>(op)) {
652 if (rescale.getRoundingMode() == RoundingMode::DOUBLE_ROUND &&
653 !targetEnv.allows(Extension::doubleround)) {
655 <<
"failed attribute check: rounding_mode = DOUBLE_ROUND "
656 <<
"requires extension [doubleround]";
659 if (rescale.getRoundingMode() == RoundingMode::INEXACT_ROUND &&
660 !targetEnv.allows(Extension::inexactround)) {
662 <<
"failed attribute check: rounding_mode = INEXACT_ROUND "
663 <<
"requires extension [inexactround]";
670 LogicalResult CheckVariable(Operation *op);
671 LogicalResult CheckVariableReadOrWrite(Operation *op);
672 LogicalResult validateValidElementType(Operation *op, Type type,
673 bool allowUnsigned =
false);
674 LogicalResult validateOperationElementTypes(Operation *op,
675 bool allowUnsigned =
false);
678 std::function<LogicalResult(Operation *,
const tosa::TargetEnv &)>>
681 TosaProfileCompliance profileComp;
682 tosa::TargetEnv targetEnv;
686LogicalResult TosaValidation::levelCheckRanks(tosa::ArgMaxOp tosaOp) {
687 auto *op = tosaOp.getOperation();
688 if (
failed(levelCheckRank(op, tosaOp.getInput(),
"operand",
693 if (
failed(levelCheckRank(op, tosaOp.getOutput(),
"result",
701LogicalResult TosaValidation::levelCheckRanks(tosa::IfOp tosaOp) {
702 auto *op = tosaOp.getOperation();
705 if (
failed(levelCheckRank(op, tosaOp.getCondition(),
"operand",
713LogicalResult TosaValidation::levelCheckRanks(tosa::VariableOp tosaOp) {
714 auto *op = tosaOp.getOperation();
716 if (
failed(levelCheckRank(op, variableType,
"variable type",
724LogicalResult TosaValidation::levelCheckSizes(tosa::VariableOp tosaOp) {
725 auto *op = tosaOp.getOperation();
727 if (
failed(levelCheckSize(op, variableType,
"variable type")))
733LogicalResult TosaValidation::levelCheckRanksAndSizes(Operation *op) {
734#define CHECK_RANKS_AND_SIZES(tosaOp) \
735 if (isa<tosa::tosaOp##Op>(op)) { \
736 if (failed(levelCheckRanks(cast<tosa::tosaOp##Op>(op)))) \
738 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
742#define CHECK_SIZES(tosaOp) \
743 if (isa<tosa::tosaOp##Op>(op)) { \
744 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
748#define CHECK_SHAPE_LEN(tosaOp) \
749 if (isa<tosa::tosaOp##Op>(op)) { \
750 if (failed(levelCheckShapeLengths(cast<tosa::tosaOp##Op>(op)))) \
882#undef CHECK_RANKS_AND_SIZES
884#undef CHECK_SHAPE_LEN
889LogicalResult TosaValidation::levelCheckSize(Operation *op,
890 const Type &typeToCheck,
891 const StringRef operandOrResult) {
892 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
894 return op->
emitOpError() <<
"failed level check: unranked tensor";
895 auto shape = type.getShape();
896 for (
auto dim : shape) {
897 const bool dimIsDynamic = mlir::ShapedType::isDynamic(dim);
898 const TosaSpecificationVersion targetVersion = targetEnv.
getSpecVersion();
899 const TosaSpecificationVersion minRequiredVersion(1, 1,
true);
909 return op->
emitOpError() <<
"failed level check: " << operandOrResult
910 <<
" shape dimension cannot be dynamic when"
911 <<
" targeting TOSA specification version 1.0"
916 int64_t element_bytes = std::max(INT64_C(1), element_bits / 8);
917 int64_t size = element_bytes * type.getNumElements();
924 const int64_t max_size =
928 <<
"failed level check: " << operandOrResult
929 <<
" tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)";
934LogicalResult TosaValidation::applyLevelCheck(Operation *op) {
941 if (
failed(levelCheckRanksAndSizes(op)))
944 if (
failed(levelCheckPool<tosa::AvgPool2dOp>(op)) ||
945 failed(levelCheckAdaptivePool<tosa::AvgPool2dAdaptiveOp>(op)) ||
946 failed(levelCheckConv<tosa::Conv2DOp>(op)) ||
947 failed(levelCheckConv<tosa::Conv3DOp>(op)) ||
948 failed(levelCheckConv<tosa::DepthwiseConv2DOp>(op)) ||
949 failed(levelCheckFFT<tosa::FFT2dOp>(op)) ||
950 failed(levelCheckPool<tosa::MaxPool2dOp>(op)) ||
951 failed(levelCheckAdaptivePool<tosa::MaxPool2dAdaptiveOp>(op)) ||
952 failed(levelCheckFFT<tosa::RFFT2dOp>(op)) ||
953 failed(levelCheckTransposeConv2d(op)) ||
failed(levelCheckResize(op)) ||
954 failed(levelCheckConv2DBlockScaled(op))) {
959 if (
failed(levelCheckListSize(op))) {
963 if (isa<tosa::IfOp>(op) || isa<tosa::WhileOp>(op)) {
964 if (
failed(levelCheckMaxNesting(op))) {
972LogicalResult TosaValidation::applyAttributeCheck(Operation *op) {
973 if (
failed(attributeCheckRescale(op)))
978inline bool CompatibleTypes(
const mlir::Type &type,
979 const mlir::Type &declaredType) {
981 return type == declaredType;
984LogicalResult TosaValidation::CheckVariable(Operation *op) {
985 if (
auto variableOp = dyn_cast<mlir::tosa::VariableOp>(op)) {
986 mlir::StringAttr nameAttr = variableOp.getNameAttr();
988 if (variablesMap.count(nameAttr))
989 return op->
emitOpError() <<
"name has already been declared";
991 auto elementType = variableOp.getType();
992 DenseIntElementsAttr varShapeAttr = variableOp.getVarShape();
993 SmallVector<int64_t> shape = to_vector(varShapeAttr.getValues<int64_t>());
994 RankedTensorType variableType =
995 RankedTensorType::get(ArrayRef<int64_t>(shape), elementType);
997 variablesMap[nameAttr] = variableType;
1003LogicalResult TosaValidation::CheckVariableReadOrWrite(Operation *op) {
1004 if (isa<mlir::tosa::VariableReadOp>(op) ||
1005 isa<mlir::tosa::VariableWriteOp>(op)) {
1006 mlir::StringAttr nameAttr = cast<mlir::StringAttr>(op->
getAttr(
"name"));
1007 if (!variablesMap.count(nameAttr))
1008 return op->
emitOpError() <<
"name has not been declared";
1010 auto varType = variablesMap[nameAttr];
1013 auto type = v.getType();
1014 if (!CompatibleTypes(type, varType))
1015 return op->
emitOpError() <<
"operand type does not equal variable type";
1019 auto type = v.getType();
1020 if (!CompatibleTypes(type, varType))
1021 return op->
emitOpError() <<
"result type does not equal variable type";
1028LogicalResult TosaValidation::applyVariableCheck(Operation *op) {
1029 if (
failed(CheckVariable(op)) ||
failed(CheckVariableReadOrWrite(op)))
1034LogicalResult checkErrorIfResize(Operation *op) {
1035 auto resize = dyn_cast<tosa::ResizeOp>(op);
1039 const Value input = resize.getInput();
1040 const Value output = resize.getOutput();
1041 const RankedTensorType inputType =
1042 llvm::dyn_cast<RankedTensorType>(input.
getType());
1043 const RankedTensorType outputType =
1044 llvm::dyn_cast<RankedTensorType>(output.
getType());
1046 if (!inputType || !outputType)
1047 return op->
emitOpError(
"expect ranked input/output tensor");
1051 if (inputType.hasStaticShape() && outputType.hasStaticShape()) {
1052 const SmallVector<int64_t, 4> sizes = {
1053 outputType.getDimSize(1), outputType.getDimSize(2),
1054 inputType.getDimSize(1), inputType.getDimSize(2)};
1055 const int64_t *maxDim = llvm::max_element(sizes);
1056 if (maxDim != sizes.end() && *maxDim >= 16384)
1058 "expect input/output height/width dims to be < 16384, ")
1059 <<
"got [OH, OW, IH, IW] = " << sizes;
1062 SmallVector<int64_t> scale;
1066 const int64_t scaleYN = scale[0];
1067 const int64_t scaleYD = scale[1];
1068 const int64_t scaleXN = scale[2];
1069 const int64_t scaleXD = scale[3];
1072 if (scaleYN > (1 << 11) || scaleXN > (1 << 11))
1074 "expect all scale numerator values to be <= (1 << 11), "
1076 << scaleYN <<
", scale_x_n=" << scaleXN;
1078 if (scaleYD >= 16 * scaleYN || scaleXD >= 16 * scaleXN)
1079 return op->
emitOpError(
"expect a downscale ratio larger than 1/16, got y=")
1080 << scaleYN <<
"/" << scaleYD <<
", x=" << scaleXN <<
"/" << scaleXD;
1089 const int64_t offsetX = offset[1];
1092 if (offsetY < -scaleYN || offsetY >= 16 * scaleYN)
1094 "expect offsetY / scaleYNumerator to be in range [-1, 16), got ")
1095 << offsetY <<
"/" << scaleYN;
1096 if (offsetX < -scaleXN || offsetX >= 16 * scaleXN)
1098 "expect offsetX / scaleXNumerator to be in range [-1, 16), got ")
1099 << offsetX <<
"/" << scaleXN;
1101 const int64_t borderY = border[0];
1102 const int64_t borderX = border[1];
1103 if (borderY < -16 * scaleYN || borderY >= scaleYN)
1105 "expect borderY / scaleYNumerator to be in range [-16, 1), got ")
1106 << borderY <<
"/" << scaleYN;
1107 if (borderX < -16 * scaleXN || borderX >= scaleXN)
1109 "expect borderX / scaleXNumerator to be in range [-16, 1), got ")
1110 << borderX <<
"/" << scaleXN;
1125 return std::nullopt;
1129 const int64_t oh = outputType.getDimSize(1);
1130 const int64_t ow = outputType.getDimSize(2);
1132 const int64_t iw = inputType.getDimSize(2);
1134 if (ih != ShapedType::kDynamic) {
1135 const std::optional<int64_t> calculatedOutHeightMinusOne =
1136 idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD);
1137 if (!calculatedOutHeightMinusOne.has_value())
1139 "expected (input_height - 1) * scale_y_n - offset_y + "
1141 <<
"to be wholly divisible by scale_y_d, got ((" << ih
1142 <<
" - 1) * " << scaleYN <<
" - " << offsetY <<
" + " << borderY
1143 <<
") / " << scaleYD;
1144 const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1;
1145 if (oh != ShapedType::kDynamic && calculatedOutHeight != oh)
1147 "calculated output height did not match expected: ")
1148 <<
"calculated=" << calculatedOutHeight <<
", expected=" << oh;
1151 if (iw != ShapedType::kDynamic) {
1152 const std::optional<int64_t> calculatedOutWidthMinusOne =
1153 idivCheck((iw - 1) * scaleXN - offsetX + borderX, scaleXD);
1154 if (!calculatedOutWidthMinusOne.has_value())
1156 "expected (input_width - 1) * scale_x_n - offset_x + "
1158 <<
"to be wholly divisible by scale_x_d, got ((" << iw
1159 <<
" - 1) * " << scaleXN <<
" - " << offsetX <<
" + " << borderX
1160 <<
") / " << scaleXD;
1161 const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1;
1162 if (ow != ShapedType::kDynamic && calculatedOutWidth != ow)
1163 return op->
emitOpError(
"calculated output width did not match expected: ")
1164 <<
"calculated=" << calculatedOutWidth <<
", expected=" << ow;
1170LogicalResult checkErrorIfMul(Operation *op) {
1171 auto mul = dyn_cast<tosa::MulOp>(op);
1177 ElementsAttr shift_elem;
1180 int32_t shift = shift_elem.getValues<IntegerAttr>()[0].getInt();
1182 if (inputElemType.isInteger(32)) {
1184 if (shift < 0 || shift > 63)
1186 <<
"requires 0 <= shift && shift <= 63, but got: " << shift;
1191 <<
"requires shift = 0 for all input data types that "
1192 "are not int32_t, but got: "
1199LogicalResult checkErrorIfTable(Operation *op) {
1200 auto table = dyn_cast<tosa::TableOp>(op);
1206 const int tableSize = inputElemType.isInteger(8) ? 256 : 513;
1208 const ShapeAdaptor tableShape(table.getTable().getType());
1209 if (tableShape.hasStaticShape()) {
1210 const auto numElements = tableShape.getNumElements();
1211 if (numElements != tableSize)
1212 return op->
emitOpError() <<
"requires table size of " << tableSize
1213 <<
", got " << numElements;
1219LogicalResult checkErrorIfRescale(Operation *op) {
1220 auto rescale = dyn_cast<tosa::RescaleOp>(op);
1224 auto inputType = llvm::dyn_cast<ShapedType>(rescale.getInput().getType());
1225 auto outputType = llvm::dyn_cast<ShapedType>(rescale.getOutput().getType());
1226 if (!inputType || !outputType || !inputType.getElementType().isInteger() ||
1227 !outputType.getElementType().isInteger())
1230 auto inElemType = inputType.getElementType();
1231 auto outElemType = outputType.getElementType();
1232 auto inWidth = inElemType.getIntOrFloatBitWidth();
1233 auto outWidth = outElemType.getIntOrFloatBitWidth();
1235 bool inputUnsigned = rescale.getInputUnsigned();
1236 bool outputUnsigned = rescale.getOutputUnsigned();
1238 bool scale32 = rescale.getScale32();
1239 auto roundingMode = rescale.getRoundingMode();
1242 if (scale32 && inWidth == 48)
1243 return op->
emitOpError() <<
"scale32 is not allowed with 48-bit input.";
1246 if (!scale32 && roundingMode == RoundingMode::DOUBLE_ROUND)
1248 <<
"DOUBLE_ROUND is only allowed with scale32=true.";
1251 if (inputUnsigned && outputUnsigned)
1252 return op->
emitOpError() <<
"input and output cannot be both unsigned.";
1255 if (outWidth == 32 && inputUnsigned)
1257 <<
"i32 output type is not allowed with unsigned input.";
1260 if (inWidth == 32 && outputUnsigned)
1262 <<
"i32 input type is not allowed with unsigned output.";
1265 if (inWidth == 48 && outputUnsigned)
1267 <<
"i48 input type is not allowed with unsigned output.";
1270 if (inWidth == 48 && inputUnsigned)
1271 return op->
emitOpError() <<
"i48 input type cannot be unsigned.";
1274 if (inWidth == 32 && inputUnsigned)
1275 return op->
emitOpError() <<
"i32 input type cannot be unsigned.";
1278 if (outWidth == 32 && outputUnsigned)
1279 return op->
emitOpError() <<
"i32 output type cannot be unsigned.";
1284LogicalResult checkErrorIfPad(Operation *op) {
1285 auto pad = dyn_cast<tosa::PadOp>(op);
1289 DenseIntElementsAttr paddingAttr;
1294 for (
const APInt &val : paddingAttr.getValues<APInt>()) {
1295 if (val.getSExtValue() < 0)
1296 return op->
emitOpError() <<
"padding value must all be non-negative, got "
1297 << val.getSExtValue();
1303LogicalResult checkErrorIfReshape(Operation *op) {
1304 auto reshapeOp = dyn_cast<tosa::ReshapeOp>(op);
1308 SmallVector<int64_t> shapeValues;
1314 return op->
emitOpError(
"shape input contains inferable dimension (")
1317 "which does not conform to the TOSA specification";
1322LogicalResult checkErrorIfSlice(Operation *op) {
1323 auto sliceOp = dyn_cast<tosa::SliceOp>(op);
1327 SmallVector<int64_t> startValues;
1328 SmallVector<int64_t> sizeValues;
1330 sliceOp.getStart().getDefiningOp(), startValues);
1331 const bool hasSizeValues =
1335 return op->
emitOpError(
"start input contains inferable dimension (")
1337 <<
") which does not conform to the TOSA specification";
1339 return op->
emitOpError(
"size input contains inferable dimension (")
1342 "does not conform to the TOSA specification";
1347static bool isOpIsolatedWithinRegion(Operation *op, Region *region) {
1348 return llvm::all_of(op->
getOperands(), [&](
auto operand) {
1349 Region *operandRegion = operand.getParentRegion();
1350 return operandRegion && region->isAncestor(operandRegion);
1354static LogicalResult isRegionIsolatedFromAbove(Region ®ionToCheck) {
1355 bool noLiveInValue =
true;
1356 regionToCheck.
walk([&noLiveInValue, ®ionToCheck](Operation *op) {
1357 if (!isOpIsolatedWithinRegion(op, ®ionToCheck)) {
1358 noLiveInValue =
false;
1363 return noLiveInValue ?
success() : failure();
1366LogicalResult checkIsolatedRegion(Operation *op, Region ®ionToCheck,
1367 StringRef regionName) {
1368 if (succeeded(isRegionIsolatedFromAbove(regionToCheck)))
1371 <<
"is not conformant to the TOSA specification. It requires the '"
1372 << regionName <<
"' region is isolated from above.\n";
1375LogicalResult checkErrorIfCondIf(Operation *op) {
1376 auto ifOp = dyn_cast<tosa::IfOp>(op);
1409 if (
failed(checkIsolatedRegion(op, ifOp.getThenGraph(),
"then")) ||
1410 failed(checkIsolatedRegion(op, ifOp.getElseGraph(),
"else")))
1415LogicalResult checkErrorIfWhileLoop(Operation *op) {
1416 auto whileOp = dyn_cast<tosa::WhileOp>(op);
1420 if (
failed(checkIsolatedRegion(op, whileOp.getCondGraph(),
"cond")) ||
1421 failed(checkIsolatedRegion(op, whileOp.getBodyGraph(),
"body")))
1426LogicalResult checkErrorIfScatter(Operation *op) {
1427 auto scatterOp = dyn_cast<tosa::ScatterOp>(op);
1432 DenseIntElementsAttr indicesAttr;
1436 auto const indicesType =
1437 dyn_cast<ShapedType>(scatterOp.getIndices().getType());
1438 if (!indicesType || !indicesType.hasRank()) {
1444 op->
emitOpError(
"indices values contain duplicates");
1451LogicalResult TosaValidation::applyErrorIfCheck(Operation *op) {
1452 if (
failed(checkErrorIfResize(op)) ||
failed(checkErrorIfMul(op)) ||
1453 failed(checkErrorIfTable(op)) ||
failed(checkErrorIfRescale(op)) ||
1454 failed(checkErrorIfPad(op)) ||
failed(checkErrorIfReshape(op)) ||
1455 failed(checkErrorIfSlice(op)) ||
failed(checkErrorIfCondIf(op)) ||
1456 failed(checkErrorIfWhileLoop(op)) ||
failed(checkErrorIfScatter(op)))
1461LogicalResult TosaValidation::applyFunctionSignatureCheck(func::FuncOp op) {
1462 const auto isShapeType = [](Type type) {
return isa<tosa::shapeType>(type); };
1463 if (llvm::any_of(op.getArgumentTypes(), isShapeType))
1464 return op.emitOpError()
1465 <<
"Function argument types must be a tensor type to be TOSA "
1466 "compliant, got !tosa.shape type";
1467 if (llvm::any_of(op.getResultTypes(), isShapeType))
1468 return op.emitOpError()
1469 <<
"Function return types must be a tensor type to be TOSA "
1470 "compliant, got !tosa.shape type";
1474LogicalResult TosaValidation::validateValidElementType(Operation *op, Type type,
1475 bool allowUnsigned) {
1476 if (isa<FloatType>(type)) {
1477 if (isa<Float32Type, Float16Type, BFloat16Type, Float8E4M3FNType,
1478 Float8E5M2Type, Float4E2M1FNType, Float6E2M3FNType,
1479 Float6E3M2FNType, Float8E8M0FNUType>(type))
1481 }
else if (
auto intTy = dyn_cast<IntegerType>(type)) {
1482 if (intTy.isSignless()) {
1483 switch (intTy.getWidth()) {
1493 }
else if (allowUnsigned && intTy.isUnsigned()) {
1494 switch (intTy.getWidth()) {
1501 }
else if (isa<tosa::shapeType>(type))
1503 else if (isa<tosa::mxint8Type, tosa::BlockScaledType>(type))
1506 return op->
emitOpError() <<
"is not profile-aligned: element type " << type
1511TosaValidation::validateOperationElementTypes(Operation *op,
1512 bool allowUnsigned) {
1515 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1521 if (
failed(validateValidElementType(op, elementTy, allowUnsigned)))
1525 if (
auto variableOp = dyn_cast<tosa::VariableOp>(op)) {
1527 validateValidElementType(op, variableOp.getType(), allowUnsigned)))
1534void TosaValidation::runOnOperation() {
1535 ModuleOp modOp = getOperation();
1536 TosaDialect *tosaDialect =
getContext().getLoadedDialect<TosaDialect>();
1541 const auto maybeTargetEnv =
1543 if (
failed(maybeTargetEnv))
1544 return signalPassFailure();
1545 targetEnv = *maybeTargetEnv;
1547 const auto functions = modOp.getOps<func::FuncOp>();
1548 if (llvm::any_of(functions, [&](func::FuncOp func) {
1549 return failed(applyFunctionSignatureCheck(func));
1551 return signalPassFailure();
1553 modOp.walk([&](Operation *op) {
1562 const bool allowUnsigned =
1563 !strictOpSpecAlignment && isa<tosa::RescaleOp>(op);
1564 if (
failed(validateOperationElementTypes(op, allowUnsigned)))
1565 return signalPassFailure();
1567 if (strictOpSpecAlignment &&
1569 return signalPassFailure();
1571 if (strictOpSpecAlignment &&
1573 return signalPassFailure();
1575 if (!allowInvalidOpDatatypeCombinations &&
1577 return signalPassFailure();
1581 if (
failed(applyConstantOperandCheck(op)))
1582 signalPassFailure();
1585 if (
failed(applyLevelCheck(op)))
1586 signalPassFailure();
1589 if (
failed(applyAttributeCheck(op)))
1590 signalPassFailure();
1593 if (
failed(applyVariableCheck(op)))
1594 signalPassFailure();
1597 if (strictOpSpecAlignment &&
failed(applyErrorIfCheck(op)))
1598 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.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
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...
result_type_range getResultTypes()
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.
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.