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