MLIR 24.0.0git
TosaValidation.cpp
Go to the documentation of this file.
1//===- TosaValidation.cpp ------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Validate if TOSA dialect input matches with the specification for given
10// requirements.
11//
12//===----------------------------------------------------------------------===//
13
17
18#include <string>
19#include <type_traits>
20
24#include "mlir/IR/Builders.h"
25#include "mlir/IR/BuiltinOps.h"
26#include "mlir/IR/Matchers.h"
28#include "mlir/Pass/Pass.h"
30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/StringExtras.h"
32#include "llvm/ADT/TypeSwitch.h"
33#include "llvm/Support/FormatVariadic.h"
34
35namespace mlir {
36namespace tosa {
37#define GEN_PASS_DEF_TOSAVALIDATION
38#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
39} // namespace tosa
40} // namespace mlir
41
42using namespace mlir;
43using namespace mlir::tosa;
44
45namespace {
46
47static LogicalResult
48checkConstantOperands(Operation *op, ArrayRef<unsigned int> operandIndices) {
49 for (const auto index : operandIndices) {
50 Attribute attr;
51 if (!matchPattern(op->getOperand(index), m_Constant(&attr))) {
52 return op->emitOpError("expected compile time resolvable constant, but "
53 "got variable value for operand #")
54 << index;
55 }
56 }
57 return success();
58}
59
60static LogicalResult checkConstantOperandMul(Operation *op,
61 const TargetEnv &env) {
62 if (!env.allows(Extension::dynamic) && isa<tosa::MulOp>(op)) {
63 // Check 'shift'
64 return checkConstantOperands(op, {2});
65 }
66 return success();
67}
68
69static LogicalResult checkConstantOperandTable(Operation *op,
70 const TargetEnv &env) {
71 if (!env.allows(Extension::dynamic) && isa<tosa::TableOp>(op)) {
72 // Check 'table'
73 return checkConstantOperands(op, {1});
74 }
75 return success();
76}
77
78static LogicalResult checkConstantOperandPad(Operation *op,
79 const TargetEnv &env) {
80 if (auto padOp = dyn_cast<tosa::PadOp>(op)) {
81 // Assume this op is zero-padding if padConst is not presented
82 if (!env.allows(Extension::dynamic) && padOp.getPadConst())
83 // Check 'pad_const'
84 // Note: 'padding' (operand 1) is not checked as it is a tosa.shape type
85 return checkConstantOperands(op, {2});
86 }
87 return success();
88}
89
90static LogicalResult checkConstantOperandRescale(Operation *op,
91 const TargetEnv &env) {
92 if (!env.allows(Extension::dynamic) && isa<tosa::RescaleOp>(op)) {
93 // Check 'multiplier', 'shift', 'input_zp' and 'output_zp'
94 return checkConstantOperands(op, {1, 2, 3, 4});
95 }
96 return success();
97}
98
99template <typename T>
100static LogicalResult checkConstantOperandConvOps(Operation *op,
101 const TargetEnv &env) {
102 if (!env.allows(Extension::dynamic) && isa<T>(op)) {
103 // Check 'input_zp' and 'weight_zp'
104 return checkConstantOperands(op, {3, 4});
105 }
106 return success();
107}
108
109static LogicalResult checkConstantOperandMatMul(Operation *op,
110 const TargetEnv &env) {
111 if (!env.allows(Extension::dynamic) &&
112 isa<tosa::MatMulOp, tosa::MatMulTOp>(op)) {
113 // Check 'A_zp' and 'B_zp'
114 return checkConstantOperands(op, {2, 3});
115 }
116 return success();
117}
118
119static LogicalResult
120checkConstantOperandRowGatherBlockScaled(Operation *op, const TargetEnv &env) {
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});
126 }
127 return success();
128}
129
130static LogicalResult checkConstantOperandRowGather(Operation *op,
131 const TargetEnv &env) {
132 if (!env.allows(Extension::dynamic) && isa<tosa::RowGatherOp>(op)) {
133 // Check 'row_count'
134 return checkConstantOperands(op, {2});
135 }
136 return success();
137}
138
139static LogicalResult checkConstantOperandAvgPool2d(Operation *op,
140 const TargetEnv &env) {
141 if (!env.allows(Extension::dynamic) && isa<tosa::AvgPool2dOp>(op)) {
142 // Check 'input_zp' and 'output_zp'
143 return checkConstantOperands(op, {1, 2});
144 }
145 return success();
146}
147
148static LogicalResult
149checkConstantOperandAvgPool2dAdaptive(Operation *op, const TargetEnv &env) {
150 if (!env.allows(Extension::dynamic) && isa<tosa::AvgPool2dAdaptiveOp>(op)) {
151 // Check 'input_zp' and 'output_zp'.
152 // Note: 'kernel', 'stride', and 'pad' (operands 3, 4, 5) are not checked
153 // as they are tosa.shape types.
154 return checkConstantOperands(op, {1, 2});
155 }
156 return success();
157}
158
159static LogicalResult checkConstantOperandNegate(Operation *op,
160 const TargetEnv &env) {
161 if (!env.allows(Extension::dynamic) && isa<tosa::NegateOp>(op)) {
162 // Check 'input1_zp' and 'output_zp'
163 return checkConstantOperands(op, {1, 2});
164 }
165 return success();
166}
167
168static LogicalResult checkConstantOperandSilceShape(Operation *op,
169 const TargetEnv &env) {
170 if (!env.allows(Extension::dynamic) && isa<tosa::SliceShapeOp>(op)) {
171 // Check 'start' and 'size'
172 return checkConstantOperands(op, {1, 2});
173 }
174 return success();
175}
176
177//===----------------------------------------------------------------------===//
178// TOSA Validation Pass.
179//===----------------------------------------------------------------------===//
180
181struct TosaValidation : public tosa::impl::TosaValidationBase<TosaValidation> {
182public:
183 explicit TosaValidation() { populateConstantOperandChecks(); }
184
185 explicit TosaValidation(const TosaValidationOptions &options)
186 : TosaValidation() {
187 this->strictOpSpecAlignment = options.strictOpSpecAlignment;
188 this->allowInvalidOpDatatypeCombinations =
189 options.allowInvalidOpDatatypeCombinations;
190 this->validateFunctionSignature = options.validateFunctionSignature;
191 }
192 void runOnOperation() final;
193
194 LogicalResult applyConstantOperandCheck(Operation *op) {
195 for (auto &checker : constCheckers) {
196 if (failed(checker(op, targetEnv)))
197 return failure();
198 }
199 return success();
200 }
201
202 LogicalResult applyFunctionSignatureCheck(func::FuncOp op);
203 LogicalResult applyLevelCheck(Operation *op);
204 LogicalResult applyAttributeCheck(Operation *op);
205
206 // check variable read/write data types against variable declarations
207 LogicalResult applyVariableCheck(Operation *op);
208
209 // check error if conditions
210 LogicalResult applyErrorIfCheck(Operation *op);
211
212private:
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);
231 }
232
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)
237 return op->emitOpError()
238 << "failed level check: " << inputName << " <= " << levelName
239 << " (" << maxLevel << "), got " << calculatedValue;
240 return success();
241 }
242
243 LogicalResult levelCheckKernel(Operation *op, int32_t v,
244 const StringRef inputName) {
245 return levelCheck(op, v, targetEnv.getLevel().MAX_KERNEL, inputName,
246 "MAX_KERNEL");
247 }
248
249 LogicalResult levelCheckStride(Operation *op, int32_t v,
250 const StringRef inputName) {
251 return levelCheck(op, v, targetEnv.getLevel().MAX_STRIDE, inputName,
252 "MAX_STRIDE");
253 }
254
255 LogicalResult levelCheckScale(Operation *op, int32_t v,
256 const StringRef inputName) {
257 return levelCheck(op, v, targetEnv.getLevel().MAX_SCALE, inputName,
258 "MAX_SCALE");
259 }
260
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");
267 }
268
269 // Perform the Level Rank check on the tensor type.
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)) {
274 if (!type.hasRank())
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";
279 }
280 return success();
281 }
282
283 // Perform the Level Rank check on the tensor value.
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);
288 }
289
290 // Perform the Level tensor size check on the tensor type.
291 LogicalResult levelCheckSize(Operation *op, const Type &typeToCheck,
292 const StringRef operandOrResult);
293
294 // Perform the Level tensor size check on the tensor value.
295 LogicalResult levelCheckSize(Operation *op, const Value &v,
296 const StringRef operandOrResult) {
297 return levelCheckSize(op, v.getType(), operandOrResult);
298 }
299
300 // Perform the Level shape length check on a value.
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)
305 return op->emitOpError()
306 << "failed shape type level check: " << typeToCheck
307 << " exceeds MAX_SHAPE_LEN";
308 }
309 return success();
310 }
311
312 // Level check sizes of all operands and results of the operation.
313 template <typename T>
314 LogicalResult levelCheckSizes(T tosaOp) {
315 auto op = tosaOp.getOperation();
316 for (auto v : op->getOperands()) {
317 if (failed(levelCheckSize(op, v, "operand")))
318 return failure();
319 }
320
321 for (auto v : op->getResults()) {
322 if (failed(levelCheckSize(op, v, "result")))
323 return failure();
324 }
325 return success();
326 }
327
328 // Level check ranks of all operands, attribute and results of the operation.
329 template <typename T>
330 LogicalResult levelCheckRanks(T tosaOp) {
331 auto op = tosaOp.getOperation();
332 const TosaLevel tosaLevel = targetEnv.getLevel();
333 for (auto v : op->getOperands()) {
334 if (failed(levelCheckRank(op, v, "operand", tosaLevel.MAX_RANK)))
335 return failure();
336 }
337
338 for (auto v : op->getResults()) {
339 if (failed(levelCheckRank(op, v, "result", tosaLevel.MAX_RANK)))
340 return failure();
341 }
342 return success();
343 }
344 // Level check shape lengths of all operands and results of an operation that
345 // are tosa.shape type.
346 template <typename T>
347 LogicalResult levelCheckShapeLengths(T tosaOp) {
348 for (const auto &v : tosaOp->getOperands()) {
349 if (failed(levelCheckShapeLength(tosaOp, v.getType(), "operand")))
350 return failure();
351 }
352 for (const auto &v : tosaOp->getResults()) {
353 if (failed(levelCheckShapeLength(tosaOp, v.getType(), "result")))
354 return failure();
355 }
356
357 return success();
358 }
359
360 // Level check ranks and sizes.
361 LogicalResult levelCheckRanksAndSizes(Operation *op);
362
363 // Pool Op: level check kernel/stride/pad values
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"))) {
369 return failure();
370 }
371 }
372 for (auto s : poolOp.getStride()) {
373 if (failed(levelCheckStride(op, s, "stride"))) {
374 return failure();
375 }
376 }
377 for (auto p : poolOp.getPad()) {
378 if (failed(levelCheckKernel(op, p, "pad"))) {
379 return failure();
380 }
381 }
382 }
383 return success();
384 }
385
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>;
390
391 template <typename T, typename std::enable_if<IsSupportedAdaptivePoolOp<T>,
392 int>::type = 0>
393 LogicalResult levelCheckAdaptivePool(Operation *op) {
394 auto poolOp = dyn_cast<T>(op);
395 if (!poolOp)
396 return success();
397
398 SmallVector<int64_t> kernelValues;
399 if (tosa::getConstShapeValues(poolOp.getKernel().getDefiningOp(),
400 kernelValues)) {
401 for (const auto k : kernelValues)
402 if (failed(levelCheckKernel(op, k, "kernel")))
403 return failure();
404 }
405
406 SmallVector<int64_t> strideValues;
407 if (tosa::getConstShapeValues(poolOp.getStride().getDefiningOp(),
408 strideValues)) {
409 for (const auto s : strideValues)
410 if (failed(levelCheckStride(op, s, "stride")))
411 return failure();
412 }
413
414 SmallVector<int64_t> padValues;
415 if (tosa::getConstShapeValues(poolOp.getPad().getDefiningOp(), padValues)) {
416 for (const auto p : padValues)
417 if (failed(levelCheckKernel(op, p, "pad")))
418 return failure();
419 }
420
421 return success();
422 }
423
424 // Conv Op: level check dilation/stride/pad values
425 template <typename T>
426 LogicalResult levelCheckConv(Operation *op) {
427 if (auto convOp = dyn_cast<T>(op)) {
428
429 for (auto k : convOp.getDilation()) {
430 if (failed(levelCheckKernel(op, k, "dilation"))) {
431 return failure();
432 }
433 }
434 for (auto p : convOp.getPad()) {
435 if (failed(levelCheckKernel(op, p, "pad"))) {
436 return failure();
437 }
438 }
439 for (auto s : convOp.getStride()) {
440 if (failed(levelCheckStride(op, s, "stride"))) {
441 return failure();
442 }
443 }
444 auto dilation = convOp.getDilation();
445 if (ShapedType weightType =
446 dyn_cast<ShapedType>(op->getOperand(1).getType())) {
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],
454 "dilation_x * KW")))
455 return failure();
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],
464 "dilation_x * KW")))
465 return failure();
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],
472 "dilation_x * KW")))
473 return failure();
474 }
475 }
476 }
477 return success();
478 }
479
480 LogicalResult levelCheckConv2DBlockScaled(Operation *op) {
481 auto convOp = dyn_cast<Conv2DBlockScaledOp>(op);
482 if (!convOp)
483 return success();
484
485 SmallVector<int64_t> padValues;
486 if (tosa::getConstShapeValues(convOp.getPad().getDefiningOp(), padValues)) {
487 for (const auto p : padValues)
488 if (failed(levelCheckKernel(op, p, "pad <= MAX_KERNEL")))
489 return failure();
490 }
491
492 SmallVector<int64_t> strideValues;
493 if (tosa::getConstShapeValues(convOp.getStride().getDefiningOp(),
494 strideValues)) {
495 for (const auto s : strideValues)
496 if (failed(levelCheckKernel(op, s, "stride <= MAX_KERNEL")))
497 return failure();
498 }
499
500 SmallVector<int64_t> dilationValues;
501 if (tosa::getConstShapeValues(convOp.getDilation().getDefiningOp(),
502 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;
511
512 if (!ShapedType::isDynamic(KH) &&
513 failed(levelCheckKernel(op, dilationValues[0] * KH,
514 "dilation_y * KH <= MAX_KERNEL)")))
515 return failure();
516
517 if (!ShapedType::isDynamic(KW) &&
518 failed(levelCheckKernel(op, dilationValues[1] * KW,
519 "dilation_x * KW <= MAX_KERNEL)")))
520 return failure();
521 }
522
523 return success();
524 }
525
526 // FFT op: level check H, W in input shape [N,H,W]
527 template <typename T>
528 LogicalResult levelCheckFFT(Operation *op) {
529 if (isa<T>(op)) {
530 for (auto v : op->getOperands()) {
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"))) {
536 return failure();
537 }
538 }
539 }
540 }
541 return success();
542 }
543
544 // TransposeConv2d op: level check kH/kW, outpad, and stride
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);
551 // level check kernel sizes for kH and KW
552 if (failed(levelCheckKernel(op, shape[1], "KH")) ||
553 failed(levelCheckKernel(op, shape[2], "KW"))) {
554 return failure();
555 }
556 }
557 for (auto p : transpose.getOutPad()) {
558 if (failed(levelCheckKernel(op, p, "pad"))) {
559 return failure();
560 }
561 }
562 for (auto s : transpose.getStride()) {
563 if (failed(levelCheckStride(op, s, "stride"))) {
564 return failure();
565 }
566 }
567 }
568 return success();
569 }
570
571 // Resize op: level check max scales
572 LogicalResult levelCheckResize(Operation *op) {
573 if (auto resize = dyn_cast<tosa::ResizeOp>(op)) {
574 SmallVector<int64_t> scale;
575 if (!tosa::getConstShapeValues(resize.getScale().getDefiningOp(),
576 scale)) {
577 return failure();
578 }
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];
583 if (failed(
584 levelCheckScale(op, scaleYN / scaleYD, "scale_y_n/scale_y_d")) ||
585 failed(
586 levelCheckScale(op, scaleXN / scaleXD, "scale_x_n/scale_x_d"))) {
587 return failure();
588 }
589 }
590 return success();
591 }
592
593 // Recursively perform a bottom-up search to determine the maximum nesting
594 // depth, starting from a specific operation and continuing up to the function
595 // or module scope. Tosa nesting_depth starts at 0 and increments by one each
596 // time a new nested `region` is encountered.
597 static void getMaxNestedDepth(Operation *op, int32_t &depth) {
598 if (isa<mlir::func::FuncOp>(op) || isa<ModuleOp>(op))
599 return;
600
601 op = op->getParentOp();
602 if (!op)
603 return;
604
605 depth++;
606 getMaxNestedDepth(op, depth);
607 }
608
609 LogicalResult levelCheckMaxNesting(Operation *op) {
610 int32_t maxNestedDepth = 0;
611 getMaxNestedDepth(op, maxNestedDepth);
612
613 const int32_t maxNestingLevel = targetEnv.getLevel().MAX_NESTING;
614 if (maxNestedDepth >= maxNestingLevel)
615 return op->emitOpError()
616 << "failed level check: tosa_nesting_depth < MAX_NESTING" << " ("
617 << maxNestingLevel << "), got " << maxNestedDepth;
618 return success();
619 }
620
621 LogicalResult levelCheckListSize(Operation *op) {
622 if (auto concat = dyn_cast<tosa::ConcatOp>(op)) {
623 return levelCheckListSize(op, concat.getInput1().size(), "input1");
624 }
625 if (auto custom = dyn_cast<tosa::CustomOp>(op)) {
626 if (failed(levelCheckListSize(op, custom.getInputList().size(),
627 "input_list")) ||
628 failed(levelCheckListSize(op, custom.getOutputList().size(),
629 "output_list"))) {
630 return failure();
631 }
632 }
633 if (auto condIf = dyn_cast<tosa::IfOp>(op)) {
634 if (failed(
635 levelCheckListSize(op, condIf.getInputList().size(), "inputs")) ||
636 failed(levelCheckListSize(op, condIf.getOutputList().size(),
637 "outputs"))) {
638 return failure();
639 }
640 }
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"))) {
644 return failure();
645 }
646 }
647 if (auto concat_shape = dyn_cast<tosa::ConcatShapeOp>(op))
648 return levelCheckListSize(op, concat_shape.getInput().size(), "input");
649 return success();
650 }
651
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)) {
656 op->emitOpError()
657 << "failed attribute check: rounding_mode = DOUBLE_ROUND "
658 << "requires extension [doubleround]";
659 return failure();
660 }
661 if (rescale.getRoundingMode() == RoundingMode::INEXACT_ROUND &&
662 !targetEnv.allows(Extension::inexactround)) {
663 op->emitOpError()
664 << "failed attribute check: rounding_mode = INEXACT_ROUND "
665 << "requires extension [inexactround]";
666 return failure();
667 }
668 }
669 return success();
670 }
671
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() &&
677 !(targetVersion.isBackwardsCompatibleWith(minRequiredVersion)))
678 return op->emitOpError()
679 << "failed attribute check: CAST attribute input_unsigned "
680 << "requires version 1.1.draft"
681 << " (got " << stringifyVersion(targetVersion) << ") ";
682 }
683 return success();
684 }
685
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);
694
695 SmallVector<
696 std::function<LogicalResult(Operation *, const tosa::TargetEnv &)>>
697 constCheckers;
699 TosaProfileCompliance profileComp;
700 tosa::TargetEnv targetEnv;
701};
702
703template <>
704LogicalResult TosaValidation::levelCheckRanks(tosa::ArgMaxOp tosaOp) {
705 auto *op = tosaOp.getOperation();
706 if (failed(levelCheckRank(op, tosaOp.getInput(), "operand",
707 targetEnv.getLevel().MAX_RANK)))
708 return failure();
709
710 // rank(output) = rank(input) - 1
711 if (failed(levelCheckRank(op, tosaOp.getOutput(), "result",
712 targetEnv.getLevel().MAX_RANK - 1)))
713 return failure();
714
715 return success();
716}
717
718template <>
719LogicalResult TosaValidation::levelCheckRanks(tosa::IfOp tosaOp) {
720 auto *op = tosaOp.getOperation();
721
722 // Only the condition input has rank limitation.
723 if (failed(levelCheckRank(op, tosaOp.getCondition(), "operand",
724 targetEnv.getLevel().MAX_RANK)))
725 return failure();
726
727 return success();
728}
729
730template <>
731LogicalResult TosaValidation::levelCheckRanks(tosa::VariableOp tosaOp) {
732 auto *op = tosaOp.getOperation();
733 auto variableType = getVariableType(tosaOp);
734 if (failed(levelCheckRank(op, variableType, "variable type",
735 targetEnv.getLevel().MAX_RANK)))
736 return failure();
737
738 return success();
739}
740
741template <>
742LogicalResult TosaValidation::levelCheckSizes(tosa::VariableOp tosaOp) {
743 auto *op = tosaOp.getOperation();
744 auto variableType = getVariableType(tosaOp);
745 if (failed(levelCheckSize(op, variableType, "variable type")))
746 return failure();
747
748 return success();
749}
750
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)))) \
755 return failure(); \
756 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
757 return failure(); \
758 }
759
760#define CHECK_SIZES(tosaOp) \
761 if (isa<tosa::tosaOp##Op>(op)) { \
762 if (failed(levelCheckSizes(cast<tosa::tosaOp##Op>(op)))) \
763 return failure(); \
764 }
765
766#define CHECK_SHAPE_LEN(tosaOp) \
767 if (isa<tosa::tosaOp##Op>(op)) { \
768 if (failed(levelCheckShapeLengths(cast<tosa::tosaOp##Op>(op)))) \
769 return failure(); \
770 }
771
772 // Tensor Operators
773 CHECK_RANKS_AND_SIZES(ArgMax);
774 // Activation Functions
777 CHECK_RANKS_AND_SIZES(Sigmoid);
779 // Elementwise Binary Operators
781 CHECK_RANKS_AND_SIZES(ArithmeticRightShift);
782 CHECK_RANKS_AND_SIZES(BitwiseAnd);
783 CHECK_RANKS_AND_SIZES(BitwiseOr);
784 CHECK_RANKS_AND_SIZES(BitwiseXor);
785 CHECK_RANKS_AND_SIZES(IntDiv);
786 CHECK_RANKS_AND_SIZES(LogicalAnd);
787 CHECK_RANKS_AND_SIZES(LogicalLeftShift);
788 CHECK_RANKS_AND_SIZES(LogicalRightShift);
789 CHECK_RANKS_AND_SIZES(LogicalOr);
790 CHECK_RANKS_AND_SIZES(LogicalXor);
791 CHECK_RANKS_AND_SIZES(Maximum);
792 CHECK_RANKS_AND_SIZES(Minimum);
797 // Elementwise Unary Operators
799 CHECK_RANKS_AND_SIZES(BitwiseNot);
806 CHECK_RANKS_AND_SIZES(LogicalNot);
807 CHECK_RANKS_AND_SIZES(Negate);
808 CHECK_RANKS_AND_SIZES(Reciprocal);
811 // Elementwise Ternary Operators
812 CHECK_RANKS_AND_SIZES(Select);
813 // Comparison Operators
815 CHECK_RANKS_AND_SIZES(Greater);
816 CHECK_RANKS_AND_SIZES(GreaterEqual);
817 // Reduction Operators
818 CHECK_RANKS_AND_SIZES(ReduceAll);
819 CHECK_RANKS_AND_SIZES(ReduceAny);
820 CHECK_RANKS_AND_SIZES(ReduceMax);
821 CHECK_RANKS_AND_SIZES(ReduceMin);
822 CHECK_RANKS_AND_SIZES(ReduceProduct);
823 CHECK_RANKS_AND_SIZES(ReduceSum);
824 // Data Layout Operators
825 CHECK_RANKS_AND_SIZES(Concat);
827 CHECK_RANKS_AND_SIZES(Reshape);
828 CHECK_RANKS_AND_SIZES(ReshapeBlockScaled);
829 CHECK_RANKS_AND_SIZES(Reverse);
832 CHECK_RANKS_AND_SIZES(Transpose);
833 // Type Conversion
835 CHECK_RANKS_AND_SIZES(CastFromBlockScaled);
836 CHECK_RANKS_AND_SIZES(CastToBlockScaled);
837 CHECK_RANKS_AND_SIZES(Rescale);
838 // Data Nodes
840 CHECK_RANKS_AND_SIZES(Identity);
841 // Control Flow Operators
843 // Variable Operators
844 CHECK_RANKS_AND_SIZES(Variable);
845 CHECK_RANKS_AND_SIZES(VariableWrite);
846 CHECK_RANKS_AND_SIZES(VariableRead);
847 // Shape Operators
849
850 // For the following operators, check whether the size of each tensor
851 // operand is valid in a given Level.
852
853 // Tensor Operators
854 CHECK_SIZES(AvgPool2d);
855 CHECK_SIZES(AvgPool2dAdaptive);
856 CHECK_SIZES(Conv2D);
857 CHECK_SIZES(Conv2DBlockScaled);
858 CHECK_SIZES(Conv3D);
859 CHECK_SIZES(DepthwiseConv2D);
860 CHECK_SIZES(TransposeConv2D);
861 CHECK_SIZES(FFT2d);
862 CHECK_SIZES(MatMul);
863 CHECK_SIZES(MatMulT);
864 CHECK_SIZES(MatmulTBlockScaled);
865 CHECK_SIZES(MaxPool2d);
866 CHECK_SIZES(MaxPool2dAdaptive);
867 CHECK_SIZES(RFFT2d);
868 // Scatter/Gather Operators
870 CHECK_SIZES(RowGather);
871 CHECK_SIZES(Scatter);
872 // Image Operators
873 CHECK_SIZES(Resize);
874 // Custom Operators
875 CHECK_SIZES(Custom);
876 // Control Flow Operators
877 CHECK_SIZES(While);
878 // Shape Operators
879 CHECK_SIZES(ConstShape);
880
881 // For the following operations, check whether the shape length of each
882 // operand is valid given a level.
883
884 // Shape Operators
885 CHECK_SHAPE_LEN(AddShape);
886 CHECK_SHAPE_LEN(AssertEqualShape);
887 CHECK_SHAPE_LEN(ConcatShape);
888 CHECK_SHAPE_LEN(DivCeilShape);
889 CHECK_SHAPE_LEN(DivFloorShape);
890 CHECK_SHAPE_LEN(Exp2Shape);
891 CHECK_SHAPE_LEN(Log2CeilShape);
892 CHECK_SHAPE_LEN(Log2FloorShape);
893 CHECK_SHAPE_LEN(MaxShape);
894 CHECK_SHAPE_LEN(MinShape);
895 CHECK_SHAPE_LEN(ModShape);
896 CHECK_SHAPE_LEN(MulShape);
897 CHECK_SHAPE_LEN(SliceShape);
898 CHECK_SHAPE_LEN(SubShape);
899
900#undef CHECK_RANKS_AND_SIZES
901#undef CHECK_SIZES
902#undef CHECK_SHAPE_LEN
903 return success();
904}
905
906// Perform the Level tensor size check on the tensor type.
907LogicalResult TosaValidation::levelCheckSize(Operation *op,
908 const Type &typeToCheck,
909 const StringRef operandOrResult) {
910 if (ShapedType type = dyn_cast<ShapedType>(typeToCheck)) {
911 if (!type.hasRank())
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);
918 if (targetVersion.isBackwardsCompatibleWith(minRequiredVersion) &&
919 dimIsDynamic)
920 // TOSA 1.1 and above supports dynamic dimensions, however, they must be
921 // resolved at backend compile time. Runtime dynamism is not currently
922 // supported. Checking this requirement is met is delegated to backends.
923 return success();
924
925 // When targeting TOSA 1.0 or below, dynamic dims are not supported
926 if (dimIsDynamic)
927 return op->emitOpError() << "failed level check: " << operandOrResult
928 << " shape dimension cannot be dynamic when"
929 << " targeting TOSA specification version 1.0"
930 << " or below";
931 }
932
933 int64_t elementBits = tosa::getBitWidth(getElementTypeOrSelf(type));
934 int64_t elementBytes = std::max(INT64_C(1), elementBits / 8);
935 int64_t size = elementBytes * type.getNumElements();
936
937 // According to 1.11. Tensor Definitions of Tosa spec, the value of
938 // tensor_size_t is 1 << MAX_LOG2_SIZE) - 1 where MAX_LOG2_SIZE is
939 // defined in 1.7. Levels.
940 // For each tensor, the number of tensor elements multiplied by the
941 // element size in bytes must be representable as a tensor_size_t.
942 const int64_t maxSize =
943 (INT64_C(1) << targetEnv.getLevel().MAX_LOG2_SIZE) - 1;
944 if (size > maxSize)
945 return op->emitOpError()
946 << "failed level check: " << operandOrResult
947 << " tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)";
948 }
949 return success();
950}
951
952LogicalResult TosaValidation::applyLevelCheck(Operation *op) {
953 if (targetEnv.getLevel() == TOSA_LEVEL_NONE) {
954 // no need to do level checks
955 return success();
956 }
957
958 // check rank and sizes early so later checks can assume shaped operands
959 if (failed(levelCheckRanksAndSizes(op)))
960 return failure();
961
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))) {
973 return failure();
974 }
975
976 // level check MAX_TENSOR_LIST_SIZE
977 if (failed(levelCheckListSize(op))) {
978 return failure();
979 }
980
981 if (isa<tosa::IfOp>(op) || isa<tosa::WhileOp>(op)) {
982 if (failed(levelCheckMaxNesting(op))) {
983 return failure();
984 }
985 }
986
987 return success();
988}
989
990LogicalResult TosaValidation::applyAttributeCheck(Operation *op) {
991 if (failed(attributeCheckRescale(op)))
992 return failure();
993 if (failed(attributeCheckCast(op)))
994 return failure();
995 return success();
996}
997
998inline bool CompatibleTypes(const mlir::Type &type,
999 const mlir::Type &declaredType) {
1000 // for now, simply use type equality comparison
1001 return type == declaredType;
1002}
1003
1004LogicalResult TosaValidation::CheckVariable(Operation *op) {
1005 if (auto variableOp = dyn_cast<mlir::tosa::VariableOp>(op)) {
1006 mlir::StringAttr nameAttr = variableOp.getNameAttr();
1007
1008 if (variablesMap.count(nameAttr))
1009 return op->emitOpError() << "name has already been declared";
1010
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);
1016
1017 variablesMap[nameAttr] = variableType;
1018 }
1019
1020 return success();
1021}
1022
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";
1032
1033 auto varType = variablesMap[nameAttr];
1034
1035 for (auto v : op->getOperands()) {
1036 auto type = v.getType();
1037 if (!CompatibleTypes(type, varType))
1038 return op->emitOpError() << "operand type does not equal variable type";
1039 }
1040
1041 for (auto v : op->getResults()) {
1042 auto type = v.getType();
1043 if (!CompatibleTypes(type, varType))
1044 return op->emitOpError() << "result type does not equal variable type";
1045 }
1046 }
1047
1048 return success();
1049}
1050
1051LogicalResult TosaValidation::applyVariableCheck(Operation *op) {
1052 if (failed(CheckVariable(op)) || failed(CheckVariableReadOrWrite(op)))
1053 return failure();
1054 return success();
1055}
1056
1057LogicalResult checkErrorIfResize(Operation *op) {
1058 auto resize = dyn_cast<tosa::ResizeOp>(op);
1059 if (!resize)
1060 return success();
1061
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());
1068
1069 if (!inputType || !outputType)
1070 return op->emitOpError("expect ranked input/output tensor");
1071
1072 // Ensure the image size is supported by GPU APIs and that for integer
1073 // implementations, position * stride does not overflow int32_t.
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)
1080 return op->emitOpError(
1081 "expect input/output height/width dims to be < 16384, ")
1082 << "got [OH, OW, IH, IW] = " << sizes;
1083 }
1084
1085 SmallVector<int64_t> scale;
1086 if (!tosa::getConstShapeValues(resize.getScale().getDefiningOp(), scale))
1087 return failure();
1088
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];
1093
1094 // Ensure scale values don't overflow int32 accumulator
1095 if (scaleYN > (1 << 11) || scaleXN > (1 << 11))
1096 return op->emitOpError(
1097 "expect all scale numerator values to be <= (1 << 11), "
1098 "got scale_y_n=")
1099 << scaleYN << ", scale_x_n=" << scaleXN;
1100
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;
1104
1105 SmallVector<int64_t> offset;
1106 SmallVector<int64_t> border;
1107 if (!tosa::getConstShapeValues(resize.getOffset().getDefiningOp(), offset) ||
1108 !tosa::getConstShapeValues(resize.getBorder().getDefiningOp(), border))
1109 return failure();
1110
1111 const int64_t offsetY = offset[0];
1112 const int64_t offsetX = offset[1];
1113 // Set a consistent lower limit of 1/16 downscale to simplify
1114 // implementations
1115 if (offsetY < -scaleYN || offsetY >= 16 * scaleYN)
1116 return op->emitOpError(
1117 "expect offsetY / scaleYNumerator to be in range [-1, 16), got ")
1118 << offsetY << "/" << scaleYN;
1119 if (offsetX < -scaleXN || offsetX >= 16 * scaleXN)
1120 return op->emitOpError(
1121 "expect offsetX / scaleXNumerator to be in range [-1, 16), got ")
1122 << offsetX << "/" << scaleXN;
1123
1124 const int64_t borderY = border[0];
1125 const int64_t borderX = border[1];
1126 if (borderY < -16 * scaleYN || borderY >= scaleYN)
1127 return op->emitOpError(
1128 "expect borderY / scaleYNumerator to be in range [-16, 1), got ")
1129 << borderY << "/" << scaleYN;
1130 if (borderX < -16 * scaleXN || borderX >= scaleXN)
1131 return op->emitOpError(
1132 "expect borderX / scaleXNumerator to be in range [-16, 1), got ")
1133 << borderX << "/" << scaleXN;
1134
1135 // The following section of code is mostly duplicated with ResizeOp::verify().
1136 //
1137 // In TOSA specification, we do not support broadcast behavior.
1138 // However, there is a rewrite pattern to materialize broadcast ResizeOp.
1139 // It makes invalid TOSA ResizeOp into valid one. To avoid breaking
1140 // existing code, we keep the rewrite pattern untouched. So, we need
1141 // loose the checking in ResizeOp::verify() to support broadcast ResizeOp.
1142 //
1143 // Here is a strict checking to conform TOSA specification.
1144 // FIXME: Remove the duplicated checkings when broadcast ResizeOp is removed.
1145 auto idivCheck = [](const int64_t lhs,
1146 const int64_t rhs) -> std::optional<int64_t> {
1147 if (lhs % rhs != 0)
1148 return std::nullopt;
1149 return lhs / rhs;
1150 };
1151
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);
1156
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())
1161 return op->emitOpError(
1162 "expected (input_height - 1) * scale_y_n - offset_y + "
1163 "border_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)
1169 return op->emitOpError(
1170 "calculated output height did not match expected: ")
1171 << "calculated=" << calculatedOutHeight << ", expected=" << oh;
1172 }
1173
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())
1178 return op->emitOpError(
1179 "expected (input_width - 1) * scale_x_n - offset_x + "
1180 "border_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;
1188 }
1189
1190 return success();
1191}
1192
1193LogicalResult checkErrorIfMul(Operation *op) {
1194 auto mul = dyn_cast<tosa::MulOp>(op);
1195 if (!mul)
1196 return success();
1197
1198 // REQUIRE(0 <= shift && shift <= 63);
1199 // REQUIRE(is_same<in_t,int32_t>() || shift == 0);
1200 ElementsAttr shift_elem;
1201 if (!matchPattern(mul.getShift(), m_Constant(&shift_elem)))
1202 return success();
1203 int32_t shift = shift_elem.getValues<IntegerAttr>()[0].getInt();
1204 auto inputElemType = getElementTypeOrSelf(mul.getInput1());
1205 if (inputElemType.isInteger(32)) {
1206 // 0 <= shift <= 63 for int32_t type
1207 if (shift < 0 || shift > 63)
1208 return op->emitOpError()
1209 << "requires 0 <= shift && shift <= 63, but got: " << shift;
1210 } else {
1211 // shift must be 0 for all other types
1212 if (shift != 0)
1213 return op->emitOpError()
1214 << "requires shift = 0 for all input data types that "
1215 "are not int32_t, but got: "
1216 << shift;
1217 }
1218
1219 return success();
1220}
1221
1222LogicalResult checkErrorIfTable(Operation *op) {
1223 auto table = dyn_cast<tosa::TableOp>(op);
1224 if (!table)
1225 return success();
1226
1227 // REQUIRE(length(table) == TABLE_SIZE) where TABLE_SIZE is 256 or 513
1228 const auto inputElemType = getElementTypeOrSelf(table.getInput1().getType());
1229 const int tableSize = inputElemType.isInteger(8) ? 256 : 513;
1230
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;
1237 }
1238
1239 return success();
1240}
1241
1242LogicalResult checkErrorIfRescale(Operation *op) {
1243 auto rescale = dyn_cast<tosa::RescaleOp>(op);
1244 if (!rescale)
1245 return success();
1246
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())
1251 return success();
1252
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();
1260
1261 bool scale32 = rescale.getScale32();
1262 auto roundingMode = rescale.getRoundingMode();
1263
1264 // ERROR_IF(scale32 && is_same<in_t,i48_t>())
1265 if (scale32 && inWidth == 48)
1266 return op->emitOpError() << "scale32 is not allowed with 48-bit input.";
1267
1268 // ERROR_IF(!scale32 && (rounding_mode == DOUBLE_ROUND))
1269 if (!scale32 && roundingMode == RoundingMode::DOUBLE_ROUND)
1270 return op->emitOpError()
1271 << "DOUBLE_ROUND is only allowed with scale32=true.";
1273 // ERROR_IF(input_unsigned && output_unsigned)
1274 if (inputUnsigned && outputUnsigned)
1275 return op->emitOpError() << "input and output cannot be both unsigned.";
1276
1277 // ERROR_IF(is_same<out_t,i32_t>() && input_unsigned)
1278 if (outWidth == 32 && inputUnsigned)
1279 return op->emitOpError()
1280 << "i32 output type is not allowed with unsigned input.";
1281
1282 // ERROR_IF(is_same<in_t,i32_t>() && output_unsigned)
1283 if (inWidth == 32 && outputUnsigned)
1284 return op->emitOpError()
1285 << "i32 input type is not allowed with unsigned output.";
1286
1287 // ERROR_IF(is_same<in_t,i48_t>() && output_unsigned)
1288 if (inWidth == 48 && outputUnsigned)
1289 return op->emitOpError()
1290 << "i48 input type is not allowed with unsigned output.";
1291
1292 // ERROR_IF(is_same<in_t, i48_t> && input_unsigned)
1293 if (inWidth == 48 && inputUnsigned)
1294 return op->emitOpError() << "i48 input type cannot be unsigned.";
1295
1296 // ERROR_IF(is_same<in_t, i32_t> && input_unsigned)
1297 if (inWidth == 32 && inputUnsigned)
1298 return op->emitOpError() << "i32 input type cannot be unsigned.";
1299
1300 // ERROR_IF(is_same<out_t, i32_t> && output_unsigned)
1301 if (outWidth == 32 && outputUnsigned)
1302 return op->emitOpError() << "i32 output type cannot be unsigned.";
1303
1304 return success();
1306
1307LogicalResult checkErrorIfPad(Operation *op) {
1308 auto pad = dyn_cast<tosa::PadOp>(op);
1309 if (!pad)
1310 return success();
1311
1312 DenseIntElementsAttr paddingAttr;
1313 if (!matchPattern(pad.getPadding(), m_Constant(&paddingAttr)))
1314 // Pad verifier will catch this
1315 return success();
1316
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();
1321 }
1322
1323 return success();
1324}
1325
1326LogicalResult checkErrorIfReshape(Operation *op) {
1327 auto reshapeOp = dyn_cast<tosa::ReshapeOp>(op);
1328 if (!reshapeOp)
1329 return success();
1330
1331 SmallVector<int64_t> shapeValues;
1332 if (!tosa::getConstShapeValues(reshapeOp.getShape().getDefiningOp(),
1333 shapeValues))
1334 return success();
1335
1336 if (llvm::is_contained(shapeValues, kInferableDimSize))
1337 return op->emitOpError("shape input contains inferable dimension (")
1339 << ") "
1340 "which does not conform to the TOSA specification";
1341
1342 return success();
1343}
1344
1345LogicalResult checkErrorIfSlice(Operation *op) {
1346 auto sliceOp = dyn_cast<tosa::SliceOp>(op);
1347 if (!sliceOp)
1348 return success();
1349
1350 SmallVector<int64_t> startValues;
1351 SmallVector<int64_t> sizeValues;
1352 const bool hasStartValues = tosa::getConstShapeValues(
1353 sliceOp.getStart().getDefiningOp(), startValues);
1354 const bool hasSizeValues =
1355 tosa::getConstShapeValues(sliceOp.getSize().getDefiningOp(), sizeValues);
1356
1357 if (hasStartValues && llvm::is_contained(startValues, kInferableDimSize))
1358 return op->emitOpError("start input contains inferable dimension (")
1360 << ") which does not conform to the TOSA specification";
1361 if (hasSizeValues && llvm::is_contained(sizeValues, kInferableDimSize))
1362 return op->emitOpError("size input contains inferable dimension (")
1364 << ") which "
1365 "does not conform to the TOSA specification";
1366
1367 return success();
1368}
1369
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);
1374 });
1375}
1376
1377static LogicalResult isRegionIsolatedFromAbove(Region &regionToCheck) {
1378 bool noLiveInValue = true;
1379 regionToCheck.walk([&noLiveInValue, &regionToCheck](Operation *op) {
1380 if (!isOpIsolatedWithinRegion(op, &regionToCheck)) {
1381 noLiveInValue = false;
1382 return WalkResult::interrupt();
1383 }
1384 return WalkResult::advance();
1385 });
1386 return noLiveInValue ? success() : failure();
1387}
1388
1389LogicalResult checkIsolatedRegion(Operation *op, Region &regionToCheck,
1390 StringRef regionName) {
1391 if (succeeded(isRegionIsolatedFromAbove(regionToCheck)))
1392 return success();
1393 return op->emitOpError()
1394 << "is not conformant to the TOSA specification. It requires the '"
1395 << regionName << "' region is isolated from above.\n";
1396}
1397
1398LogicalResult checkErrorIfCondIf(Operation *op) {
1399 auto ifOp = dyn_cast<tosa::IfOp>(op);
1400 if (!ifOp)
1401 return success();
1402
1403 // Currently the dialect supports declaring cond_if operations that
1404 // have then/else regions that reference values from outside these
1405 // regions. According to the specification, all values used by the
1406 // then/else regions must be explicitly declared within the regions.
1407 // Therefore we must check that the then/else regions are
1408 // "isolated from above", in order to be conformant to the
1409 // specification.
1410 //
1411 // Note: the dialect currently supports two styles of syntax for
1412 // declaring "cond_if" operations. We'll refer to these as follows:
1413 //
1414 // Generic:
1415 // %0 = "tosa.cond_if"(%arg0, %arg1, %arg2) ({
1416 // ^bb0(%arg3, %arg4):
1417 // tosa.yield %arg3
1418 // }, {
1419 // ^bb0(%arg3, %arg4):
1420 // tosa.yield %arg4
1421 // })
1422 //
1423 // Simplified:
1424 // %0 = tosa.cond_if %arg2 (%arg3 = %arg0, %arg4 = %arg1) {
1425 // ^bb0(%arg3, %arg4):
1426 // tosa.yield %arg3
1427 // } else {
1428 // ^bb0(%arg3, %arg4):
1429 // tosa.yield %arg4
1430 // }
1431
1432 if (failed(checkIsolatedRegion(op, ifOp.getThenGraph(), "then")) ||
1433 failed(checkIsolatedRegion(op, ifOp.getElseGraph(), "else")))
1434 return failure();
1435 return success();
1436}
1437
1438LogicalResult checkErrorIfWhileLoop(Operation *op) {
1439 auto whileOp = dyn_cast<tosa::WhileOp>(op);
1440 if (!whileOp)
1441 return success();
1442
1443 if (failed(checkIsolatedRegion(op, whileOp.getCondGraph(), "cond")) ||
1444 failed(checkIsolatedRegion(op, whileOp.getBodyGraph(), "body")))
1445 return failure();
1446 return success();
1447}
1448
1449LogicalResult checkErrorIfScatter(Operation *op) {
1450 auto scatterOp = dyn_cast<tosa::ScatterOp>(op);
1451 if (!scatterOp)
1452 return success();
1453
1454 // for constant indices, check that there are no duplicate values
1455 DenseIntElementsAttr indicesAttr;
1456 if (!matchPattern(scatterOp.getIndices(), m_Constant(&indicesAttr)))
1457 return success();
1458
1459 auto const indicesType =
1460 dyn_cast<ShapedType>(scatterOp.getIndices().getType());
1461 if (!indicesType || !indicesType.hasRank()) {
1462 op->emitOpError("expect ranked indices tensor");
1463 return failure();
1464 }
1465
1466 if (!hasUniqueConstantScatterIndices(indicesType, indicesAttr)) {
1467 op->emitOpError("indices values contain duplicates");
1468 return failure();
1469 }
1470
1471 return success();
1472}
1473
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)))
1480 return failure();
1481 return success();
1482}
1483
1484LogicalResult TosaValidation::applyFunctionSignatureCheck(func::FuncOp op) {
1485 // Require tensor type parameters and results
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";
1495
1496 // Validate element types
1497 if (failed(validateOperationElementTypes(op, !strictOpSpecAlignment)))
1498 return failure();
1499
1500 // Level check
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)))
1505 return failure();
1506 if (failed(levelCheckSize(op, argType, inputDesc)))
1507 return failure();
1508 }
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)))
1512 return failure();
1513 if (failed(levelCheckSize(op, resultType, resultDesc)))
1514 return failure();
1515 }
1516
1517 // Explicitly check for no zero dimensions
1518 // Note: This check is not required for TOSA operations since it is mandated
1519 // on construction
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";
1527 }
1528 }
1529
1530 return success();
1531}
1532
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))
1539 return success();
1540 } else if (auto intTy = dyn_cast<IntegerType>(type)) {
1541 if (intTy.isSignless()) {
1542 switch (intTy.getWidth()) {
1543 case 1:
1544 case 4:
1545 case 8:
1546 case 16:
1547 case 32:
1548 case 48:
1549 case 64:
1550 return success();
1551 }
1552 } else if (allowUnsigned && intTy.isUnsigned()) {
1553 switch (intTy.getWidth()) {
1554 case 8:
1555 case 16:
1556 case 32:
1557 return success();
1558 }
1559 }
1560 } else if (isa<tosa::shapeType>(type))
1561 return success();
1562 else if (isa<tosa::mxint8Type, tosa::BlockScaledType>(type))
1563 return success();
1564
1565 return op->emitOpError() << "is not profile-aligned: element type " << type
1566 << " is not legal";
1567}
1568
1569LogicalResult
1570TosaValidation::validateOperationElementTypes(TosaOp op, bool allowUnsigned) {
1571 for (Value operand : op->getOperands()) {
1572 Type elementTy = getElementTypeOrSelf(operand);
1573 if (failed(validateValidElementType(op, elementTy, allowUnsigned)))
1574 return failure();
1575 }
1576
1577 for (Type resultTy : op->getResultTypes()) {
1578 Type elementTy = getElementTypeOrSelf(resultTy);
1579 if (failed(validateValidElementType(op, elementTy, allowUnsigned)))
1580 return failure();
1581 }
1582
1583 if (auto variableOp = dyn_cast<tosa::VariableOp>(*op)) {
1584 if (failed(
1585 validateValidElementType(op, variableOp.getType(), allowUnsigned)))
1586 return failure();
1587 }
1588 return success();
1589}
1590
1591LogicalResult
1592TosaValidation::validateOperationElementTypes(func::FuncOp op,
1593 bool allowUnsigned) {
1594 for (const Type &argType :
1595 llvm::concat<const Type>(op.getArgumentTypes(), op.getResultTypes())) {
1596 const Type elementTy = getElementTypeOrSelf(argType);
1597 if (failed(validateValidElementType(op, elementTy, allowUnsigned)))
1598 return failure();
1599 }
1600
1601 return success();
1602}
1603
1604void TosaValidation::runOnOperation() {
1605 ModuleOp modOp = getOperation();
1606 TosaDialect *tosaDialect = getContext().getLoadedDialect<TosaDialect>();
1607 if (!tosaDialect)
1608 return;
1609
1610 const TargetEnvAttr targetEnvAttr = lookupTargetEnvOrDefault(modOp);
1611 const auto maybeTargetEnv =
1612 tosa::TargetEnv::createTargetEnvFromAttr(targetEnvAttr, modOp.getLoc());
1613 if (failed(maybeTargetEnv))
1614 return signalPassFailure();
1615 targetEnv = *maybeTargetEnv;
1616
1617 const auto functions = modOp.getOps<func::FuncOp>();
1618 if (validateFunctionSignature &&
1619 llvm::any_of(functions, [&](func::FuncOp func) {
1620 return failed(applyFunctionSignatureCheck(func));
1621 }))
1622 return signalPassFailure();
1623
1624 modOp.walk([&](TosaOp op) {
1625 // validate operator element types:
1626 // - rescale operator is allowed to have ui8/ui16/ui32
1627 // operands/results when strictOpSpecAlignment is false
1628 // - perform valid element type check at the beginning to
1629 // protect rest of code against quantized element types
1630 const bool allowUnsigned =
1631 !strictOpSpecAlignment && isa<tosa::RescaleOp>(op);
1632 if (failed(validateOperationElementTypes(op, allowUnsigned)))
1633 return signalPassFailure();
1634
1635 if (strictOpSpecAlignment &&
1636 failed(profileComp.checkProfile(op, targetEnv)))
1637 return signalPassFailure();
1638
1639 if (strictOpSpecAlignment &&
1640 failed(profileComp.checkExtension(op, targetEnv)))
1641 return signalPassFailure();
1642
1643 if (!allowInvalidOpDatatypeCombinations &&
1644 failed(profileComp.checkInvalid(op)))
1645 return signalPassFailure();
1646
1647 // Some uses of TOSA rely on the constant operands of particular
1648 // operations.
1649 if (failed(applyConstantOperandCheck(op)))
1650 signalPassFailure();
1651
1652 // do level checks
1653 if (failed(applyLevelCheck(op)))
1654 signalPassFailure();
1655
1656 // check additional attribute restrictions
1657 if (failed(applyAttributeCheck(op)))
1658 signalPassFailure();
1659
1660 // do variable type checks
1661 if (failed(applyVariableCheck(op)))
1662 signalPassFailure();
1663
1664 // do error if checks
1665 if (strictOpSpecAlignment && failed(applyErrorIfCheck(op)))
1666 signalPassFailure();
1667 });
1668}
1669} // namespace
return success()
lhs
b getContext())
static llvm::ManagedStatic< PassManagerOptions > options
static std::optional< int64_t > idivCheck(const int64_t lhs, const int64_t rhs)
Definition TosaOps.cpp:581
#define CHECK_RANKS_AND_SIZES(tosaOp)
#define CHECK_SIZES(tosaOp)
#define CHECK_SHAPE_LEN(tosaOp)
@ Gather
#define mul(a, b)
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.
Definition Attributes.h:25
An attribute that represents a reference to a dense integer vector or tensor object.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
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...
Definition Region.h:312
Type getType() const
Return the type of this value.
Definition Value.h:105
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
This class represents the capability enabled in the target implementation such as profile,...
Definition TargetEnv.h:119
TosaLevel getLevel() const
Definition TargetEnv.h:136
static FailureOr< TargetEnv > createTargetEnvFromAttr(TargetEnvAttr targetAttr, Location targetEnvAttrLoc)
bool allows(Profile prof) const
Definition TargetEnv.h:139
TosaSpecificationVersion getSpecVersion() const
Definition TargetEnv.h:132
bool isBackwardsCompatibleWith(TosaSpecificationVersion baseVersion) const
Definition TargetEnv.h:69
SmallVector< AffineExpr, 4 > concat(ArrayRef< AffineExpr > a, ArrayRef< AffineExpr > b)
Return the vector that is the concatenation of a and b.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
llvm::SmallString< 4 > stringifyVersion(TosaSpecificationVersion version)
Definition TargetEnv.cpp:25
RankedTensorType getVariableType(VariableOp variableOp)
static constexpr TosaLevel TOSA_LEVEL_NONE
Definition TargetEnv.h:45
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...
Definition TosaOps.h:102
unsigned getBitWidth(Type type)
Definition TosaOps.cpp:633
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.
Definition Matchers.h:490
@ Mul
RHS of mul is always a constant or a symbolic expression.
Definition AffineExpr.h:43
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369