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