[Mlir-commits] [mlir] [mlir][tosa] Add integer type conversion pass (PR #232853)
Michael Platings
llvmlistbot at llvm.org
Fri Oct 23 05:48:44 PDT 2026
https://github.com/mplatings updated https://github.com/llvm/llvm-project/pull/232853
>From 2ecb384a9c785794daac3fc81022825caca6e8f5 Mon Sep 17 00:00:00 2001
From: Michael Platings <michael.platings at arm.com>
Date: Wed, 14 Oct 2026 16:01:47 +0100
Subject: [PATCH 1/4] [mlir][tosa] Add integer type conversion pass
Add tosa-convert-integer-types with a list of
source:destination bitwidth mappings, such as 4:8,48:64.
This is useful to facilitate lowering to backends that do not support i4
and/or i48.
Add a host byte-order lit feature to gate tests with little-endian
resource data.
Assisted-by: Codex
---
.../mlir/Dialect/Tosa/Transforms/Passes.h | 2 +
.../mlir/Dialect/Tosa/Transforms/Passes.td | 32 +
.../Dialect/Tosa/Transforms/CMakeLists.txt | 2 +
.../Transforms/TosaConvertIntegerTypes.cpp | 329 +++++++++
.../tosa-convert-integer-types-invalid.mlir | 94 +++
.../tosa-convert-integer-types-mappings.mlir | 239 +++++++
.../tosa-convert-integer-types-options.mlir | 55 ++
.../Tosa/tosa-convert-integer-types.mlir | 640 ++++++++++++++++++
mlir/test/lit.cfg.py | 3 +
9 files changed, 1396 insertions(+)
create mode 100644 mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
create mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
create mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
create mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
create mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
index e33d7c698856ce..c75e0e41ea4255 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
@@ -17,6 +17,8 @@
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
#include "mlir/Pass/Pass.h"
+#include <utility>
+
namespace mlir {
class TypeConverter;
namespace tosa {
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 0ce6c4a059c60d..8241f3c590fdba 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -217,6 +217,38 @@ def TosaGatherScatterHardeningPass
}];
}
+def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types", "ModuleOp"> {
+ let summary = "Convert selected TOSA integer types";
+ let description = [{
+ Converts integer types using `bitwidth-mappings`, a required list of
+ `<source>:<destination>` bitwidth pairs.
+ A typical use case is `bitwidth-mappings=4:8,48:64` which maps i4 and i48 to
+ i8 and i64 respectively.
+
+ Mappings apply once to the original width, preserving signedness and
+ leaving unmapped widths unchanged. Widening sign-extends signed and
+ signless constants and zero-extends unsigned constants. Narrowing truncates
+ without checking for data loss. The caller is responsible for ensuring the
+ conversion is appropriate.
+
+ Conversion covers types, function signatures and attribute payloads
+ throughout the module, including non-TOSA operations.
+ External callers and implementations must use the converted signatures.
+
+ Run after `tosa-validate`: converted IR may not conform to the TOSA
+ specification.
+ }];
+
+ let options = [
+ ListOption<"bitwidthMappings", "bitwidth-mappings",
+ "std::pair<unsigned, unsigned>",
+ "Integer bitwidth mappings <source>:<destination> "
+ "(required, non-empty)">
+ ];
+
+ let dependentDialects = ["func::FuncDialect", "tosa::TosaDialect"];
+}
+
def TosaNarrowI64ToI32Pass : Pass<"tosa-narrow-i64-to-i32", "func::FuncOp"> {
let summary = "Narrow I64 TOSA operations to I32";
let description = [{
diff --git a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
index da23374ed65e1c..9998f963ddbabb 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
@@ -16,6 +16,7 @@ add_mlir_dialect_library(MLIRTosaTransforms
TosaProfileCompliance.cpp
TosaValidation.cpp
TosaNarrowTypes.cpp
+ TosaConvertIntegerTypes.cpp
TosaInputShape.cpp
ADDITIONAL_HEADER_DIRS
@@ -28,6 +29,7 @@ add_mlir_dialect_library(MLIRTosaTransforms
MLIRFuncDialect
MLIRFuncTransformOps
MLIRPass
+ MLIRTensorDialect
MLIRTosaDialect
MLIRTransformUtils
MLIRFuncTransforms
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
new file mode 100644
index 00000000000000..f4399fd1629af0
--- /dev/null
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
@@ -0,0 +1,329 @@
+//===- TosaConvertIntegerTypes.cpp - Convert TOSA integers ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/Tosa/Transforms/Passes.h"
+
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/Tensor/IR/Tensor.h"
+#include "mlir/Dialect/Tosa/IR/TosaOps.h"
+#include "mlir/IR/AttrTypeSubElements.h"
+#include "mlir/IR/DialectResourceBlobManager.h"
+
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <utility>
+
+namespace llvm::cl {
+template <>
+class parser<std::pair<unsigned, unsigned>>
+ : public basic_parser<std::pair<unsigned, unsigned>> {
+public:
+ parser(Option &option) : basic_parser(option) {}
+
+ bool parse(Option &option, StringRef argName, StringRef arg,
+ std::pair<unsigned, unsigned> &value) {
+ auto [source, destination] = arg.split(':');
+ if (source.getAsInteger(10, value.first) ||
+ destination.getAsInteger(10, value.second))
+ return option.error("expected <source>:<destination> integer bitwidths",
+ argName);
+ return false;
+ }
+
+ StringRef getValueName() const override { return "source:destination"; }
+
+ static void print(raw_ostream &os,
+ const std::pair<unsigned, unsigned> &value) {
+ os << value.first << ":" << value.second;
+ }
+};
+} // namespace llvm::cl
+
+namespace mlir::tosa {
+#define GEN_PASS_DEF_TOSACONVERTINTEGERTYPESPASS
+#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
+} // namespace mlir::tosa
+
+using namespace mlir;
+
+namespace {
+// Preserve inherent dense arrays and known structural scalars at the operation
+// boundary. Extend this policy for additional storage constraints; all other
+// attributes and operations participate in generic conversion. Identical values
+// in discardable or nested metadata still follow the bitwidth mapping.
+static bool shouldPreserveAttribute(Operation *op, NamedAttribute attr) {
+ StringRef name = attr.getName().getValue();
+ if (!op->getInherentAttr(name))
+ return false;
+ if (isa<DenseArrayAttr>(attr.getValue()))
+ return true;
+ if (isa<tensor::ConcatOp>(op) && name == "dim")
+ return true;
+ if (op->getName().getDialectNamespace() !=
+ tosa::TosaDialect::getDialectNamespace())
+ return false;
+
+ // TOSA axes and boolean options have operation-defined storage types.
+ // Identify these by name: a semantic i1 value is also a BoolAttr, and
+ // scalar payloads such as clamp bounds must still be converted.
+ return llvm::StringSwitch<bool>(name)
+ .Cases({"axis", "local_bound", "inverse", "round", "input_unsigned",
+ "output_unsigned", "scale32", "per_channel"},
+ true)
+ .Default(false);
+}
+
+static APInt convertValue(const APInt &value, Type type, unsigned width) {
+ return type.isUnsignedInteger() ? value.zextOrTrunc(width)
+ : value.sextOrTrunc(width);
+}
+
+// Convert a validated byte-aligned payload, retaining storage for every
+// element. In particular, each i4 element occupies one byte, not half a byte.
+static SmallVector<char> convertRawValues(ArrayRef<char> rawData,
+ IntegerType type,
+ IntegerType convertedType) {
+ const unsigned inputBytes = llvm::divideCeil(type.getWidth(), 8);
+ const unsigned outputBytes = llvm::divideCeil(convertedType.getWidth(), 8);
+ const size_t numElements = rawData.size() / inputBytes;
+ SmallVector<char> data(numElements * outputBytes);
+ const auto *input = reinterpret_cast<const uint8_t *>(rawData.data());
+ auto *output = reinterpret_cast<uint8_t *>(data.data());
+ for (size_t i = 0; i < numElements; ++i) {
+ APInt value(inputBytes * 8, 0);
+ llvm::LoadIntFromMemory(value, input + i * inputBytes, inputBytes);
+ // Discard padding bits before interpreting the source sign bit.
+ value = value.trunc(type.getWidth());
+ llvm::StoreIntToMemory(convertValue(value, type, convertedType.getWidth()),
+ output + i * outputBytes, outputBytes);
+ }
+ return data;
+}
+
+class IntegerTypeReplacer : public AttrTypeReplacer {
+public:
+ // The mappings must outlive this replacer.
+ explicit IntegerTypeReplacer(const DenseMap<unsigned, unsigned> &mappings) {
+ addReplacement([&mappings](IntegerType type) -> std::optional<Type> {
+ auto mapping = mappings.find(type.getWidth());
+ if (mapping == mappings.end())
+ return std::nullopt;
+ return IntegerType::get(type.getContext(), mapping->second,
+ type.getSignedness());
+ });
+ // Reject unsupported typed payloads rather than changing just their type.
+ // Specific payload handlers below take precedence over this fallback.
+ addReplacement([this](TypedAttr attr) -> std::optional<Attribute> {
+ if (replace(attr.getType()) != attr.getType())
+ return nullptr;
+ return std::nullopt;
+ });
+ addReplacement(
+ [this](IntegerAttr attr) { return skip(convertInteger(attr)); });
+ addReplacement(
+ [this](DenseArrayAttr attr) { return skip(convertDenseArray(attr)); });
+ addReplacement([this](DenseTypedElementsAttr attr) {
+ return skip(convertDenseElements(attr));
+ });
+ addReplacement([this](SparseElementsAttr attr) {
+ return skip(convertSparseElements(attr));
+ });
+ addReplacement([this](DenseResourceElementsAttr attr) {
+ return skip(convertDenseResource(attr));
+ });
+ }
+
+private:
+ Attribute convertInteger(IntegerAttr attr) {
+ Type type = attr.getType();
+ Type convertedType = replace(type);
+ if (!convertedType)
+ return nullptr;
+ if (type == convertedType)
+ return attr;
+ return IntegerAttr::get(
+ convertedType,
+ convertValue(attr.getValue(), type,
+ cast<IntegerType>(convertedType).getWidth()));
+ }
+
+ Attribute convertDenseArray(DenseArrayAttr attr) {
+ Type type = attr.getElementType();
+ Type convertedType = replace(type);
+ if (!convertedType)
+ return nullptr;
+ if (type == convertedType)
+ return attr;
+ // Dense array assembly only supports byte-sized integers and i1.
+ unsigned width = cast<IntegerType>(convertedType).getWidth();
+ if (width != 1 && width % 8 != 0)
+ return nullptr;
+ // Unlike dense elements, arrays always store every element, even splats.
+ SmallVector<char> data =
+ convertRawValues(attr.getRawData(), cast<IntegerType>(type),
+ cast<IntegerType>(convertedType));
+ return DenseArrayAttr::get(convertedType, attr.size(), data);
+ }
+
+ Attribute convertDenseElements(DenseTypedElementsAttr attr) {
+ auto convertedType = dyn_cast_or_null<ShapedType>(replace(attr.getType()));
+ if (!convertedType)
+ return nullptr;
+
+ Type elementType = attr.getElementType();
+ DenseElementsAttr convertedAttr = attr;
+ if (elementType != convertedType.getElementType()) {
+ auto convertedElementType =
+ dyn_cast<IntegerType>(convertedType.getElementType());
+ if (!isa<IntegerType>(elementType) || !convertedElementType)
+ return nullptr;
+ convertedAttr = attr.mapValues(
+ convertedType.getElementType(),
+ [elementType, convertedElementType](const APInt &value) {
+ return convertValue(value, elementType,
+ convertedElementType.getWidth());
+ });
+ }
+ return convertedAttr.reshape(convertedType);
+ }
+
+ Attribute convertSparseElements(SparseElementsAttr attr) {
+ auto type = dyn_cast_or_null<ShapedType>(replace(attr.getType()));
+ auto values =
+ dyn_cast_or_null<DenseElementsAttr>(replace(attr.getValues()));
+ if (!type || !values)
+ return nullptr;
+ // Sparse indices must remain i64 even when i64 values are converted.
+ return SparseElementsAttr::get(type, attr.getIndices(), values);
+ }
+
+ Attribute convertDenseResource(DenseResourceElementsAttr attr) {
+ ShapedType type = attr.getType();
+ Type elementType = type.getElementType();
+ auto convertedType = dyn_cast_or_null<ShapedType>(replace(type));
+ if (!convertedType)
+ return nullptr;
+ Type convertedElementType = convertedType.getElementType();
+ if (elementType == convertedElementType)
+ return DenseResourceElementsAttr::get(convertedType, attr.getRawHandle());
+ auto integerType = dyn_cast<IntegerType>(elementType);
+ auto convertedIntegerType =
+ dyn_cast_or_null<IntegerType>(convertedElementType);
+ if (!integerType || !convertedIntegerType)
+ return nullptr;
+ const AsmResourceBlob *blob = attr.getRawHandle().getBlob();
+ if (!blob)
+ return nullptr;
+ ArrayRef<char> rawData = blob->getData();
+ const unsigned inputBytes = llvm::divideCeil(integerType.getWidth(), 8);
+ if (rawData.size() % inputBytes != 0)
+ return nullptr;
+ if (type.hasStaticShape() &&
+ rawData.size() !=
+ inputBytes * static_cast<uint64_t>(type.getNumElements()))
+ return nullptr;
+ SmallVector<char> data =
+ convertRawValues(rawData, integerType, convertedIntegerType);
+ // Alignment must be a power of two, including for sub-byte and
+ // non-power-of-two destination widths.
+ unsigned alignment =
+ std::min<uint64_t>(llvm::PowerOf2Ceil(llvm::divideCeil(
+ convertedIntegerType.getWidth(), 8)),
+ alignof(uint64_t));
+ return DenseResourceElementsAttr::get(
+ convertedType, attr.getRawHandle().getKey(),
+ HeapAsmResourceBlob::allocateAndCopyWithAlign(data, alignment));
+ }
+
+ // Payload handlers already converted their subelements. Do not apply a
+ // second mapping when a destination width is also a source width.
+ static ReplaceFnResult<Attribute> skip(Attribute attr) {
+ return std::make_pair(attr, WalkResult::skip());
+ }
+};
+
+struct TosaConvertIntegerTypes
+ : tosa::impl::TosaConvertIntegerTypesPassBase<TosaConvertIntegerTypes> {
+ using Base::Base;
+
+ void runOnOperation() override {
+ if (bitwidthMappings.empty()) {
+ getOperation().emitError(
+ "The 'bitwidth-mappings' option must be specified and non-empty.");
+ return signalPassFailure();
+ }
+ DenseMap<unsigned, unsigned> mappings;
+ for (auto [source, destination] : bitwidthMappings) {
+ for (auto [kind, width] : {std::make_pair("source", source),
+ std::make_pair("destination", destination)}) {
+ if (width == 0 || width > IntegerType::kMaxWidth) {
+ getOperation().emitError("invalid ")
+ << kind << " integer bitwidth " << width
+ << ": expected a value between 1 and " << IntegerType::kMaxWidth;
+ return signalPassFailure();
+ }
+ }
+ auto [it, inserted] = mappings.try_emplace(source, destination);
+ if (!inserted && it->second != destination) {
+ getOperation().emitError(
+ "conflicting mappings for source integer bitwidth ")
+ << source << ": " << it->second << " and " << destination;
+ return signalPassFailure();
+ }
+ }
+ IntegerTypeReplacer replacer(mappings);
+ WalkResult result = getOperation()->walk([&](Operation *op) -> WalkResult {
+ bool invalid = false;
+ SmallVector<NamedAttribute> convertedAttrs;
+ for (NamedAttribute attr : op->getAttrs()) {
+ // Preserve fixed-storage attributes at the operation boundary so the
+ // same attribute can still convert as discardable or nested metadata.
+ Attribute value = attr.getValue();
+ if (!shouldPreserveAttribute(op, attr))
+ value = replacer.replace(value);
+ if (!value)
+ invalid = true;
+ else
+ convertedAttrs.emplace_back(attr.getName(), value);
+ }
+ auto replaceValueType = [&](Value value) {
+ if (Type type = replacer.replace(value.getType()))
+ value.setType(type);
+ else
+ invalid = true;
+ };
+ for (Value value : op->getResults())
+ replaceValueType(value);
+ for (Region ®ion : op->getRegions())
+ for (Block &block : region)
+ for (BlockArgument arg : block.getArguments())
+ replaceValueType(arg);
+
+ if (invalid) {
+ op->emitError("failed to legalize operation '")
+ << op->getName()
+ << "': failed to convert integer types or attributes";
+ return WalkResult::interrupt();
+ }
+ op->setAttrs(convertedAttrs);
+ return WalkResult::advance();
+ });
+ if (result.wasInterrupted())
+ signalPassFailure();
+ }
+};
+} // namespace
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
new file mode 100644
index 00000000000000..58d7efb97fa52e
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
@@ -0,0 +1,94 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64,8:4' %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=32:32' %s | FileCheck %s
+
+// Unselected widths must leave even missing or malformed resources untouched.
+// CHECK-LABEL: func.func @missing_resource()
+// CHECK: tosa.const values(dense_resource<missing> : tensor<2xi48>) : () -> tensor<2xi48>
+
+// A missing resource must produce a diagnostic, not an invalid i64 constant.
+func.func @missing_resource() -> tensor<2xi48> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const'}}
+ %0 = tosa.const values(dense_resource<missing> : tensor<2xi48>) : () -> tensor<2xi48>
+ return %0 : tensor<2xi48>
+}
+
+// -----
+
+// An i48 element occupies six bytes; this resource contains only two bytes.
+func.func @malformed_resource() -> tensor<2xi48> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const'}}
+ %0 = tosa.const values(dense_resource<malformed> : tensor<2xi48>) : () -> tensor<2xi48>
+ return %0 : tensor<2xi48>
+}
+{-#
+ dialect_resources: {
+ builtin: {
+ // 01000000: alignment 1. Payload: bytes 01 02, too short for an i48.
+ malformed: "0x010000000102"
+ }
+ }
+#-}
+
+// -----
+
+// Complete elements must match the element count of a static shape.
+func.func @resource_wrong_element_count() -> tensor<2xi48> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
+ %0 = tosa.const values(dense_resource<short> : tensor<2xi48>) : () -> tensor<2xi48>
+ return %0 : tensor<2xi48>
+}
+{-# dialect_resources: { builtin: {
+ // Alignment 1 followed by one i48 value; the shape requires two.
+ short: "0x01000000FFFFFFFFFFFF"
+} } #-}
+
+// -----
+
+// An unresolved shape must not allow a payload containing an incomplete i48.
+func.func @malformed_dynamic_resource() -> tensor<?xi48> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
+ %0 = tosa.const values(dense_resource<partial> : tensor<?xi48>) : () -> tensor<?xi48>
+ return %0 : tensor<?xi48>
+}
+{-# dialect_resources: { builtin: {
+ // Alignment 1 followed by only two payload bytes.
+ partial: "0x010000000102"
+} } #-}
+
+// -----
+
+// Report missing i48 resources nested in attributes even when the operation's
+// operand and result types need no widening.
+func.func @nested_missing_resource(%arg: tensor<2xi32>) -> tensor<2xi32> {
+ // expected-error @+1 {{failed to convert integer types or attributes}}
+ %0 = tosa.identity %arg {test.nested = [{value = dense_resource<missing> : tensor<2xi48>}]} : (tensor<2xi32>) -> tensor<2xi32>
+ return %0 : tensor<2xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @missing_i4_resource()
+// CHECK: tosa.const values(dense_resource<missing> : tensor<2xi4>) : () -> tensor<2xi4>
+func.func @missing_i4_resource() -> tensor<2xi4> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
+ %0 = tosa.const values(dense_resource<missing> : tensor<2xi4>) : () -> tensor<2xi4>
+ return %0 : tensor<2xi4>
+}
+
+// -----
+
+// Each i4 requires one byte; a blob packing two nibbles per byte is too short.
+func.func @packed_i4_resource() -> tensor<2xi4> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
+ %0 = tosa.const values(dense_resource<packed> : tensor<2xi4>) : () -> tensor<2xi4>
+ return %0 : tensor<2xi4>
+}
+{-# dialect_resources: { builtin: {
+ packed: "0x0100000079"
+} } #-}
+
+// -----
+
+// Dense arrays cannot represent sub-byte destinations other than i1.
+// expected-error @+1 {{failed to legalize operation 'builtin.module': failed to convert integer types or attributes}}
+module attributes {test.array = array<i8: -1, 7>} {}
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
new file mode 100644
index 00000000000000..0a454067087e42
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
@@ -0,0 +1,239 @@
+// REQUIRES: host-byteorder-little-endian
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=1:8,3:4,4:8,8:4,13:16,16:1,24:32,32:24,48:64,64:48,65:128,128:65' -mlir-print-op-generic %s | FileCheck %s --check-prefixes=CHECK,SCALAR '-D$WIDTH=24'
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=128:65,65:128,64:48,48:64,32:24,24:32,16:1,13:16,8:4,4:8,3:4,1:8' -mlir-print-op-generic %s | FileCheck %s --check-prefixes=CHECK,SCALAR '-D$WIDTH=24'
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=1:8,32:64' -mlir-print-op-generic %s | FileCheck %s --check-prefix=SCALAR '-D$WIDTH=64'
+
+// Each mapping applies once to the original width, including cycles and
+// destinations that are also sources. Reversing the list must have no effect.
+// CHECK: "func.func"() <{function_type = (i8, i4, si4, ui4, i8, i4, i16, i1, i32, i24, i64, i48, i128, i65) -> (), sym_name = "types", sym_visibility = "private"}>
+func.func private @types(i1, i3, si3, ui3, i4, i8, i13, i16, i24, i32, i48, i64, i65, i128)
+
+// -----
+
+// Conversion truncates, including when values do not fit in the destination.
+// CHECK: "func.func"() <{function_type = (tensor<4xi4>, tensor<2xi48>) -> tensor<4xi4>, sym_name = "callee", sym_visibility = "private"}>
+func.func private @callee(tensor<4xi8>, tensor<2xi64>) -> tensor<4xi8>
+// CHECK-LABEL: "func.func"() <{function_type = (tensor<2xi48>) -> tensor<4xi4>, sym_name = "caller"}>
+func.func @caller(%arg: tensor<2xi64>) -> tensor<4xi8> {
+ // CHECK: "tosa.const"() <{values = dense<[-7, 7, -1, 0]> : tensor<4xi4>}>
+ %0 = tosa.const values(dense<[9, 23, -1, -16]> : tensor<4xi8>) : () -> tensor<4xi8>
+ // CHECK: "func.call"(%{{.*}}, %{{.*}}) <{callee = @callee}> : (tensor<4xi4>, tensor<2xi48>) -> tensor<4xi4>
+ %1 = func.call @callee(%0, %arg) : (tensor<4xi8>, tensor<2xi64>) -> tensor<4xi8>
+ return %1 : tensor<4xi8>
+}
+
+// -----
+
+// Narrow scalar, dense, sparse and array payloads, including i1.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "narrow_constants"}>
+// CHECK: test.a_scalar = -7 : si4
+// CHECK-SAME: test.b_unsigned = 15 : ui4
+// CHECK-SAME: test.c_array = array<i24: -8388608, 8388607, -1, 0>
+// CHECK-SAME: test.d_splat = dense<-7> : tensor<3xsi4>
+// CHECK-SAME: test.e_unsigned = dense<[8, 7, 15, 0]> : tensor<4xui4>
+// CHECK-SAME: test.f_sparse = sparse<{{\[\[}}1], [3]], [-1, 0]> : tensor<4xi48>
+// CHECK-SAME: test.g_i1_array = array<i1: true, false, true>
+// CHECK-SAME: test.h_i1_dense = dense<[true, false, true]> : tensor<3xi1>
+// CHECK-SAME: test.i_types = [i24, si48, ui65]
+// CHECK-SAME: test.j_wide_sparse = sparse<{{\[\[}}1], [3]], [-1, 18446744073709551615]> : tensor<4xi65>
+module @narrow_constants attributes {
+ test.a_scalar = 25 : si8,
+ test.b_unsigned = 255 : ui8,
+ test.c_array = array<i32: 8388608, 25165823, -1, -16777216>,
+ test.d_splat = dense<25> : tensor<3xsi8>,
+ test.e_unsigned = dense<[8, 23, 255, 16]> : tensor<4xui8>,
+ test.f_sparse = sparse<[[1], [3]], [-1, 281474976710656]> : tensor<4xi64>,
+ test.g_i1_array = array<i16: -1, 2, 3>,
+ test.h_i1_dense = dense<[-1, 2, 3]> : tensor<3xi16>,
+ test.i_types = [i32, si64, ui128],
+ test.j_wide_sparse = sparse<[[1], [3]], [-1, 18446744073709551615]> : tensor<4xi128>
+} {}
+
+// -----
+
+// The 64:48 mapping must truncate stored values, but preserve coordinates.
+// Bit 48 distinguishes this index from zero even without printed index types.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "sparse_indices"}>
+// CHECK: test.sparse = sparse<{{\[\[}}0], [281474976710656]], [-1, 0]> : tensor<281474976710657xi48>
+module @sparse_indices attributes {
+ test.sparse = sparse<[[0], [281474976710656]], [-1, 281474976710656]> : tensor<281474976710657xi64>
+} {}
+
+// -----
+
+// Widen sub-byte, non-power-of-two and greater-than-64-bit payloads.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "widen_constants"}>
+// CHECK: test.array = array<i32: -8388608, 8388607>
+// CHECK-SAME: test.array_unsigned = array<ui32: 16777215>
+// CHECK-SAME: test.dense = dense<[-4, -1, 0, 3]> : tensor<4xi4>
+// CHECK-SAME: test.mid_signed = dense<[-4096, 4095]> : tensor<2xi16>
+// CHECK-SAME: test.mid_unsigned = 8191 : ui16
+// CHECK-SAME: test.scalar = -4 : si4
+// CHECK-SAME: test.sparse = sparse<{{\[\[}}1], [3]], [-2, 1]> : tensor<4xi4>
+// CHECK-SAME: test.unsigned = 7 : ui4
+// CHECK-SAME: test.wide = dense<[-1, 18446744073709551615]> : tensor<2xi128>
+// CHECK-SAME: test.wide_unsigned = 36893488147419103231 : ui128
+module @widen_constants attributes {
+ test.array = array<i24: -8388608, 8388607>,
+ test.array_unsigned = array<ui24: 16777215>,
+ test.dense = dense<[-4, -1, 0, 3]> : tensor<4xi3>,
+ test.mid_signed = dense<[-4096, 4095]> : tensor<2xi13>,
+ test.mid_unsigned = 8191 : ui13,
+ test.scalar = -4 : si3,
+ test.sparse = sparse<[[1], [3]], [-2, 1]> : tensor<4xi3>,
+ test.unsigned = 7 : ui3,
+ test.wide = dense<[-1, 18446744073709551615]> : tensor<2xi65>,
+ test.wide_unsigned = 36893488147419103231 : ui65
+} {}
+
+// -----
+
+// Map every nested type once, including when resource data is unavailable.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "mapping_encodings"}>
+// CHECK: test.type = tensor<2xi4, {test.widths = [i4, i8, i4]}>
+// CHECK-SAME: test.unresolved_resource = dense_resource<missing> : tensor<2xi7, {test.widths = [i4, i8, i4]}>
+module @mapping_encodings attributes {
+ test.type = tensor<2xi3, {test.widths = [i3, i4, i8]}>,
+ test.unresolved_resource = dense_resource<missing> : tensor<2xi7, {test.widths = [i3, i4, i8]}>
+} {}
+
+// -----
+
+// CHECK-LABEL: "builtin.module"() <{sym_name = "narrow_resources"}>
+// CHECK: test.a_i4 = dense_resource<[[I4:[a-zA-Z0-9_]+]]> : tensor<4xi4>
+// CHECK-SAME: test.b_i24 = dense_resource<[[I24:[a-zA-Z0-9_]+]]> : tensor<2xi24>
+// CHECK-SAME: test.c_i48 = dense_resource<[[I48:[a-zA-Z0-9_]+]]> : tensor<2xi48>
+// CHECK-SAME: test.d_i65 = dense_resource<[[I65:[a-zA-Z0-9_]+]]> : tensor<2xi65>
+// CHECK-SAME: test.e_i1 = dense_resource<[[I1:[a-zA-Z0-9_]+]]> : tensor<3xi1>
+module @narrow_resources attributes {
+ test.a_i4 = dense_resource<to4> : tensor<4xi8>,
+ test.b_i24 = dense_resource<to24> : tensor<2xi32>,
+ test.c_i48 = dense_resource<to48> : tensor<2xi64>,
+ test.d_i65 = dense_resource<to65> : tensor<2xi128>,
+ test.e_i1 = dense_resource<to1> : tensor<3xi16>
+} {}
+
+{-# dialect_resources: { builtin: {
+ to4: "0x010000000917FFF0",
+ to24: "0x01000000FFFFFFFFFFFFFF00",
+ to48: "0x01000000FFFFFFFFFFFFFFFF0000000000000100",
+ to65: "0x01000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000",
+ to1: "0x01000000FFFF02000300"
+ // CHECK-DAG: [[I4]]: "0x0100000009070F00"
+ // CHECK-DAG: [[I24]]: "0x04000000FFFFFFFFFFFF"
+ // CHECK-DAG: [[I48]]: "0x08000000FFFFFFFFFFFF000000000000"
+ // CHECK-DAG: [[I65]]: "0x08000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00"
+ // CHECK-DAG: [[I1]]: "0x01000000010001"
+} } #-}
+
+// -----
+
+// Widen resource elements and their encodings using the original types.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "widen_resources"}>
+// CHECK: test.a_subbyte = dense_resource<[[SUBBYTE:[a-zA-Z0-9_]+]]> : tensor<4xi4, {test.widths = [i4, i8, i4]}>
+// CHECK-SAME: test.b_i16 = dense_resource<[[I16:[a-zA-Z0-9_]+]]> : tensor<2xi16>
+// CHECK-SAME: test.c_i32 = dense_resource<[[I32:[a-zA-Z0-9_]+]]> : tensor<2xi32>
+// CHECK-SAME: test.d_i128 = dense_resource<[[I128:[a-zA-Z0-9_]+]]> : tensor<2xi128>
+// CHECK-SAME: test.e_unsigned = dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui4>
+module @widen_resources attributes {
+ test.a_subbyte = dense_resource<subbyte> : tensor<4xi3, {test.widths = [i3, i4, i8]}>,
+ test.b_i16 = dense_resource<to16> : tensor<2xi13>,
+ test.c_i32 = dense_resource<to32> : tensor<2xi24>,
+ test.d_i128 = dense_resource<to128> : tensor<2xi65>,
+ test.e_unsigned = dense_resource<unsigned_data> : tensor<3xui3>
+} {}
+
+{-# dialect_resources: { builtin: {
+ // [-4, -1, 0, 3], with one byte per i3 value.
+ subbyte: "0x0100000004070003",
+ // [-4096, 4095], with two bytes per i13 value.
+ to16: "0x010000000010FF0F",
+ // [-8388608, 8388607], with three bytes per i24 value.
+ to32: "0x01000000000080FFFF7F",
+ // [-1, 2^64 - 1], with nine bytes per i65 value.
+ to128: "0x01000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00",
+ // [0, 4, 7].
+ unsigned_data: "0x01000000000407"
+ // CHECK-DAG: [[SUBBYTE]]: "0x010000000C0F0003"
+ // CHECK-DAG: [[I16]]: "0x0200000000F0FF0F"
+ // CHECK-DAG: [[I32]]: "0x04000000000080FFFFFF7F00"
+ // CHECK-DAG: [[I128]]: "0x08000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000"
+ // CHECK-DAG: [[UNSIGNED]]: "0x01000000000407"
+} } #-}
+
+// -----
+
+// Structural attributes have operation-defined storage types. Mapping i64 to
+// i48 must preserve the required DenseI64ArrayAttr properties, even though
+// standalone dense arrays can be converted. All tensor elements here are f32
+// to isolate changes to structural attributes from tensor type conversion.
+// Generic printing exposes the attribute storage types.
+// The discardable test.pad attribute shares the original padding array but
+// must still be converted to i48.
+
+// CHECK: "tosa.conv2d"
+// CHECK-SAME: acc_type = f32
+// CHECK-SAME: dilation = array<i64: 1, 1>
+// CHECK-SAME: pad = array<i64: 0, 0, 0, 0>
+// CHECK-SAME: stride = array<i64: 1, 1>
+// CHECK-SAME: test.pad = array<i48: 0, 0, 0, 0>
+// CHECK-SAME: -> tensor<1x3x3x1xf32>
+func.func @conv2d(%input: tensor<1x3x3x1xf32>, %weight: tensor<1x1x1x1xf32>, %bias: tensor<1xf32>, %izp: tensor<1xf32>, %wzp: tensor<1xf32>) -> tensor<1x3x3x1xf32> {
+ %0 = tosa.conv2d %input, %weight, %bias, %izp, %wzp pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(f32) {test.pad = array<i64: 0, 0, 0, 0>} : (tensor<1x3x3x1xf32>, tensor<1x1x1x1xf32>, tensor<1xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x3x3x1xf32>
+ return %0 : tensor<1x3x3x1xf32>
+}
+
+// -----
+
+// CHECK: "tosa.avg_pool2d"
+// CHECK-SAME: acc_type = f32
+// CHECK-SAME: kernel = array<i64: 2, 2>
+// CHECK-SAME: pad = array<i64: 0, 1, 0, 1>
+// CHECK-SAME: stride = array<i64: 1, 1>
+// CHECK-SAME: -> tensor<1x7x7x9xf32>
+func.func @avg_pool2d(%input: tensor<1x7x7x9xf32>, %izp: tensor<1xf32>, %ozp: tensor<1xf32>) -> tensor<1x7x7x9xf32> {
+ %0 = tosa.avg_pool2d %input, %izp, %ozp kernel([2, 2]) stride([1, 1]) pad([0, 1, 0, 1]) acc_type(f32) : (tensor<1x7x7x9xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x7x7x9xf32>
+ return %0 : tensor<1x7x7x9xf32>
+}
+
+// -----
+
+// TOSA flags keep their boolean storage, while the same values in metadata
+// and arithmetic constants follow the mapping. Check true and false explicitly
+// because dropping an optional flag can silently restore its default value.
+// SCALAR-LABEL: sym_name = "boolean_flags"
+func.func @boolean_flags(%arg: tensor<2xi32>) -> tensor<2xi32> {
+ // SCALAR: "tosa.cast"
+ // SCALAR-SAME: input_unsigned = true
+ // SCALAR-SAME: test.flag = -1 : i8
+ // SCALAR-SAME: test.nested = {flag = -1 : i8}
+ %0 = tosa.cast %arg input_unsigned(true) {test.flag = true, test.nested = {flag = true}} : (tensor<2xi32>) -> tensor<2xi32>
+ // SCALAR: "tosa.cast"
+ // SCALAR-SAME: input_unsigned = false
+ // SCALAR-SAME: test.flag = 0 : i8
+ %1 = tosa.cast %0 input_unsigned(false) {test.flag = false} : (tensor<2xi32>) -> tensor<2xi32>
+ // SCALAR: "arith.constant"() <{value = -1 : i8}>
+ %true = arith.constant true
+ // SCALAR: "arith.constant"() <{value = 0 : i8}>
+ %false = arith.constant false
+ return %1 : tensor<2xi32>
+}
+
+// -----
+
+// Axes retain their required i32 storage during both widening and narrowing.
+// Scalar clamp bounds, discardable attributes and nested metadata still change.
+// SCALAR-LABEL: sym_name = "scalar_attributes"
+func.func @scalar_attributes(%arg: tensor<2x3xi32>) -> tensor<2x1xi32> {
+ // SCALAR: "tosa.clamp"
+ // SCALAR-SAME: max_val = 7 : i[[$WIDTH]], min_val = 0 : i[[$WIDTH]]
+ // SCALAR-SAME: -> tensor<2x3xi[[$WIDTH]]>
+ %0 = tosa.clamp %arg min_val(0 : i32) max_val(7 : i32) : (tensor<2x3xi32>) -> tensor<2x3xi32>
+ // SCALAR: "tosa.reduce_sum"
+ // SCALAR-SAME: axis = 1 : i32
+ // SCALAR-SAME: test.axis = 1 : i[[$WIDTH]]
+ // SCALAR-SAME: test.nested = {axis = 1 : i[[$WIDTH]]}
+ // SCALAR-SAME: -> tensor<2x1xi[[$WIDTH]]>
+ %1 = tosa.reduce_sum %0 axis(1) {test.axis = 1 : i32, test.nested = {axis = 1 : i32}} : (tensor<2x3xi32>) -> tensor<2x1xi32>
+ return %1 : tensor<2x1xi32>
+}
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
new file mode 100644
index 00000000000000..239f1856d7cd16
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
@@ -0,0 +1,55 @@
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64,4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' %s | FileCheck %s '-D$I4=8' '-D$I48=48'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' %s | FileCheck %s '-D$I4=4' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=32:32' %s | FileCheck %s '-D$I4=4' '-D$I48=48'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' %s | mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' | FileCheck %s '-D$I4=8' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' %s | mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' | FileCheck %s '-D$I4=8' '-D$I48=64'
+//
+// RUN: not mlir-opt -tosa-convert-integer-types %s 2>&1 | FileCheck %s --check-prefix=MISSING
+// RUN: not mlir-opt -pass-pipeline='builtin.module(tosa-convert-integer-types)' %s 2>&1 | FileCheck %s --check-prefix=MISSING
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=' %s 2>&1 | FileCheck %s --check-prefix=MISSING
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=0:8,4:8' %s 2>&1 | FileCheck %s --check-prefix=ZERO
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=16777216:16777216' %s 2>&1 | FileCheck %s --check-prefix=TOO-LARGE
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=8388609:16777216' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:0' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION-ZERO
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8,4:16' %s 2>&1 | FileCheck %s --check-prefix=CONFLICT
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8:16' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=-4:8' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:foo' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+
+// Both conversions must update function signatures, calls, region arguments and
+// nested attributes consistently. The options also control constant payloads.
+// The i32 metadata exercises an explicit identity mapping as well as unmapped types.
+// CHECK: func.func private @callee(tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
+func.func private @callee(tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>)
+
+// CHECK-LABEL: func.func @caller(
+// CHECK-SAME: %{{.*}}: tensor<i1>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
+func.func @caller(%cond: tensor<i1>) -> (tensor<2xi4>, tensor<2xi48>) {
+ // CHECK: tosa.const values(dense<[-7, 7]> : tensor<2xi[[$I4]]>)
+ %a = tosa.const values(dense<[-7, 7]> : tensor<2xi4>) : () -> tensor<2xi4>
+ // CHECK: tosa.const values(dense<[-1, 140737488355327]> : tensor<2xi[[$I48]]>)
+ %b = tosa.const values(dense<[-1, 140737488355327]> : tensor<2xi48>) : () -> tensor<2xi48>
+ // CHECK: call @callee(%{{.*}}, %{{.*}}) {test.types = [i[[$I4]], i[[$I48]], i32]} : (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
+ %c:2 = func.call @callee(%a, %b) {test.types = [i4, i48, i32]} : (tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>)
+ %d:2 = tosa.cond_if %cond (%x = %c#0, %y = %c#1) : tensor<i1> (tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>) {
+ // CHECK: ^bb0(%{{.*}}: tensor<2xi[[$I4]]>, %{{.*}}: tensor<2xi[[$I48]]>):
+ ^bb0(%x: tensor<2xi4>, %y: tensor<2xi48>):
+ tosa.yield %x, %y : tensor<2xi4>, tensor<2xi48>
+ } else {
+ ^bb0(%x: tensor<2xi4>, %y: tensor<2xi48>):
+ tosa.yield %x, %y : tensor<2xi4>, tensor<2xi48>
+ }
+ return %d#0, %d#1 : tensor<2xi4>, tensor<2xi48>
+}
+
+// MISSING: error: The 'bitwidth-mappings' option must be specified and non-empty.
+// ZERO: error: invalid source integer bitwidth 0: expected a value between 1 and 16777215
+// TOO-LARGE: error: invalid source integer bitwidth 16777216: expected a value between 1 and 16777215
+// DESTINATION: error: invalid destination integer bitwidth 16777216: expected a value between 1 and 16777215
+// DESTINATION-ZERO: error: invalid destination integer bitwidth 0: expected a value between 1 and 16777215
+// CONFLICT: error: conflicting mappings for source integer bitwidth 4: 8 and 16
+// SYNTAX: expected <source>:<destination> integer bitwidths
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
new file mode 100644
index 00000000000000..c7187d79fce416
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
@@ -0,0 +1,640 @@
+// REQUIRES: host-byteorder-little-endian
+// RUN: mlir-opt -split-input-file -pass-pipeline='builtin.module(tosa-convert-integer-types{bitwidth-mappings=4:8,48:64})' %s | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' %s | mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
+
+// CHECK-LABEL: func.func @identity(
+// CHECK-SAME: %[[ARG:.*]]: tensor<?x2xi64>) -> tensor<?x2xi64>
+func.func @identity(%arg: tensor<?x2xi48>) -> tensor<?x2xi48> {
+ // CHECK: %[[ID:.*]] = tosa.identity %[[ARG]] {test.marker = "keep"} : (tensor<?x2xi64>) -> tensor<?x2xi64>
+ %0 = tosa.identity %arg {test.marker = "keep"} : (tensor<?x2xi48>) -> tensor<?x2xi48>
+ // CHECK-NEXT: return %[[ID]] : tensor<?x2xi64>
+ return %0 : tensor<?x2xi48>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @clamp
+func.func @clamp(%arg: tensor<4xi48>) -> tensor<4xi48> {
+ // CHECK: tosa.clamp %{{.*}} min_val(-140737488355328 : i64) max_val(140737488355327 : i64) {test.marker = "keep"} : (tensor<4xi64>) -> tensor<4xi64>
+ %0 = tosa.clamp %arg min_val(-140737488355328 : i48) max_val(140737488355327 : i48) {test.marker = "keep"} : (tensor<4xi48>) -> tensor<4xi48>
+ return %0 : tensor<4xi48>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @reshape
+func.func @reshape(%arg: tensor<2x3xi48>) -> tensor<6xi48> {
+ // CHECK: %[[SHAPE:.*]] = tosa.const_shape values(dense<6> : tensor<1xindex>) : () -> !tosa.shape<1>
+ %shape = tosa.const_shape values(dense<6> : tensor<1xindex>) : () -> !tosa.shape<1>
+ // CHECK: tosa.reshape %{{.*}}, %[[SHAPE]] : (tensor<2x3xi64>, !tosa.shape<1>) -> tensor<6xi64>
+ %0 = tosa.reshape %arg, %shape : (tensor<2x3xi48>, !tosa.shape<1>) -> tensor<6xi48>
+ return %0 : tensor<6xi48>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @cond_if
+func.func @cond_if(%cond: tensor<i1>, %arg: tensor<2xi48>) -> tensor<2xi48> {
+ // CHECK: %[[IF:.*]] = tosa.cond_if %arg0
+ // CHECK-SAME: (tensor<2xi64>) -> tensor<2xi64>
+ %0 = tosa.cond_if %cond (%x = %arg) : tensor<i1> (tensor<2xi48>) -> tensor<2xi48> {
+ // CHECK: ^bb0(%[[THEN:.*]]: tensor<2xi64>):
+ ^bb0(%x: tensor<2xi48>):
+ // CHECK: %[[ADD:.*]] = tosa.add %[[THEN]], %[[THEN]] : (tensor<2xi64>, tensor<2xi64>) -> tensor<2xi64>
+ %1 = tosa.add %x, %x : (tensor<2xi48>, tensor<2xi48>) -> tensor<2xi48>
+ // CHECK: tosa.yield %[[ADD]] : tensor<2xi64>
+ tosa.yield %1 : tensor<2xi48>
+ } else {
+ // CHECK: ^bb0(%[[ELSE:.*]]: tensor<2xi64>):
+ ^bb0(%x: tensor<2xi48>):
+ // CHECK: tosa.yield %[[ELSE]] : tensor<2xi64>
+ tosa.yield %x : tensor<2xi48>
+ }
+ // CHECK: return %[[IF]] : tensor<2xi64>
+ return %0 : tensor<2xi48>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @while_loop
+func.func @while_loop(%arg: tensor<i48>, %limit: tensor<i48>) -> tensor<i48> {
+ // CHECK: %[[LOOP:.*]] = tosa.while_loop
+ // CHECK-SAME: (tensor<i64>) -> tensor<i64>
+ %0 = tosa.while_loop (%iter = %arg) : (tensor<i48>) -> tensor<i48> {
+ // CHECK: tosa.greater %{{.*}}, %{{.*}} : (tensor<i64>, tensor<i64>) -> tensor<i1>
+ %cond = tosa.greater %limit, %iter : (tensor<i48>, tensor<i48>) -> tensor<i1>
+ // CHECK: tosa.yield %{{.*}} : tensor<i1>
+ tosa.yield %cond : tensor<i1>
+ } do {
+ // CHECK: ^bb0(%[[ITER:.*]]: tensor<i64>):
+ ^bb0(%iter: tensor<i48>):
+ // CHECK: %[[ONE:.*]] = tosa.const values(dense<1> : tensor<i64>) : () -> tensor<i64>
+ %one = tosa.const values(dense<1> : tensor<i48>) : () -> tensor<i48>
+ // CHECK: %[[ADD:.*]] = tosa.add %[[ITER]], %[[ONE]] : (tensor<i64>, tensor<i64>) -> tensor<i64>
+ %next = tosa.add %iter, %one : (tensor<i48>, tensor<i48>) -> tensor<i48>
+ // CHECK: tosa.yield %[[ADD]] : tensor<i64>
+ tosa.yield %next : tensor<i48>
+ }
+ // CHECK: return %[[LOOP]] : tensor<i64>
+ return %0 : tensor<i48>
+}
+
+// -----
+
+// CHECK: func.func private @external(tensor<?xi64>) -> tensor<*xi64>
+func.func private @external(tensor<?xi48>) -> tensor<*xi48>
+
+// -----
+
+// Widen function arguments even when the body does not use them.
+// CHECK-LABEL: func.func @unused_arguments(
+// CHECK-SAME: %{{.*}}: tensor<?x2xi64>, %{{.*}}: tensor<*xi64>, %{{.*}}: tensor<2xui64>)
+func.func @unused_arguments(%ranked: tensor<?x2xi48>, %unranked: tensor<*xi48>, %unsigned: tensor<2xui48>) {
+ // CHECK-NEXT: return
+ return
+}
+
+// -----
+
+// Exercise an accumulator attribute even when no operand or result needs widening.
+// CHECK-LABEL: func.func @accumulator_only
+func.func @accumulator_only(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>, %bias: tensor<1xi32>, %izp: tensor<1xi16>, %wzp: tensor<1xi8>) -> tensor<1x3x3x1xi32> {
+ // CHECK: tosa.conv2d
+ // CHECK-SAME: acc_type(i64)
+ // CHECK-SAME: -> tensor<1x3x3x1xi32>
+ %0 = tosa.conv2d %input, %weight, %bias, %izp, %wzp pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(i48) : (tensor<1x3x3x1xi16>, tensor<1x1x1x1xi8>, tensor<1xi32>, tensor<1xi16>, tensor<1xi8>) -> tensor<1x3x3x1xi32>
+ return %0 : tensor<1x3x3x1xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @conv_rescale(
+// CHECK-SAME: %{{.*}}: tensor<1x3x3x1xi16>, %{{.*}}: tensor<1x1x1x1xi8>) -> tensor<1x3x3x1xi8>
+func.func @conv_rescale(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>) -> tensor<1x3x3x1xi8> {
+ // CHECK: %[[BIAS:.*]] = tosa.const values(dense<0> : tensor<1xi64>) : () -> tensor<1xi64>
+ %bias = tosa.const values(dense<0> : tensor<1xi48>) : () -> tensor<1xi48>
+ %izp = tosa.const values(dense<0> : tensor<1xi16>) : () -> tensor<1xi16>
+ %wzp = tosa.const values(dense<0> : tensor<1xi8>) : () -> tensor<1xi8>
+ // CHECK: %[[CONV:.*]] = tosa.conv2d %{{.*}}, %{{.*}}, %[[BIAS]], %{{.*}}, %{{.*}} pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(i64) : (tensor<1x3x3x1xi16>, tensor<1x1x1x1xi8>, tensor<1xi64>, tensor<1xi16>, tensor<1xi8>) -> tensor<1x3x3x1xi64>
+ %0 = tosa.conv2d %input, %weight, %bias, %izp, %wzp pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(i48) : (tensor<1x3x3x1xi16>, tensor<1x1x1x1xi8>, tensor<1xi48>, tensor<1xi16>, tensor<1xi8>) -> tensor<1x3x3x1xi48>
+ %mult = tosa.const values(dense<16384> : tensor<1xi16>) : () -> tensor<1xi16>
+ %shift = tosa.const values(dense<15> : tensor<1xi8>) : () -> tensor<1xi8>
+ // CHECK: %[[ZP:.*]] = tosa.const values(dense<0> : tensor<1xi64>) : () -> tensor<1xi64>
+ %rzp = tosa.const values(dense<0> : tensor<1xi48>) : () -> tensor<1xi48>
+ // CHECK: tosa.rescale %[[CONV]], %{{.*}}, %{{.*}}, %[[ZP]], %{{.*}} scale32(false) rounding_mode<SINGLE_ROUND> per_channel(false) input_unsigned(false) output_unsigned(false) : (tensor<1x3x3x1xi64>, tensor<1xi16>, tensor<1xi8>, tensor<1xi64>, tensor<1xi8>) -> tensor<1x3x3x1xi8>
+ %1 = tosa.rescale %0, %mult, %shift, %rzp, %wzp scale32(false) rounding_mode<SINGLE_ROUND> per_channel(false) input_unsigned(false) output_unsigned(false) : (tensor<1x3x3x1xi48>, tensor<1xi16>, tensor<1xi8>, tensor<1xi48>, tensor<1xi8>) -> tensor<1x3x3x1xi8>
+ return %1 : tensor<1x3x3x1xi8>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @casts
+func.func @casts(%arg: tensor<2xi32>) -> tensor<2xi8> {
+ // CHECK: %[[IN:.*]] = tosa.cast %arg0 input_unsigned(true) {test.marker = "keep"} : (tensor<2xi32>) -> tensor<2xi64>
+ %0 = tosa.cast %arg input_unsigned(true) {test.marker = "keep"} : (tensor<2xi32>) -> tensor<2xi64>
+ // CHECK: %[[OUT:.*]] = tosa.cast %[[IN]] : (tensor<2xi64>) -> tensor<2xi8>
+ %1 = tosa.cast %0 : (tensor<2xi64>) -> tensor<2xi8>
+ // CHECK: return %[[OUT]] : tensor<2xi8>
+ return %1 : tensor<2xi8>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @unchanged(
+// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64>) -> (tensor<2xi64>, tensor<2xf32>, i32)
+func.func @unchanged(%arg: tensor<2xi64>) -> (tensor<2xi64>, tensor<2xf32>, i32) {
+ // CHECK: %[[ID:.*]] = tosa.identity %[[ARG]] : (tensor<2xi64>) -> tensor<2xi64>
+ %0 = tosa.identity %arg : (tensor<2xi64>) -> tensor<2xi64>
+ // CHECK: %[[C:.*]] = tosa.const values(dense<1.000000e+00> : tensor<2xf32>) : () -> tensor<2xf32>
+ %1 = tosa.const values(dense<1.0> : tensor<2xf32>) : () -> tensor<2xf32>
+ // CHECK: %[[A:.*]] = arith.constant 48 : i32
+ %2 = arith.constant 48 : i32
+ // CHECK-NEXT: return %[[ID]], %[[C]], %[[A]] : tensor<2xi64>, tensor<2xf32>, i32
+ return %0, %1, %2 : tensor<2xi64>, tensor<2xf32>, i32
+}
+
+// -----
+
+// Recurse into nested attributes even when operand and result types are unchanged.
+// CHECK-LABEL: func.func @attributes_only
+func.func @attributes_only(%arg: tensor<2xi32>) -> tensor<2xi32> {
+ // CHECK: tosa.identity %arg0 {test.nested = [{signed = -140737488355328 : si64, type = tensor<2xi64>, unsigned = 281474976710655 : ui64}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
+ %0 = tosa.identity %arg {test.nested = [{signed = -140737488355328 : si48, type = tensor<2xi48>, unsigned = 281474976710655 : ui48}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
+ return %0 : tensor<2xi32>
+}
+
+// -----
+
+// Widen nested values even when the enclosing operation's types are unchanged.
+// CHECK-LABEL: func.func @internal_region(
+// CHECK-SAME: %[[COND:.*]]: tensor<i1>) -> tensor<i1>
+func.func @internal_region(%cond: tensor<i1>) -> tensor<i1> {
+ // CHECK: %[[IF:.*]] = tosa.cond_if %[[COND]] : tensor<i1> -> tensor<i1>
+ %0 = tosa.cond_if %cond : tensor<i1> -> tensor<i1> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<1> : tensor<i64>) : () -> tensor<i64>
+ %c = tosa.const values(dense<1> : tensor<i48>) : () -> tensor<i48>
+ // CHECK: %[[D:.*]] = tosa.const values(dense<2> : tensor<i64>) : () -> tensor<i64>
+ %d = tosa.const values(dense<2> : tensor<i48>) : () -> tensor<i48>
+ // CHECK: %[[SELECT:.*]] = tosa.select %[[COND]], %[[C]], %[[D]] : (tensor<i1>, tensor<i64>, tensor<i64>) -> tensor<i64>
+ %select = tosa.select %cond, %c, %d : (tensor<i1>, tensor<i48>, tensor<i48>) -> tensor<i48>
+ // CHECK: %[[EQ:.*]] = tosa.equal %[[SELECT]], %[[C]] : (tensor<i64>, tensor<i64>) -> tensor<i1>
+ %eq = tosa.equal %select, %c : (tensor<i48>, tensor<i48>) -> tensor<i1>
+ // CHECK: tosa.yield %[[EQ]] : tensor<i1>
+ tosa.yield %eq : tensor<i1>
+ } else {
+ // CHECK: tosa.yield %[[COND]] : tensor<i1>
+ tosa.yield %cond : tensor<i1>
+ }
+ // CHECK: return %[[IF]] : tensor<i1>
+ return %0 : tensor<i1>
+}
+
+// -----
+
+// Mixed types and multiple results; preserve function and call attributes.
+// CHECK-LABEL: func.func private @mixed(
+// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xui64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>)
+func.func private @mixed(%a: tensor<?xi48>, %b: tensor<*xui48>, %c: tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>) {
+ // CHECK: return %[[B]], %[[A]], %[[C]] : tensor<*xui64>, tensor<?xi64>, tensor<2xi32>
+ return %b, %a, %c : tensor<*xui48>, tensor<?xi48>, tensor<2xi32>
+}
+// CHECK-LABEL: func.func @mixed_caller(
+// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xui64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>) attributes {test.marker = "function"}
+func.func @mixed_caller(%a: tensor<?xi48>, %b: tensor<*xui48>, %c: tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>) attributes {test.marker = "function"} {
+ // CHECK: %[[CALL:.*]]:3 = call @mixed(%[[A]], %[[B]], %[[C]]) {test.marker = "call"} : (tensor<?xi64>, tensor<*xui64>, tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>)
+ %0:3 = func.call @mixed(%a, %b, %c) {test.marker = "call"} : (tensor<?xi48>, tensor<*xui48>, tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>)
+ // CHECK: return %[[CALL]]#0, %[[CALL]]#1, %[[CALL]]#2 : tensor<*xui64>, tensor<?xi64>, tensor<2xi32>
+ return %0#0, %0#1, %0#2 : tensor<*xui48>, tensor<?xi48>, tensor<2xi32>
+}
+
+// -----
+
+// Convert module metadata, including constant payloads without SSA users.
+// CHECK-LABEL: module @module_attributes
+// CHECK-SAME: test.constant = dense<[-1, 140737488355327]> : tensor<2xi64>
+// CHECK-SAME: test.type = tensor<2xi64>
+// CHECK-SAME: test.value = -1 : i64
+module @module_attributes attributes {
+ test.constant = dense<[-1, 140737488355327]> : tensor<2xi48>,
+ test.type = tensor<2xi48>,
+ test.value = -1 : i48
+} {}
+
+// -----
+
+// Convert function, argument and result metadata along with the signature.
+// CHECK-LABEL: func.func @function_attributes(
+// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64> {test.type = tensor<2xi64>}) -> (tensor<2xi64> {test.type = tensor<2xi64>}) attributes {test.value = -1 : i64}
+func.func @function_attributes(%arg: tensor<2xi48> {test.type = tensor<2xi48>}) -> (tensor<2xi48> {test.type = tensor<2xi48>}) attributes {test.value = -1 : i48} {
+ // CHECK: return %[[ARG]] : tensor<2xi64>
+ return %arg : tensor<2xi48>
+}
+
+// -----
+
+// Convert types embedded in tensor encodings while preserving other metadata.
+// CHECK-LABEL: func.func @encoded_tensor(
+// CHECK-SAME: %[[ARG:.*]]: tensor<?x2xi64, {i = i64, j = "k"}>) -> tensor<?x2xi64, {i = i64, j = "k"}>
+func.func @encoded_tensor(%arg: tensor<?x2xi48, {i = i48, j = "k"}>) -> tensor<?x2xi48, {i = i48, j = "k"}> {
+ // CHECK: return %[[ARG]] : tensor<?x2xi64, {i = i64, j = "k"}>
+ return %arg : tensor<?x2xi48, {i = i48, j = "k"}>
+}
+
+// -----
+
+// Preserve stride metadata on tensor arguments and results while widening.
+// CHECK-LABEL: func.func @strided_tensor(
+// CHECK-SAME: %[[ARG:.*]]: tensor<2x3xi64> {test.strides = strided<[5, 1]>}) -> (tensor<2x3xi64> {test.strides = strided<[5, 1]>})
+func.func @strided_tensor(%arg: tensor<2x3xi48> {test.strides = strided<[5, 1]>}) -> (tensor<2x3xi48> {test.strides = strided<[5, 1]>}) {
+ // CHECK: return %[[ARG]] : tensor<2x3xi64>
+ return %arg : tensor<2x3xi48>
+}
+
+// -----
+
+// Preserve a memref's non-default strides and offset while widening its elements.
+// CHECK-LABEL: func.func @strided_memref(
+// CHECK-SAME: %[[ARG:.*]]: memref<2x3xi64, strided<[5, 1], offset: 2>>) -> memref<2x3xi64, strided<[5, 1], offset: 2>>
+func.func @strided_memref(%arg: memref<2x3xi48, strided<[5, 1], offset: 2>>) -> memref<2x3xi48, strided<[5, 1], offset: 2>> {
+ // CHECK: return %[[ARG]] : memref<2x3xi64, strided<[5, 1], offset: 2>>
+ return %arg : memref<2x3xi48, strided<[5, 1], offset: 2>>
+}
+
+// -----
+
+// Convert a function reference, its indirect call and a function-valued result.
+// CHECK: func.func private @referenced(tensor<2xi64>) -> tensor<2xi64>
+func.func private @referenced(tensor<2xi48>) -> tensor<2xi48>
+// CHECK-LABEL: func.func @function_reference(
+// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64>) -> (tensor<2xi64>, (tensor<2xi64>) -> tensor<2xi64>)
+func.func @function_reference(%arg: tensor<2xi48>) -> (tensor<2xi48>, (tensor<2xi48>) -> tensor<2xi48>) {
+ // CHECK: %[[FN:.*]] = constant @referenced : (tensor<2xi64>) -> tensor<2xi64>
+ %fn = func.constant @referenced : (tensor<2xi48>) -> tensor<2xi48>
+ // CHECK: %[[RESULT:.*]] = call_indirect %[[FN]](%[[ARG]]) : (tensor<2xi64>) -> tensor<2xi64>
+ %result = func.call_indirect %fn(%arg) : (tensor<2xi48>) -> tensor<2xi48>
+ // CHECK: return %[[RESULT]], %[[FN]] : tensor<2xi64>, (tensor<2xi64>) -> tensor<2xi64>
+ return %result, %fn : tensor<2xi48>, (tensor<2xi48>) -> tensor<2xi48>
+}
+
+// -----
+
+// Widen an arithmetic producer and its TOSA user inside a non-TOSA container.
+// CHECK-LABEL: func.func @nested_tosa()
+func.func @nested_tosa() {
+ // CHECK: scf.execute_region {
+ scf.execute_region {
+ // CHECK: %[[C:.*]] = arith.constant dense<-1> : tensor<2xi64>
+ %c = arith.constant dense<-1> : tensor<2xi48>
+ // CHECK: tosa.identity %[[C]] : (tensor<2xi64>) -> tensor<2xi64>
+ %0 = tosa.identity %c : (tensor<2xi48>) -> tensor<2xi48>
+ // CHECK-NEXT: scf.yield
+ scf.yield
+ }
+ // CHECK: return
+ return
+}
+// -----
+
+// Only the selected i4 and i48 widths change. Arbitrary widths such as i3
+// remain unchanged.
+// CHECK: func.func private @types(i1, i3, i8, i7, i8, i9, i16, i24, i32, i64, i64, i128) -> (si3, ui3)
+func.func private @types(i1, i3, i4, i7, i8, i9, i16, i24, i32, i48, i64, i128) -> (si3, ui3)
+
+// -----
+
+// Constant payloads and resource storage.
+
+// CHECK-LABEL: func.func @subbyte_constants()
+// CHECK-SAME: -> (tensor<5xi8>, tensor<2x3xi8>, tensor<3xui8>)
+func.func @subbyte_constants() -> (tensor<5xi4>, tensor<2x3xi4>, tensor<3xui4>) {
+ // CHECK: tosa.const values(dense<[-7, -1, 0, 1, 7]> : tensor<5xi8>)
+ %a = tosa.const values(dense<[-7, -1, 0, 1, 7]> : tensor<5xi4>) : () -> tensor<5xi4>
+ // CHECK: tosa.const values(dense<-7> : tensor<2x3xi8>)
+ %b = tosa.const values(dense<-7> : tensor<2x3xi4>) : () -> tensor<2x3xi4>
+ // CHECK: tosa.const values(dense<[0, 8, 15]> : tensor<3xui8>)
+ %c = tosa.const values(dense<[0, 8, 15]> : tensor<3xui4>) : () -> tensor<3xui4>
+ return %a, %b, %c : tensor<5xi4>, tensor<2x3xi4>, tensor<3xui4>
+}
+
+// -----
+
+// Widen scalar and dense attributes, including explicitly signed types.
+// CHECK-LABEL: func.func @attributes(
+// CHECK-SAME: tensor<2xi8, {type = i8}>) -> tensor<2xi8, {type = i8}>
+func.func @attributes(%arg: tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i4}> {
+ // CHECK: tosa.identity %arg0
+ // CHECK-SAME: test.nested = [{signed = -7 : si8, signless = -1 : i8, unsigned = 15 : ui8}]
+ // CHECK-SAME: test.signed = dense<[-7, -1, 7]> : tensor<3xsi8>
+ %0 = tosa.identity %arg {
+ test.nested = [{signed = -7 : si4, signless = -1 : i4, unsigned = 15 : ui4}],
+ test.signed = dense<[-7, -1, 7]> : tensor<3xsi4>
+ } : (tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i4}>
+ return %0 : tensor<2xi4, {type = i4}>
+}
+
+// -----
+
+// Widen sparse i4 values while preserving their coordinates.
+// CHECK-LABEL: func.func @subbyte_sparse_constant()
+// CHECK-SAME: -> tensor<4xi8>
+func.func @subbyte_sparse_constant() -> tensor<4xi4> {
+ // CHECK: tosa.const values(sparse<{{\[\[}}1], [3]], [-7, 7]> : tensor<4xi8>)
+ %0 = tosa.const values(sparse<[[1], [3]], [-7, 7]> : tensor<4xi4>) : () -> tensor<4xi4>
+ return %0 : tensor<4xi4>
+}
+
+// -----
+
+// i4 resources use one byte per element. Preserve the original blob for an i8
+// view, share the widened resource, and retain storage for repeated values.
+// CHECK: module attributes {test.bytes = dense_resource<signed_data> : tensor<5xi8>, test.original = dense_resource<[[SIGNED:[a-zA-Z0-9_]+]]> : tensor<5xi8>}
+module attributes {
+ test.bytes = dense_resource<signed_data> : tensor<5xi8>,
+ test.original = dense_resource<signed_data> : tensor<5xi4>
+} {
+ // CHECK: func.func @resources()
+ // CHECK-SAME: attributes {test.signed = dense_resource<[[REPEATED:[a-zA-Z0-9_]+]]> : tensor<3xsi8>}
+ func.func @resources() attributes {test.signed = dense_resource<repeated> : tensor<3xsi4>} {
+ // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi8>)
+ %a = tosa.const values(dense_resource<signed_data> : tensor<5xi4>) : () -> tensor<5xi4>
+ // CHECK: tosa.const values(dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui8>)
+ %b = tosa.const values(dense_resource<unsigned_data> : tensor<3xui4>) : () -> tensor<3xui4>
+ return
+ }
+ // CHECK: func.func @shared_resource()
+ func.func @shared_resource() {
+ // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi8>)
+ %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi4>) : () -> tensor<5xi4>
+ return
+ }
+}
+{-# dialect_resources: { builtin: {
+ // Alignment 1 followed by [-7, -1, 0, 1, 7], [0, 8, 15], and [-1, -1, -1].
+ // CHECK-DAG: signed_data: "0x01000000090F000107"
+ signed_data: "0x01000000090F000107",
+ unsigned_data: "0x0100000000080F",
+ repeated: "0x010000000F0F0F"
+ // CHECK-DAG: [[SIGNED]]: "0x01000000F9FF000107"
+ // CHECK-DAG: [[UNSIGNED]]: "0x0100000000080F"
+ // CHECK-DAG: [[REPEATED]]: "0x01000000FFFFFF"
+} } #-}
+
+// -----
+
+// CHECK-LABEL: func.func @subbyte_dynamic_resource()
+// CHECK-SAME: -> tensor<?xi8>
+func.func @subbyte_dynamic_resource() -> tensor<?xi4> {
+ // CHECK: tosa.const values(dense_resource<[[DYNAMIC:[a-zA-Z0-9_]+]]> : tensor<?xi8>)
+ %0 = tosa.const values(dense_resource<dynamic> : tensor<?xi4>) : () -> tensor<?xi4>
+ return %0 : tensor<?xi4>
+}
+{-# dialect_resources: { builtin: {
+ dynamic: "0x01000000090F"
+ // CHECK: [[DYNAMIC]]: "0x01000000F9FF"
+} } #-}
+// -----
+
+// Preserve sparse indices and implicit zeros while widening stored values.
+// CHECK-LABEL: func.func @sparse_constant()
+// CHECK-SAME: -> tensor<4xi64>
+func.func @sparse_constant() -> tensor<4xi48> {
+ // CHECK: %[[C:.*]] = tosa.const values(sparse<{{\[\[}}1], [3]], [-1, 140737488355327]> : tensor<4xi64>) : () -> tensor<4xi64>
+ %0 = tosa.const values(sparse<[[1], [3]], [-1, 140737488355327]> : tensor<4xi48>) : () -> tensor<4xi48>
+ // CHECK-NEXT: return %[[C]] : tensor<4xi64>
+ return %0 : tensor<4xi48>
+}
+
+// -----
+
+// Preserve floating-point values while widening i48 in the tensor encoding.
+// CHECK-LABEL: func.func @float_constant_encoding_only()
+// CHECK-SAME: -> tensor<2xf32, {i = i64}>
+func.func @float_constant_encoding_only() -> tensor<2xf32, {i = i48}> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<[-1.000000e+00, 7.000000e+00]> : tensor<2xf32, {i = i64}>) : () -> tensor<2xf32, {i = i64}>
+ %0 = tosa.const values(dense<[-1.0, 7.0]> : tensor<2xf32, {i = i48}>) : () -> tensor<2xf32, {i = i48}>
+ // CHECK-NEXT: return %[[C]] : tensor<2xf32, {i = i64}>
+ return %0 : tensor<2xf32, {i = i48}>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @constants
+func.func @constants() -> (tensor<5xi48>, tensor<2x3xi48>, tensor<i48>) {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<[-140737488355328, -1, 0, 1, 140737488355327]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense<[-140737488355328, -1, 0, 1, 140737488355327]> : tensor<5xi48>) : () -> tensor<5xi48>
+ // CHECK: tosa.const values(dense<-1> : tensor<2x3xi64>) : () -> tensor<2x3xi64>
+ %1 = tosa.const values(dense<-1> : tensor<2x3xi48>) : () -> tensor<2x3xi48>
+ // CHECK: tosa.const values(dense<-140737488355328> : tensor<i64>) : () -> tensor<i64>
+ %3 = tosa.const values(dense<-140737488355328> : tensor<i48>) : () -> tensor<i48>
+ // CHECK: return %[[C]], %{{.*}}, %{{.*}} : tensor<5xi64>, tensor<2x3xi64>, tensor<i64>
+ return %0, %1, %3 : tensor<5xi48>, tensor<2x3xi48>, tensor<i48>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @unsigned_constants
+func.func @unsigned_constants() -> tensor<3xui48> {
+ // CHECK: tosa.const values(dense<[0, 140737488355328, 281474976710655]> : tensor<3xui64>) : () -> tensor<3xui64>
+ %0 = tosa.const values(dense<[0, 140737488355328, 281474976710655]> : tensor<3xui48>) : () -> tensor<3xui48>
+ return %0 : tensor<3xui48>
+}
+
+// -----
+
+// Widen packed dense arrays without reinterpreting their six-byte elements.
+// Cover signedness, empty arrays and repeated values that must remain arrays.
+// CHECK-LABEL: func.func @dense_arrays(
+func.func @dense_arrays(%arg: tensor<2xi48>) -> tensor<2xi48> {
+ // CHECK: tosa.identity %arg0
+ // CHECK-SAME: test.empty = array<i64>
+ // CHECK-SAME: test.repeated = array<i64: -1, -1, -1>
+ // CHECK-SAME: test.signed = array<si64: -140737488355328, -1, 140737488355327>
+ // CHECK-SAME: test.signless = array<i64: 1, -1, 2>
+ // CHECK-SAME: test.unsigned = array<ui64: 0, 140737488355328, 281474976710655>
+ %0 = tosa.identity %arg {
+ test.empty = array<i48>,
+ test.repeated = array<i48: -1, -1, -1>,
+ test.signed = array<si48: -140737488355328, -1, 140737488355327>,
+ test.signless = array<i48: 1, -1, 2>,
+ test.unsigned = array<ui48: 0, 140737488355328, 281474976710655>
+ } : (tensor<2xi48>) -> tensor<2xi48>
+ return %0 : tensor<2xi48>
+}
+
+// -----
+
+// Dense constant attributes and results must use the same widened encoding.
+// CHECK-LABEL: func.func @encoded_constant()
+// CHECK-SAME: -> tensor<2xi64, {i = i64, j = "k"}>
+func.func @encoded_constant() -> tensor<2xi48, {i = i48, j = "k"}> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<-1> : tensor<2xi64, {i = i64, j = "k"}>) : () -> tensor<2xi64, {i = i64, j = "k"}>
+ %0 = tosa.const values(dense<-1> : tensor<2xi48, {i = i48, j = "k"}>) : () -> tensor<2xi48, {i = i48, j = "k"}>
+ // CHECK-NEXT: return %[[C]] : tensor<2xi64, {i = i64, j = "k"}>
+ return %0 : tensor<2xi48, {i = i48, j = "k"}>
+}
+
+// -----
+
+// Convert dense constant encodings even when the element type is unchanged.
+// CHECK-LABEL: func.func @encoded_constant_encoding_only()
+// CHECK-SAME: -> tensor<2xi32, {i = i64, j = "k"}>
+func.func @encoded_constant_encoding_only() -> tensor<2xi32, {i = i48, j = "k"}> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<[-1, 7]> : tensor<2xi32, {i = i64, j = "k"}>) : () -> tensor<2xi32, {i = i64, j = "k"}>
+ %0 = tosa.const values(dense<[-1, 7]> : tensor<2xi32, {i = i48, j = "k"}>) : () -> tensor<2xi32, {i = i48, j = "k"}>
+ // CHECK-NEXT: return %[[C]] : tensor<2xi32, {i = i64, j = "k"}>
+ return %0 : tensor<2xi32, {i = i48, j = "k"}>
+}
+
+// -----
+
+// Preserve layout metadata and element order while widening a dense constant.
+// CHECK-LABEL: func.func @strided_constant()
+// CHECK-SAME: -> tensor<2x2xi64>
+func.func @strided_constant() -> tensor<2x2xi48> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense<{{\[}}[-140737488355328, -1], [0, 140737488355327]]> : tensor<2x2xi64>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi64>
+ %0 = tosa.const values(dense<[[-140737488355328, -1], [0, 140737488355327]]> : tensor<2x2xi48>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi48>
+ // CHECK-NEXT: return %[[C]] : tensor<2x2xi64>
+ return %0 : tensor<2x2xi48>
+}
+
+// -----
+
+// Preserve layout metadata and element order while widening a resource payload.
+// CHECK-LABEL: func.func @strided_resource_constant()
+// CHECK-SAME: -> tensor<2x2xi64>
+func.func @strided_resource_constant() -> tensor<2x2xi48> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense_resource<[[STRIDED:[a-zA-Z0-9_]+]]> : tensor<2x2xi64>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi64>
+ %0 = tosa.const values(dense_resource<strided> : tensor<2x2xi48>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi48>
+ // CHECK-NEXT: return %[[C]] : tensor<2x2xi64>
+ return %0 : tensor<2x2xi48>
+}
+
+{-# dialect_resources: { builtin: {
+ // Signed i48 minimum, -1, zero and maximum, sign-extended to i64 in order.
+ // CHECK: [[STRIDED]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFF7F0000"
+ strided: "0x01000000000000000080FFFFFFFFFFFF000000000000FFFFFFFFFF7F"
+} } #-}
+
+// -----
+
+// Repeated values must retain storage for every element in the resource.
+// CHECK-LABEL: func.func @repeated_resource()
+func.func @repeated_resource() -> tensor<2x2xi48> {
+ // CHECK: tosa.const values(dense_resource<[[REPEATED:[a-zA-Z0-9_]+]]> : tensor<2x2xi64>)
+ %0 = tosa.const values(dense_resource<repeated> : tensor<2x2xi48>) : () -> tensor<2x2xi48>
+ return %0 : tensor<2x2xi48>
+}
+
+{-# dialect_resources: { builtin: {
+ // Four copies of -1, each occupying six bytes in the input and eight in the output.
+ // CHECK: [[REPEATED]]: "0x08000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ repeated: "0x01000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+} } #-}
+
+// -----
+
+// Widen each complete element in the resource without resolving its shape.
+// CHECK-LABEL: func.func @dynamic_resource()
+// CHECK-SAME: -> tensor<?xi64>
+func.func @dynamic_resource() -> tensor<?xi48> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense_resource<[[DYNAMIC:[a-zA-Z0-9_]+]]> : tensor<?xi64>) : () -> tensor<?xi64>
+ %0 = tosa.const values(dense_resource<dynamic> : tensor<?xi48>) : () -> tensor<?xi48>
+ // CHECK-NEXT: return %[[C]] : tensor<?xi64>
+ return %0 : tensor<?xi48>
+}
+{-#
+ dialect_resources: {
+ builtin: {
+ // Alignment 1 followed by one little-endian i48 value of -1.
+ // CHECK: [[DYNAMIC]]: "0x08000000FFFFFFFFFFFFFFFF"
+ dynamic: "0x01000000FFFFFFFFFFFF"
+ }
+ }
+#-}
+
+// -----
+
+// Unranked tensors are valid in the dialect before shape inference. Widening
+// the resource's elements should preserve its unranked shape.
+// CHECK-LABEL: func.func @unranked_resource()
+// CHECK-SAME: -> tensor<*xi64>
+func.func @unranked_resource() -> tensor<*xi48> {
+ // CHECK: %[[C:.*]] = tosa.const values(dense_resource<[[UNRANKED:[a-zA-Z0-9_]+]]> : tensor<*xi64>) : () -> tensor<*xi64>
+ %0 = tosa.const values(dense_resource<unranked> : tensor<*xi48>) : () -> tensor<*xi48>
+ // CHECK-NEXT: return %[[C]] : tensor<*xi64>
+ return %0 : tensor<*xi48>
+}
+{-#
+ dialect_resources: {
+ builtin: {
+ // Alignment 1 followed by one little-endian i48 value of -1.
+ // CHECK: [[UNRANKED]]: "0x08000000FFFFFFFFFFFFFFFF"
+ unranked: "0x01000000FFFFFFFFFFFF"
+ }
+ }
+#-}
+
+// -----
+
+// Check that dense constants and resource payloads remain correct after
+// one or two runs of the pass.
+// The module attribute and both functions must share the widened resource.
+// An i8 view of the same blob must retain the original bytes.
+// CHECK-LABEL: module attributes
+// CHECK-SAME: {test.bytes = dense_resource<signed_data> : tensor<30xi8>, test.original = dense_resource<[[SIGNED:[a-zA-Z0-9_]+]]> : tensor<5xi64>}
+module attributes {test.bytes = dense_resource<signed_data> : tensor<30xi8>, test.original = dense_resource<signed_data> : tensor<5xi48>} {
+ // CHECK: func.func @signed_resource
+ func.func @signed_resource() {
+ // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi48>) : () -> tensor<5xi48>
+ // CHECK: tosa.const values(dense<-1> : tensor<2xi64>) : () -> tensor<2xi64>
+ %dense = tosa.const values(dense<-1> : tensor<2xi48>) : () -> tensor<2xi48>
+ return
+ }
+ // CHECK: func.func @shared_resource
+ func.func @shared_resource() {
+ // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi48>) : () -> tensor<5xi48>
+ return
+ }
+ // CHECK: func.func @unsigned_resource
+ func.func @unsigned_resource() {
+ // CHECK: tosa.const values(dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui64>) : () -> tensor<3xui64>
+ %0 = tosa.const values(dense_resource<unsigned_data> : tensor<3xui48>) : () -> tensor<3xui48>
+ return
+ }
+}
+
+{-#
+ dialect_resources: {
+ builtin: {
+ // Each blob starts with a four-byte little-endian alignment header.
+ // 01000000 means byte alignment; the remaining bytes are packed i48s.
+ // Signed values: [-140737488355328, -1, 0, 1, 140737488355327].
+ // CHECK-DAG: signed_data: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F"
+ signed_data: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F",
+ // Unsigned values: [0, 140737488355328, 281474976710655].
+ unsigned_data: "0x01000000000000000000000000000080FFFFFFFFFFFF"
+ // Widened blobs have alignment 8 (08000000) and the same numeric values
+ // stored as eight-byte little-endian integers, sign- or zero-extended.
+ // CHECK-DAG: [[SIGNED]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF00000000000000000100000000000000FFFFFFFFFF7F0000"
+ // CHECK-DAG: [[UNSIGNED]]: "0x0800000000000000000000000000000000800000FFFFFFFFFFFF0000"
+ }
+ }
+#-}
+// -----
+
+// Unselected widths remain unchanged in payloads, including unresolved resources.
+// CHECK: module @constants attributes {
+// CHECK-SAME: test.array = array<i128: -1>
+// CHECK-SAME: test.resource = dense_resource<missing> : tensor<2xi128>
+// CHECK-SAME: test.scalar = -1 : i128
+// CHECK-SAME: test.signed = dense<[-4, -1, 0, 3]> : tensor<4xi3>
+// CHECK-SAME: test.unsigned = dense<[0, 4, 7]> : tensor<3xui3>
+module @constants attributes {
+ test.array = array<i128: -1>,
+ test.resource = dense_resource<missing> : tensor<2xi128>,
+ test.scalar = -1 : i128,
+ test.signed = dense<[-4, -1, 0, 3]> : tensor<4xi3>,
+ test.unsigned = dense<[0, 4, 7]> : tensor<3xui3>
+} {}
diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py
index dbd18f8e8b0f2f..865c937595726e 100644
--- a/mlir/test/lit.cfg.py
+++ b/mlir/test/lit.cfg.py
@@ -5,6 +5,7 @@
import re
import shutil
import subprocess
+import sys
import tempfile
import lit.formats
@@ -357,6 +358,8 @@ def enable_llc(llc_executable):
append_path=True,
)
+config.available_features.add("host-byteorder-" + sys.byteorder + "-endian")
+
if config.enable_assertions:
config.available_features.add("asserts")
else:
>From 3f8c3b7c26bc134ba63c7b3ba1e0e26a82e3c5b3 Mon Sep 17 00:00:00 2001
From: Michael Platings <michael.platings at arm.com>
Date: Thu, 22 Oct 2026 15:00:42 +0100
Subject: [PATCH 2/4] fixup! [mlir][tosa] Add integer type conversion pass
---
.../mlir/Dialect/Tosa/Transforms/Passes.td | 2 +
.../Transforms/TosaConvertIntegerTypes.cpp | 127 ++------
.../tosa-convert-integer-types-invalid.mlir | 81 ++---
.../tosa-convert-integer-types-mappings.mlir | 128 +-------
.../tosa-convert-integer-types-options.mlir | 42 +--
.../Tosa/tosa-convert-integer-types.mlir | 282 +-----------------
6 files changed, 86 insertions(+), 576 deletions(-)
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 8241f3c590fdba..0f8250fb38c252 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -224,6 +224,8 @@ def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types", "ModuleOp">
`<source>:<destination>` bitwidth pairs.
A typical use case is `bitwidth-mappings=4:8,48:64` which maps i4 and i48 to
i8 and i64 respectively.
+ Source bitwidths 1, 32 and 64 are not supported because some attributes
+ require these fixed widths.
Mappings apply once to the original width, preserving signedness and
leaving unmapped widths unchanged. Widening sign-extends signed and
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
index f4399fd1629af0..698417e60d30b6 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
@@ -9,7 +9,6 @@
#include "mlir/Dialect/Tosa/Transforms/Passes.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
-#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
#include "mlir/IR/AttrTypeSubElements.h"
#include "mlir/IR/DialectResourceBlobManager.h"
@@ -17,11 +16,9 @@
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MathExtras.h"
-#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
@@ -61,32 +58,6 @@ namespace mlir::tosa {
using namespace mlir;
namespace {
-// Preserve inherent dense arrays and known structural scalars at the operation
-// boundary. Extend this policy for additional storage constraints; all other
-// attributes and operations participate in generic conversion. Identical values
-// in discardable or nested metadata still follow the bitwidth mapping.
-static bool shouldPreserveAttribute(Operation *op, NamedAttribute attr) {
- StringRef name = attr.getName().getValue();
- if (!op->getInherentAttr(name))
- return false;
- if (isa<DenseArrayAttr>(attr.getValue()))
- return true;
- if (isa<tensor::ConcatOp>(op) && name == "dim")
- return true;
- if (op->getName().getDialectNamespace() !=
- tosa::TosaDialect::getDialectNamespace())
- return false;
-
- // TOSA axes and boolean options have operation-defined storage types.
- // Identify these by name: a semantic i1 value is also a BoolAttr, and
- // scalar payloads such as clamp bounds must still be converted.
- return llvm::StringSwitch<bool>(name)
- .Cases({"axis", "local_bound", "inverse", "round", "input_unsigned",
- "output_unsigned", "scale32", "per_channel"},
- true)
- .Default(false);
-}
-
static APInt convertValue(const APInt &value, Type type, unsigned width) {
return type.isUnsignedInteger() ? value.zextOrTrunc(width)
: value.sextOrTrunc(width);
@@ -125,13 +96,6 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
return IntegerType::get(type.getContext(), mapping->second,
type.getSignedness());
});
- // Reject unsupported typed payloads rather than changing just their type.
- // Specific payload handlers below take precedence over this fallback.
- addReplacement([this](TypedAttr attr) -> std::optional<Attribute> {
- if (replace(attr.getType()) != attr.getType())
- return nullptr;
- return std::nullopt;
- });
addReplacement(
[this](IntegerAttr attr) { return skip(convertInteger(attr)); });
addReplacement(
@@ -139,20 +103,17 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
addReplacement([this](DenseTypedElementsAttr attr) {
return skip(convertDenseElements(attr));
});
- addReplacement([this](SparseElementsAttr attr) {
- return skip(convertSparseElements(attr));
- });
addReplacement([this](DenseResourceElementsAttr attr) {
return skip(convertDenseResource(attr));
});
}
+ bool hasFailed() const { return conversionFailed; }
+
private:
Attribute convertInteger(IntegerAttr attr) {
Type type = attr.getType();
Type convertedType = replace(type);
- if (!convertedType)
- return nullptr;
if (type == convertedType)
return attr;
return IntegerAttr::get(
@@ -164,8 +125,6 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
Attribute convertDenseArray(DenseArrayAttr attr) {
Type type = attr.getElementType();
Type convertedType = replace(type);
- if (!convertedType)
- return nullptr;
if (type == convertedType)
return attr;
// Dense array assembly only supports byte-sized integers and i1.
@@ -189,7 +148,7 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
if (elementType != convertedType.getElementType()) {
auto convertedElementType =
dyn_cast<IntegerType>(convertedType.getElementType());
- if (!isa<IntegerType>(elementType) || !convertedElementType)
+ if (!convertedElementType)
return nullptr;
convertedAttr = attr.mapValues(
convertedType.getElementType(),
@@ -201,16 +160,6 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
return convertedAttr.reshape(convertedType);
}
- Attribute convertSparseElements(SparseElementsAttr attr) {
- auto type = dyn_cast_or_null<ShapedType>(replace(attr.getType()));
- auto values =
- dyn_cast_or_null<DenseElementsAttr>(replace(attr.getValues()));
- if (!type || !values)
- return nullptr;
- // Sparse indices must remain i64 even when i64 values are converted.
- return SparseElementsAttr::get(type, attr.getIndices(), values);
- }
-
Attribute convertDenseResource(DenseResourceElementsAttr attr) {
ShapedType type = attr.getType();
Type elementType = type.getElementType();
@@ -220,11 +169,10 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
Type convertedElementType = convertedType.getElementType();
if (elementType == convertedElementType)
return DenseResourceElementsAttr::get(convertedType, attr.getRawHandle());
- auto integerType = dyn_cast<IntegerType>(elementType);
- auto convertedIntegerType =
- dyn_cast_or_null<IntegerType>(convertedElementType);
- if (!integerType || !convertedIntegerType)
+ auto convertedIntegerType = dyn_cast<IntegerType>(convertedElementType);
+ if (!convertedIntegerType)
return nullptr;
+ auto integerType = cast<IntegerType>(elementType);
const AsmResourceBlob *blob = attr.getRawHandle().getBlob();
if (!blob)
return nullptr;
@@ -240,10 +188,8 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
convertRawValues(rawData, integerType, convertedIntegerType);
// Alignment must be a power of two, including for sub-byte and
// non-power-of-two destination widths.
- unsigned alignment =
- std::min<uint64_t>(llvm::PowerOf2Ceil(llvm::divideCeil(
- convertedIntegerType.getWidth(), 8)),
- alignof(uint64_t));
+ unsigned alignment = llvm::PowerOf2Ceil(
+ llvm::divideCeil(convertedIntegerType.getWidth(), 8));
return DenseResourceElementsAttr::get(
convertedType, attr.getRawHandle().getKey(),
HeapAsmResourceBlob::allocateAndCopyWithAlign(data, alignment));
@@ -251,9 +197,13 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
// Payload handlers already converted their subelements. Do not apply a
// second mapping when a destination width is also a source width.
- static ReplaceFnResult<Attribute> skip(Attribute attr) {
+ ReplaceFnResult<Attribute> skip(Attribute attr) {
+ // replaceElementsIn ignores null replacements, so track failures here.
+ conversionFailed |= !attr;
return std::make_pair(attr, WalkResult::skip());
}
+
+ bool conversionFailed = false;
};
struct TosaConvertIntegerTypes
@@ -277,6 +227,14 @@ struct TosaConvertIntegerTypes
return signalPassFailure();
}
}
+ // These widths are fixed by boolean flags, axes, structural arrays and
+ // sparse indices. Reject these sources rather than exempting those
+ // attributes.
+ if (source == 1 || source == 32 || source == 64) {
+ getOperation().emitError("unsupported source integer bitwidth ")
+ << source;
+ return signalPassFailure();
+ }
auto [it, inserted] = mappings.try_emplace(source, destination);
if (!inserted && it->second != destination) {
getOperation().emitError(
@@ -286,44 +244,13 @@ struct TosaConvertIntegerTypes
}
}
IntegerTypeReplacer replacer(mappings);
- WalkResult result = getOperation()->walk([&](Operation *op) -> WalkResult {
- bool invalid = false;
- SmallVector<NamedAttribute> convertedAttrs;
- for (NamedAttribute attr : op->getAttrs()) {
- // Preserve fixed-storage attributes at the operation boundary so the
- // same attribute can still convert as discardable or nested metadata.
- Attribute value = attr.getValue();
- if (!shouldPreserveAttribute(op, attr))
- value = replacer.replace(value);
- if (!value)
- invalid = true;
- else
- convertedAttrs.emplace_back(attr.getName(), value);
- }
- auto replaceValueType = [&](Value value) {
- if (Type type = replacer.replace(value.getType()))
- value.setType(type);
- else
- invalid = true;
- };
- for (Value value : op->getResults())
- replaceValueType(value);
- for (Region ®ion : op->getRegions())
- for (Block &block : region)
- for (BlockArgument arg : block.getArguments())
- replaceValueType(arg);
-
- if (invalid) {
- op->emitError("failed to legalize operation '")
- << op->getName()
- << "': failed to convert integer types or attributes";
- return WalkResult::interrupt();
- }
- op->setAttrs(convertedAttrs);
- return WalkResult::advance();
- });
- if (result.wasInterrupted())
+ replacer.recursivelyReplaceElementsIn(getOperation(), /*replaceAttrs=*/true,
+ /*replaceLocs=*/false,
+ /*replaceTypes=*/true);
+ if (replacer.hasFailed()) {
+ getOperation().emitError("failed to convert integer types or attributes");
signalPassFailure();
+ }
}
};
} // namespace
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
index 58d7efb97fa52e..50a78a76a8f788 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
@@ -1,41 +1,28 @@
// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64,8:4' %s
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=32:32' %s | FileCheck %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=16:16' %s | FileCheck %s
// Unselected widths must leave even missing or malformed resources untouched.
// CHECK-LABEL: func.func @missing_resource()
// CHECK: tosa.const values(dense_resource<missing> : tensor<2xi48>) : () -> tensor<2xi48>
// A missing resource must produce a diagnostic, not an invalid i64 constant.
-func.func @missing_resource() -> tensor<2xi48> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const'}}
- %0 = tosa.const values(dense_resource<missing> : tensor<2xi48>) : () -> tensor<2xi48>
- return %0 : tensor<2xi48>
-}
-
-// -----
-
-// An i48 element occupies six bytes; this resource contains only two bytes.
-func.func @malformed_resource() -> tensor<2xi48> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const'}}
- %0 = tosa.const values(dense_resource<malformed> : tensor<2xi48>) : () -> tensor<2xi48>
- return %0 : tensor<2xi48>
-}
-{-#
- dialect_resources: {
- builtin: {
- // 01000000: alignment 1. Payload: bytes 01 02, too short for an i48.
- malformed: "0x010000000102"
- }
+// expected-error @+1 {{failed to convert integer types or attributes}}
+module {
+ func.func @missing_resource() -> tensor<2xi48> {
+ %0 = tosa.const values(dense_resource<missing> : tensor<2xi48>) : () -> tensor<2xi48>
+ return %0 : tensor<2xi48>
}
-#-}
+}
// -----
// Complete elements must match the element count of a static shape.
-func.func @resource_wrong_element_count() -> tensor<2xi48> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
- %0 = tosa.const values(dense_resource<short> : tensor<2xi48>) : () -> tensor<2xi48>
- return %0 : tensor<2xi48>
+// expected-error @+1 {{failed to convert integer types or attributes}}
+module {
+ func.func @resource_wrong_element_count() -> tensor<2xi48> {
+ %0 = tosa.const values(dense_resource<short> : tensor<2xi48>) : () -> tensor<2xi48>
+ return %0 : tensor<2xi48>
+ }
}
{-# dialect_resources: { builtin: {
// Alignment 1 followed by one i48 value; the shape requires two.
@@ -45,10 +32,12 @@ func.func @resource_wrong_element_count() -> tensor<2xi48> {
// -----
// An unresolved shape must not allow a payload containing an incomplete i48.
-func.func @malformed_dynamic_resource() -> tensor<?xi48> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
- %0 = tosa.const values(dense_resource<partial> : tensor<?xi48>) : () -> tensor<?xi48>
- return %0 : tensor<?xi48>
+// expected-error @+1 {{failed to convert integer types or attributes}}
+module {
+ func.func @malformed_dynamic_resource() -> tensor<?xi48> {
+ %0 = tosa.const values(dense_resource<partial> : tensor<?xi48>) : () -> tensor<?xi48>
+ return %0 : tensor<?xi48>
+ }
}
{-# dialect_resources: { builtin: {
// Alignment 1 followed by only two payload bytes.
@@ -59,29 +48,23 @@ func.func @malformed_dynamic_resource() -> tensor<?xi48> {
// Report missing i48 resources nested in attributes even when the operation's
// operand and result types need no widening.
-func.func @nested_missing_resource(%arg: tensor<2xi32>) -> tensor<2xi32> {
- // expected-error @+1 {{failed to convert integer types or attributes}}
- %0 = tosa.identity %arg {test.nested = [{value = dense_resource<missing> : tensor<2xi48>}]} : (tensor<2xi32>) -> tensor<2xi32>
- return %0 : tensor<2xi32>
-}
-
-// -----
-
-// CHECK-LABEL: func.func @missing_i4_resource()
-// CHECK: tosa.const values(dense_resource<missing> : tensor<2xi4>) : () -> tensor<2xi4>
-func.func @missing_i4_resource() -> tensor<2xi4> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
- %0 = tosa.const values(dense_resource<missing> : tensor<2xi4>) : () -> tensor<2xi4>
- return %0 : tensor<2xi4>
+// expected-error @+1 {{failed to convert integer types or attributes}}
+module {
+ func.func @nested_missing_resource(%arg: tensor<2xi32>) -> tensor<2xi32> {
+ %0 = tosa.identity %arg {test.nested = [{value = dense_resource<missing> : tensor<2xi48>}]} : (tensor<2xi32>) -> tensor<2xi32>
+ return %0 : tensor<2xi32>
+ }
}
// -----
// Each i4 requires one byte; a blob packing two nibbles per byte is too short.
-func.func @packed_i4_resource() -> tensor<2xi4> {
- // expected-error @+1 {{failed to legalize operation 'tosa.const': failed to convert integer types or attributes}}
- %0 = tosa.const values(dense_resource<packed> : tensor<2xi4>) : () -> tensor<2xi4>
- return %0 : tensor<2xi4>
+// expected-error @+1 {{failed to convert integer types or attributes}}
+module {
+ func.func @packed_i4_resource() -> tensor<2xi4> {
+ %0 = tosa.const values(dense_resource<packed> : tensor<2xi4>) : () -> tensor<2xi4>
+ return %0 : tensor<2xi4>
+ }
}
{-# dialect_resources: { builtin: {
packed: "0x0100000079"
@@ -90,5 +73,5 @@ func.func @packed_i4_resource() -> tensor<2xi4> {
// -----
// Dense arrays cannot represent sub-byte destinations other than i1.
-// expected-error @+1 {{failed to legalize operation 'builtin.module': failed to convert integer types or attributes}}
+// expected-error @+1 {{failed to convert integer types or attributes}}
module attributes {test.array = array<i8: -1, 7>} {}
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
index 0a454067087e42..4a4ad6f49f1e6e 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
@@ -1,26 +1,11 @@
// REQUIRES: host-byteorder-little-endian
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=1:8,3:4,4:8,8:4,13:16,16:1,24:32,32:24,48:64,64:48,65:128,128:65' -mlir-print-op-generic %s | FileCheck %s --check-prefixes=CHECK,SCALAR '-D$WIDTH=24'
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=128:65,65:128,64:48,48:64,32:24,24:32,16:1,13:16,8:4,4:8,3:4,1:8' -mlir-print-op-generic %s | FileCheck %s --check-prefixes=CHECK,SCALAR '-D$WIDTH=24'
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=1:8,32:64' -mlir-print-op-generic %s | FileCheck %s --check-prefix=SCALAR '-D$WIDTH=64'
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=3:4,4:8,8:4,13:16,16:1,24:32,40:24,48:64,56:48,65:128,128:65' -mlir-print-op-generic %s | FileCheck %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=128:65,65:128,56:48,48:64,40:24,24:32,16:1,13:16,8:4,4:8,3:4' -mlir-print-op-generic %s | FileCheck %s
// Each mapping applies once to the original width, including cycles and
// destinations that are also sources. Reversing the list must have no effect.
-// CHECK: "func.func"() <{function_type = (i8, i4, si4, ui4, i8, i4, i16, i1, i32, i24, i64, i48, i128, i65) -> (), sym_name = "types", sym_visibility = "private"}>
-func.func private @types(i1, i3, si3, ui3, i4, i8, i13, i16, i24, i32, i48, i64, i65, i128)
-
-// -----
-
-// Conversion truncates, including when values do not fit in the destination.
-// CHECK: "func.func"() <{function_type = (tensor<4xi4>, tensor<2xi48>) -> tensor<4xi4>, sym_name = "callee", sym_visibility = "private"}>
-func.func private @callee(tensor<4xi8>, tensor<2xi64>) -> tensor<4xi8>
-// CHECK-LABEL: "func.func"() <{function_type = (tensor<2xi48>) -> tensor<4xi4>, sym_name = "caller"}>
-func.func @caller(%arg: tensor<2xi64>) -> tensor<4xi8> {
- // CHECK: "tosa.const"() <{values = dense<[-7, 7, -1, 0]> : tensor<4xi4>}>
- %0 = tosa.const values(dense<[9, 23, -1, -16]> : tensor<4xi8>) : () -> tensor<4xi8>
- // CHECK: "func.call"(%{{.*}}, %{{.*}}) <{callee = @callee}> : (tensor<4xi4>, tensor<2xi48>) -> tensor<4xi4>
- %1 = func.call @callee(%0, %arg) : (tensor<4xi8>, tensor<2xi64>) -> tensor<4xi8>
- return %1 : tensor<4xi8>
-}
+// CHECK: "func.func"() <{function_type = (i4, si4, ui4, i8, i4, i16, i1, i32, i24, i64, i48, i128, i65) -> (), sym_name = "types", sym_visibility = "private"}>
+func.func private @types(i3, si3, ui3, i4, i8, i13, i16, i24, i40, i48, i56, i65, i128)
// -----
@@ -39,28 +24,18 @@ func.func @caller(%arg: tensor<2xi64>) -> tensor<4xi8> {
module @narrow_constants attributes {
test.a_scalar = 25 : si8,
test.b_unsigned = 255 : ui8,
- test.c_array = array<i32: 8388608, 25165823, -1, -16777216>,
+ test.c_array = array<i40: 8388608, 25165823, -1, -16777216>,
test.d_splat = dense<25> : tensor<3xsi8>,
test.e_unsigned = dense<[8, 23, 255, 16]> : tensor<4xui8>,
- test.f_sparse = sparse<[[1], [3]], [-1, 281474976710656]> : tensor<4xi64>,
+ test.f_sparse = sparse<[[1], [3]], [-1, 281474976710656]> : tensor<4xi56>,
test.g_i1_array = array<i16: -1, 2, 3>,
test.h_i1_dense = dense<[-1, 2, 3]> : tensor<3xi16>,
- test.i_types = [i32, si64, ui128],
+ test.i_types = [i40, si56, ui128],
test.j_wide_sparse = sparse<[[1], [3]], [-1, 18446744073709551615]> : tensor<4xi128>
} {}
// -----
-// The 64:48 mapping must truncate stored values, but preserve coordinates.
-// Bit 48 distinguishes this index from zero even without printed index types.
-// CHECK-LABEL: "builtin.module"() <{sym_name = "sparse_indices"}>
-// CHECK: test.sparse = sparse<{{\[\[}}0], [281474976710656]], [-1, 0]> : tensor<281474976710657xi48>
-module @sparse_indices attributes {
- test.sparse = sparse<[[0], [281474976710656]], [-1, 281474976710656]> : tensor<281474976710657xi64>
-} {}
-
-// -----
-
// Widen sub-byte, non-power-of-two and greater-than-64-bit payloads.
// CHECK-LABEL: "builtin.module"() <{sym_name = "widen_constants"}>
// CHECK: test.array = array<i32: -8388608, 8388607>
@@ -107,22 +82,22 @@ module @mapping_encodings attributes {
// CHECK-SAME: test.e_i1 = dense_resource<[[I1:[a-zA-Z0-9_]+]]> : tensor<3xi1>
module @narrow_resources attributes {
test.a_i4 = dense_resource<to4> : tensor<4xi8>,
- test.b_i24 = dense_resource<to24> : tensor<2xi32>,
- test.c_i48 = dense_resource<to48> : tensor<2xi64>,
+ test.b_i24 = dense_resource<to24> : tensor<2xi40>,
+ test.c_i48 = dense_resource<to48> : tensor<2xi56>,
test.d_i65 = dense_resource<to65> : tensor<2xi128>,
test.e_i1 = dense_resource<to1> : tensor<3xi16>
} {}
{-# dialect_resources: { builtin: {
to4: "0x010000000917FFF0",
- to24: "0x01000000FFFFFFFFFFFFFF00",
- to48: "0x01000000FFFFFFFFFFFFFFFF0000000000000100",
+ to24: "0x01000000FFFFFFFFFFFFFFFFFF00",
+ to48: "0x01000000FFFFFFFFFFFFFF00000000000001",
to65: "0x01000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000",
to1: "0x01000000FFFF02000300"
// CHECK-DAG: [[I4]]: "0x0100000009070F00"
// CHECK-DAG: [[I24]]: "0x04000000FFFFFFFFFFFF"
// CHECK-DAG: [[I48]]: "0x08000000FFFFFFFFFFFF000000000000"
- // CHECK-DAG: [[I65]]: "0x08000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00"
+ // CHECK-DAG: [[I65]]: "0x10000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00"
// CHECK-DAG: [[I1]]: "0x01000000010001"
} } #-}
@@ -157,83 +132,6 @@ module @widen_resources attributes {
// CHECK-DAG: [[SUBBYTE]]: "0x010000000C0F0003"
// CHECK-DAG: [[I16]]: "0x0200000000F0FF0F"
// CHECK-DAG: [[I32]]: "0x04000000000080FFFFFF7F00"
- // CHECK-DAG: [[I128]]: "0x08000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000"
+ // CHECK-DAG: [[I128]]: "0x10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000"
// CHECK-DAG: [[UNSIGNED]]: "0x01000000000407"
} } #-}
-
-// -----
-
-// Structural attributes have operation-defined storage types. Mapping i64 to
-// i48 must preserve the required DenseI64ArrayAttr properties, even though
-// standalone dense arrays can be converted. All tensor elements here are f32
-// to isolate changes to structural attributes from tensor type conversion.
-// Generic printing exposes the attribute storage types.
-// The discardable test.pad attribute shares the original padding array but
-// must still be converted to i48.
-
-// CHECK: "tosa.conv2d"
-// CHECK-SAME: acc_type = f32
-// CHECK-SAME: dilation = array<i64: 1, 1>
-// CHECK-SAME: pad = array<i64: 0, 0, 0, 0>
-// CHECK-SAME: stride = array<i64: 1, 1>
-// CHECK-SAME: test.pad = array<i48: 0, 0, 0, 0>
-// CHECK-SAME: -> tensor<1x3x3x1xf32>
-func.func @conv2d(%input: tensor<1x3x3x1xf32>, %weight: tensor<1x1x1x1xf32>, %bias: tensor<1xf32>, %izp: tensor<1xf32>, %wzp: tensor<1xf32>) -> tensor<1x3x3x1xf32> {
- %0 = tosa.conv2d %input, %weight, %bias, %izp, %wzp pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(f32) {test.pad = array<i64: 0, 0, 0, 0>} : (tensor<1x3x3x1xf32>, tensor<1x1x1x1xf32>, tensor<1xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x3x3x1xf32>
- return %0 : tensor<1x3x3x1xf32>
-}
-
-// -----
-
-// CHECK: "tosa.avg_pool2d"
-// CHECK-SAME: acc_type = f32
-// CHECK-SAME: kernel = array<i64: 2, 2>
-// CHECK-SAME: pad = array<i64: 0, 1, 0, 1>
-// CHECK-SAME: stride = array<i64: 1, 1>
-// CHECK-SAME: -> tensor<1x7x7x9xf32>
-func.func @avg_pool2d(%input: tensor<1x7x7x9xf32>, %izp: tensor<1xf32>, %ozp: tensor<1xf32>) -> tensor<1x7x7x9xf32> {
- %0 = tosa.avg_pool2d %input, %izp, %ozp kernel([2, 2]) stride([1, 1]) pad([0, 1, 0, 1]) acc_type(f32) : (tensor<1x7x7x9xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x7x7x9xf32>
- return %0 : tensor<1x7x7x9xf32>
-}
-
-// -----
-
-// TOSA flags keep their boolean storage, while the same values in metadata
-// and arithmetic constants follow the mapping. Check true and false explicitly
-// because dropping an optional flag can silently restore its default value.
-// SCALAR-LABEL: sym_name = "boolean_flags"
-func.func @boolean_flags(%arg: tensor<2xi32>) -> tensor<2xi32> {
- // SCALAR: "tosa.cast"
- // SCALAR-SAME: input_unsigned = true
- // SCALAR-SAME: test.flag = -1 : i8
- // SCALAR-SAME: test.nested = {flag = -1 : i8}
- %0 = tosa.cast %arg input_unsigned(true) {test.flag = true, test.nested = {flag = true}} : (tensor<2xi32>) -> tensor<2xi32>
- // SCALAR: "tosa.cast"
- // SCALAR-SAME: input_unsigned = false
- // SCALAR-SAME: test.flag = 0 : i8
- %1 = tosa.cast %0 input_unsigned(false) {test.flag = false} : (tensor<2xi32>) -> tensor<2xi32>
- // SCALAR: "arith.constant"() <{value = -1 : i8}>
- %true = arith.constant true
- // SCALAR: "arith.constant"() <{value = 0 : i8}>
- %false = arith.constant false
- return %1 : tensor<2xi32>
-}
-
-// -----
-
-// Axes retain their required i32 storage during both widening and narrowing.
-// Scalar clamp bounds, discardable attributes and nested metadata still change.
-// SCALAR-LABEL: sym_name = "scalar_attributes"
-func.func @scalar_attributes(%arg: tensor<2x3xi32>) -> tensor<2x1xi32> {
- // SCALAR: "tosa.clamp"
- // SCALAR-SAME: max_val = 7 : i[[$WIDTH]], min_val = 0 : i[[$WIDTH]]
- // SCALAR-SAME: -> tensor<2x3xi[[$WIDTH]]>
- %0 = tosa.clamp %arg min_val(0 : i32) max_val(7 : i32) : (tensor<2x3xi32>) -> tensor<2x3xi32>
- // SCALAR: "tosa.reduce_sum"
- // SCALAR-SAME: axis = 1 : i32
- // SCALAR-SAME: test.axis = 1 : i[[$WIDTH]]
- // SCALAR-SAME: test.nested = {axis = 1 : i[[$WIDTH]]}
- // SCALAR-SAME: -> tensor<2x1xi[[$WIDTH]]>
- %1 = tosa.reduce_sum %0 axis(1) {test.axis = 1 : i32, test.nested = {axis = 1 : i32}} : (tensor<2x3xi32>) -> tensor<2x1xi32>
- return %1 : tensor<2x1xi32>
-}
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
index 239f1856d7cd16..e6dcae5e87c1c1 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
@@ -2,12 +2,8 @@
// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64,4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' %s | FileCheck %s '-D$I4=8' '-D$I48=48'
// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' %s | FileCheck %s '-D$I4=4' '-D$I48=64'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=32:32' %s | FileCheck %s '-D$I4=4' '-D$I48=48'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' %s | mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' | FileCheck %s '-D$I4=8' '-D$I48=64'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' %s | mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' | FileCheck %s '-D$I4=8' '-D$I48=64'
//
// RUN: not mlir-opt -tosa-convert-integer-types %s 2>&1 | FileCheck %s --check-prefix=MISSING
-// RUN: not mlir-opt -pass-pipeline='builtin.module(tosa-convert-integer-types)' %s 2>&1 | FileCheck %s --check-prefix=MISSING
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=' %s 2>&1 | FileCheck %s --check-prefix=MISSING
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=0:8,4:8' %s 2>&1 | FileCheck %s --check-prefix=ZERO
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=16777216:16777216' %s 2>&1 | FileCheck %s --check-prefix=TOO-LARGE
@@ -15,36 +11,18 @@
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:0' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION-ZERO
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8,4:16' %s 2>&1 | FileCheck %s --check-prefix=CONFLICT
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8:16' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=-4:8' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:foo' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-// Both conversions must update function signatures, calls, region arguments and
-// nested attributes consistently. The options also control constant payloads.
-// The i32 metadata exercises an explicit identity mapping as well as unmapped types.
-// CHECK: func.func private @callee(tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
-func.func private @callee(tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>)
-
-// CHECK-LABEL: func.func @caller(
-// CHECK-SAME: %{{.*}}: tensor<i1>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
-func.func @caller(%cond: tensor<i1>) -> (tensor<2xi4>, tensor<2xi48>) {
- // CHECK: tosa.const values(dense<[-7, 7]> : tensor<2xi[[$I4]]>)
- %a = tosa.const values(dense<[-7, 7]> : tensor<2xi4>) : () -> tensor<2xi4>
- // CHECK: tosa.const values(dense<[-1, 140737488355327]> : tensor<2xi[[$I48]]>)
- %b = tosa.const values(dense<[-1, 140737488355327]> : tensor<2xi48>) : () -> tensor<2xi48>
- // CHECK: call @callee(%{{.*}}, %{{.*}}) {test.types = [i[[$I4]], i[[$I48]], i32]} : (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>) -> (tensor<2xi[[$I4]]>, tensor<2xi[[$I48]]>)
- %c:2 = func.call @callee(%a, %b) {test.types = [i4, i48, i32]} : (tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>)
- %d:2 = tosa.cond_if %cond (%x = %c#0, %y = %c#1) : tensor<i1> (tensor<2xi4>, tensor<2xi48>) -> (tensor<2xi4>, tensor<2xi48>) {
- // CHECK: ^bb0(%{{.*}}: tensor<2xi[[$I4]]>, %{{.*}}: tensor<2xi[[$I48]]>):
- ^bb0(%x: tensor<2xi4>, %y: tensor<2xi48>):
- tosa.yield %x, %y : tensor<2xi4>, tensor<2xi48>
- } else {
- ^bb0(%x: tensor<2xi4>, %y: tensor<2xi48>):
- tosa.yield %x, %y : tensor<2xi4>, tensor<2xi48>
- }
- return %d#0, %d#1 : tensor<2xi4>, tensor<2xi48>
-}
+// Check selected and unselected widths in both types and constant values.
+// CHECK-LABEL: module attributes {
+// CHECK-SAME: test.i4 = dense<[-7, 7]> : tensor<2xi[[$I4]]>
+// CHECK-SAME: test.i48 = dense<[-1, 140737488355327]> : tensor<2xi[[$I48]]>
+// CHECK-SAME: test.types = [i[[$I4]], i[[$I48]], i1, i32, i64]
+module attributes {
+ test.i4 = dense<[-7, 7]> : tensor<2xi4>,
+ test.i48 = dense<[-1, 140737488355327]> : tensor<2xi48>,
+ test.types = [i4, i48, i1, i32, i64]
+} {}
// MISSING: error: The 'bitwidth-mappings' option must be specified and non-empty.
// ZERO: error: invalid source integer bitwidth 0: expected a value between 1 and 16777215
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
index c7187d79fce416..8838428de3f94a 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
@@ -2,17 +2,6 @@
// RUN: mlir-opt -split-input-file -pass-pipeline='builtin.module(tosa-convert-integer-types{bitwidth-mappings=4:8,48:64})' %s | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' %s | mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
-// CHECK-LABEL: func.func @identity(
-// CHECK-SAME: %[[ARG:.*]]: tensor<?x2xi64>) -> tensor<?x2xi64>
-func.func @identity(%arg: tensor<?x2xi48>) -> tensor<?x2xi48> {
- // CHECK: %[[ID:.*]] = tosa.identity %[[ARG]] {test.marker = "keep"} : (tensor<?x2xi64>) -> tensor<?x2xi64>
- %0 = tosa.identity %arg {test.marker = "keep"} : (tensor<?x2xi48>) -> tensor<?x2xi48>
- // CHECK-NEXT: return %[[ID]] : tensor<?x2xi64>
- return %0 : tensor<?x2xi48>
-}
-
-// -----
-
// CHECK-LABEL: func.func @clamp
func.func @clamp(%arg: tensor<4xi48>) -> tensor<4xi48> {
// CHECK: tosa.clamp %{{.*}} min_val(-140737488355328 : i64) max_val(140737488355327 : i64) {test.marker = "keep"} : (tensor<4xi64>) -> tensor<4xi64>
@@ -22,17 +11,6 @@ func.func @clamp(%arg: tensor<4xi48>) -> tensor<4xi48> {
// -----
-// CHECK-LABEL: func.func @reshape
-func.func @reshape(%arg: tensor<2x3xi48>) -> tensor<6xi48> {
- // CHECK: %[[SHAPE:.*]] = tosa.const_shape values(dense<6> : tensor<1xindex>) : () -> !tosa.shape<1>
- %shape = tosa.const_shape values(dense<6> : tensor<1xindex>) : () -> !tosa.shape<1>
- // CHECK: tosa.reshape %{{.*}}, %[[SHAPE]] : (tensor<2x3xi64>, !tosa.shape<1>) -> tensor<6xi64>
- %0 = tosa.reshape %arg, %shape : (tensor<2x3xi48>, !tosa.shape<1>) -> tensor<6xi48>
- return %0 : tensor<6xi48>
-}
-
-// -----
-
// CHECK-LABEL: func.func @cond_if
func.func @cond_if(%cond: tensor<i1>, %arg: tensor<2xi48>) -> tensor<2xi48> {
// CHECK: %[[IF:.*]] = tosa.cond_if %arg0
@@ -56,58 +34,6 @@ func.func @cond_if(%cond: tensor<i1>, %arg: tensor<2xi48>) -> tensor<2xi48> {
// -----
-// CHECK-LABEL: func.func @while_loop
-func.func @while_loop(%arg: tensor<i48>, %limit: tensor<i48>) -> tensor<i48> {
- // CHECK: %[[LOOP:.*]] = tosa.while_loop
- // CHECK-SAME: (tensor<i64>) -> tensor<i64>
- %0 = tosa.while_loop (%iter = %arg) : (tensor<i48>) -> tensor<i48> {
- // CHECK: tosa.greater %{{.*}}, %{{.*}} : (tensor<i64>, tensor<i64>) -> tensor<i1>
- %cond = tosa.greater %limit, %iter : (tensor<i48>, tensor<i48>) -> tensor<i1>
- // CHECK: tosa.yield %{{.*}} : tensor<i1>
- tosa.yield %cond : tensor<i1>
- } do {
- // CHECK: ^bb0(%[[ITER:.*]]: tensor<i64>):
- ^bb0(%iter: tensor<i48>):
- // CHECK: %[[ONE:.*]] = tosa.const values(dense<1> : tensor<i64>) : () -> tensor<i64>
- %one = tosa.const values(dense<1> : tensor<i48>) : () -> tensor<i48>
- // CHECK: %[[ADD:.*]] = tosa.add %[[ITER]], %[[ONE]] : (tensor<i64>, tensor<i64>) -> tensor<i64>
- %next = tosa.add %iter, %one : (tensor<i48>, tensor<i48>) -> tensor<i48>
- // CHECK: tosa.yield %[[ADD]] : tensor<i64>
- tosa.yield %next : tensor<i48>
- }
- // CHECK: return %[[LOOP]] : tensor<i64>
- return %0 : tensor<i48>
-}
-
-// -----
-
-// CHECK: func.func private @external(tensor<?xi64>) -> tensor<*xi64>
-func.func private @external(tensor<?xi48>) -> tensor<*xi48>
-
-// -----
-
-// Widen function arguments even when the body does not use them.
-// CHECK-LABEL: func.func @unused_arguments(
-// CHECK-SAME: %{{.*}}: tensor<?x2xi64>, %{{.*}}: tensor<*xi64>, %{{.*}}: tensor<2xui64>)
-func.func @unused_arguments(%ranked: tensor<?x2xi48>, %unranked: tensor<*xi48>, %unsigned: tensor<2xui48>) {
- // CHECK-NEXT: return
- return
-}
-
-// -----
-
-// Exercise an accumulator attribute even when no operand or result needs widening.
-// CHECK-LABEL: func.func @accumulator_only
-func.func @accumulator_only(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>, %bias: tensor<1xi32>, %izp: tensor<1xi16>, %wzp: tensor<1xi8>) -> tensor<1x3x3x1xi32> {
- // CHECK: tosa.conv2d
- // CHECK-SAME: acc_type(i64)
- // CHECK-SAME: -> tensor<1x3x3x1xi32>
- %0 = tosa.conv2d %input, %weight, %bias, %izp, %wzp pad([0, 0, 0, 0]) stride([1, 1]) dilation([1, 1]) acc_type(i48) : (tensor<1x3x3x1xi16>, tensor<1x1x1x1xi8>, tensor<1xi32>, tensor<1xi16>, tensor<1xi8>) -> tensor<1x3x3x1xi32>
- return %0 : tensor<1x3x3x1xi32>
-}
-
-// -----
-
// CHECK-LABEL: func.func @conv_rescale(
// CHECK-SAME: %{{.*}}: tensor<1x3x3x1xi16>, %{{.*}}: tensor<1x1x1x1xi8>) -> tensor<1x3x3x1xi8>
func.func @conv_rescale(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>) -> tensor<1x3x3x1xi8> {
@@ -128,33 +54,6 @@ func.func @conv_rescale(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>
// -----
-// CHECK-LABEL: func.func @casts
-func.func @casts(%arg: tensor<2xi32>) -> tensor<2xi8> {
- // CHECK: %[[IN:.*]] = tosa.cast %arg0 input_unsigned(true) {test.marker = "keep"} : (tensor<2xi32>) -> tensor<2xi64>
- %0 = tosa.cast %arg input_unsigned(true) {test.marker = "keep"} : (tensor<2xi32>) -> tensor<2xi64>
- // CHECK: %[[OUT:.*]] = tosa.cast %[[IN]] : (tensor<2xi64>) -> tensor<2xi8>
- %1 = tosa.cast %0 : (tensor<2xi64>) -> tensor<2xi8>
- // CHECK: return %[[OUT]] : tensor<2xi8>
- return %1 : tensor<2xi8>
-}
-
-// -----
-
-// CHECK-LABEL: func.func @unchanged(
-// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64>) -> (tensor<2xi64>, tensor<2xf32>, i32)
-func.func @unchanged(%arg: tensor<2xi64>) -> (tensor<2xi64>, tensor<2xf32>, i32) {
- // CHECK: %[[ID:.*]] = tosa.identity %[[ARG]] : (tensor<2xi64>) -> tensor<2xi64>
- %0 = tosa.identity %arg : (tensor<2xi64>) -> tensor<2xi64>
- // CHECK: %[[C:.*]] = tosa.const values(dense<1.000000e+00> : tensor<2xf32>) : () -> tensor<2xf32>
- %1 = tosa.const values(dense<1.0> : tensor<2xf32>) : () -> tensor<2xf32>
- // CHECK: %[[A:.*]] = arith.constant 48 : i32
- %2 = arith.constant 48 : i32
- // CHECK-NEXT: return %[[ID]], %[[C]], %[[A]] : tensor<2xi64>, tensor<2xf32>, i32
- return %0, %1, %2 : tensor<2xi64>, tensor<2xf32>, i32
-}
-
-// -----
-
// Recurse into nested attributes even when operand and result types are unchanged.
// CHECK-LABEL: func.func @attributes_only
func.func @attributes_only(%arg: tensor<2xi32>) -> tensor<2xi32> {
@@ -165,32 +64,6 @@ func.func @attributes_only(%arg: tensor<2xi32>) -> tensor<2xi32> {
// -----
-// Widen nested values even when the enclosing operation's types are unchanged.
-// CHECK-LABEL: func.func @internal_region(
-// CHECK-SAME: %[[COND:.*]]: tensor<i1>) -> tensor<i1>
-func.func @internal_region(%cond: tensor<i1>) -> tensor<i1> {
- // CHECK: %[[IF:.*]] = tosa.cond_if %[[COND]] : tensor<i1> -> tensor<i1>
- %0 = tosa.cond_if %cond : tensor<i1> -> tensor<i1> {
- // CHECK: %[[C:.*]] = tosa.const values(dense<1> : tensor<i64>) : () -> tensor<i64>
- %c = tosa.const values(dense<1> : tensor<i48>) : () -> tensor<i48>
- // CHECK: %[[D:.*]] = tosa.const values(dense<2> : tensor<i64>) : () -> tensor<i64>
- %d = tosa.const values(dense<2> : tensor<i48>) : () -> tensor<i48>
- // CHECK: %[[SELECT:.*]] = tosa.select %[[COND]], %[[C]], %[[D]] : (tensor<i1>, tensor<i64>, tensor<i64>) -> tensor<i64>
- %select = tosa.select %cond, %c, %d : (tensor<i1>, tensor<i48>, tensor<i48>) -> tensor<i48>
- // CHECK: %[[EQ:.*]] = tosa.equal %[[SELECT]], %[[C]] : (tensor<i64>, tensor<i64>) -> tensor<i1>
- %eq = tosa.equal %select, %c : (tensor<i48>, tensor<i48>) -> tensor<i1>
- // CHECK: tosa.yield %[[EQ]] : tensor<i1>
- tosa.yield %eq : tensor<i1>
- } else {
- // CHECK: tosa.yield %[[COND]] : tensor<i1>
- tosa.yield %cond : tensor<i1>
- }
- // CHECK: return %[[IF]] : tensor<i1>
- return %0 : tensor<i1>
-}
-
-// -----
-
// Mixed types and multiple results; preserve function and call attributes.
// CHECK-LABEL: func.func private @mixed(
// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xui64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>)
@@ -209,19 +82,6 @@ func.func @mixed_caller(%a: tensor<?xi48>, %b: tensor<*xui48>, %c: tensor<2xi32>
// -----
-// Convert module metadata, including constant payloads without SSA users.
-// CHECK-LABEL: module @module_attributes
-// CHECK-SAME: test.constant = dense<[-1, 140737488355327]> : tensor<2xi64>
-// CHECK-SAME: test.type = tensor<2xi64>
-// CHECK-SAME: test.value = -1 : i64
-module @module_attributes attributes {
- test.constant = dense<[-1, 140737488355327]> : tensor<2xi48>,
- test.type = tensor<2xi48>,
- test.value = -1 : i48
-} {}
-
-// -----
-
// Convert function, argument and result metadata along with the signature.
// CHECK-LABEL: func.func @function_attributes(
// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64> {test.type = tensor<2xi64>}) -> (tensor<2xi64> {test.type = tensor<2xi64>}) attributes {test.value = -1 : i64}
@@ -232,26 +92,6 @@ func.func @function_attributes(%arg: tensor<2xi48> {test.type = tensor<2xi48>})
// -----
-// Convert types embedded in tensor encodings while preserving other metadata.
-// CHECK-LABEL: func.func @encoded_tensor(
-// CHECK-SAME: %[[ARG:.*]]: tensor<?x2xi64, {i = i64, j = "k"}>) -> tensor<?x2xi64, {i = i64, j = "k"}>
-func.func @encoded_tensor(%arg: tensor<?x2xi48, {i = i48, j = "k"}>) -> tensor<?x2xi48, {i = i48, j = "k"}> {
- // CHECK: return %[[ARG]] : tensor<?x2xi64, {i = i64, j = "k"}>
- return %arg : tensor<?x2xi48, {i = i48, j = "k"}>
-}
-
-// -----
-
-// Preserve stride metadata on tensor arguments and results while widening.
-// CHECK-LABEL: func.func @strided_tensor(
-// CHECK-SAME: %[[ARG:.*]]: tensor<2x3xi64> {test.strides = strided<[5, 1]>}) -> (tensor<2x3xi64> {test.strides = strided<[5, 1]>})
-func.func @strided_tensor(%arg: tensor<2x3xi48> {test.strides = strided<[5, 1]>}) -> (tensor<2x3xi48> {test.strides = strided<[5, 1]>}) {
- // CHECK: return %[[ARG]] : tensor<2x3xi64>
- return %arg : tensor<2x3xi48>
-}
-
-// -----
-
// Preserve a memref's non-default strides and offset while widening its elements.
// CHECK-LABEL: func.func @strided_memref(
// CHECK-SAME: %[[ARG:.*]]: memref<2x3xi64, strided<[5, 1], offset: 2>>) -> memref<2x3xi64, strided<[5, 1], offset: 2>>
@@ -262,22 +102,6 @@ func.func @strided_memref(%arg: memref<2x3xi48, strided<[5, 1], offset: 2>>) ->
// -----
-// Convert a function reference, its indirect call and a function-valued result.
-// CHECK: func.func private @referenced(tensor<2xi64>) -> tensor<2xi64>
-func.func private @referenced(tensor<2xi48>) -> tensor<2xi48>
-// CHECK-LABEL: func.func @function_reference(
-// CHECK-SAME: %[[ARG:.*]]: tensor<2xi64>) -> (tensor<2xi64>, (tensor<2xi64>) -> tensor<2xi64>)
-func.func @function_reference(%arg: tensor<2xi48>) -> (tensor<2xi48>, (tensor<2xi48>) -> tensor<2xi48>) {
- // CHECK: %[[FN:.*]] = constant @referenced : (tensor<2xi64>) -> tensor<2xi64>
- %fn = func.constant @referenced : (tensor<2xi48>) -> tensor<2xi48>
- // CHECK: %[[RESULT:.*]] = call_indirect %[[FN]](%[[ARG]]) : (tensor<2xi64>) -> tensor<2xi64>
- %result = func.call_indirect %fn(%arg) : (tensor<2xi48>) -> tensor<2xi48>
- // CHECK: return %[[RESULT]], %[[FN]] : tensor<2xi64>, (tensor<2xi64>) -> tensor<2xi64>
- return %result, %fn : tensor<2xi48>, (tensor<2xi48>) -> tensor<2xi48>
-}
-
-// -----
-
// Widen an arithmetic producer and its TOSA user inside a non-TOSA container.
// CHECK-LABEL: func.func @nested_tosa()
func.func @nested_tosa() {
@@ -293,6 +117,7 @@ func.func @nested_tosa() {
// CHECK: return
return
}
+
// -----
// Only the selected i4 and i48 widths change. Arbitrary widths such as i3
@@ -334,17 +159,6 @@ func.func @attributes(%arg: tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i
// -----
-// Widen sparse i4 values while preserving their coordinates.
-// CHECK-LABEL: func.func @subbyte_sparse_constant()
-// CHECK-SAME: -> tensor<4xi8>
-func.func @subbyte_sparse_constant() -> tensor<4xi4> {
- // CHECK: tosa.const values(sparse<{{\[\[}}1], [3]], [-7, 7]> : tensor<4xi8>)
- %0 = tosa.const values(sparse<[[1], [3]], [-7, 7]> : tensor<4xi4>) : () -> tensor<4xi4>
- return %0 : tensor<4xi4>
-}
-
-// -----
-
// i4 resources use one byte per element. Preserve the original blob for an i8
// view, share the widened resource, and retain storage for repeated values.
// CHECK: module attributes {test.bytes = dense_resource<signed_data> : tensor<5xi8>, test.original = dense_resource<[[SIGNED:[a-zA-Z0-9_]+]]> : tensor<5xi8>}
@@ -381,19 +195,6 @@ module attributes {
// -----
-// CHECK-LABEL: func.func @subbyte_dynamic_resource()
-// CHECK-SAME: -> tensor<?xi8>
-func.func @subbyte_dynamic_resource() -> tensor<?xi4> {
- // CHECK: tosa.const values(dense_resource<[[DYNAMIC:[a-zA-Z0-9_]+]]> : tensor<?xi8>)
- %0 = tosa.const values(dense_resource<dynamic> : tensor<?xi4>) : () -> tensor<?xi4>
- return %0 : tensor<?xi4>
-}
-{-# dialect_resources: { builtin: {
- dynamic: "0x01000000090F"
- // CHECK: [[DYNAMIC]]: "0x01000000F9FF"
-} } #-}
-// -----
-
// Preserve sparse indices and implicit zeros while widening stored values.
// CHECK-LABEL: func.func @sparse_constant()
// CHECK-SAME: -> tensor<4xi64>
@@ -475,64 +276,6 @@ func.func @encoded_constant() -> tensor<2xi48, {i = i48, j = "k"}> {
// -----
-// Convert dense constant encodings even when the element type is unchanged.
-// CHECK-LABEL: func.func @encoded_constant_encoding_only()
-// CHECK-SAME: -> tensor<2xi32, {i = i64, j = "k"}>
-func.func @encoded_constant_encoding_only() -> tensor<2xi32, {i = i48, j = "k"}> {
- // CHECK: %[[C:.*]] = tosa.const values(dense<[-1, 7]> : tensor<2xi32, {i = i64, j = "k"}>) : () -> tensor<2xi32, {i = i64, j = "k"}>
- %0 = tosa.const values(dense<[-1, 7]> : tensor<2xi32, {i = i48, j = "k"}>) : () -> tensor<2xi32, {i = i48, j = "k"}>
- // CHECK-NEXT: return %[[C]] : tensor<2xi32, {i = i64, j = "k"}>
- return %0 : tensor<2xi32, {i = i48, j = "k"}>
-}
-
-// -----
-
-// Preserve layout metadata and element order while widening a dense constant.
-// CHECK-LABEL: func.func @strided_constant()
-// CHECK-SAME: -> tensor<2x2xi64>
-func.func @strided_constant() -> tensor<2x2xi48> {
- // CHECK: %[[C:.*]] = tosa.const values(dense<{{\[}}[-140737488355328, -1], [0, 140737488355327]]> : tensor<2x2xi64>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi64>
- %0 = tosa.const values(dense<[[-140737488355328, -1], [0, 140737488355327]]> : tensor<2x2xi48>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi48>
- // CHECK-NEXT: return %[[C]] : tensor<2x2xi64>
- return %0 : tensor<2x2xi48>
-}
-
-// -----
-
-// Preserve layout metadata and element order while widening a resource payload.
-// CHECK-LABEL: func.func @strided_resource_constant()
-// CHECK-SAME: -> tensor<2x2xi64>
-func.func @strided_resource_constant() -> tensor<2x2xi48> {
- // CHECK: %[[C:.*]] = tosa.const values(dense_resource<[[STRIDED:[a-zA-Z0-9_]+]]> : tensor<2x2xi64>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi64>
- %0 = tosa.const values(dense_resource<strided> : tensor<2x2xi48>) {test.layout = strided<[1, 2]>} : () -> tensor<2x2xi48>
- // CHECK-NEXT: return %[[C]] : tensor<2x2xi64>
- return %0 : tensor<2x2xi48>
-}
-
-{-# dialect_resources: { builtin: {
- // Signed i48 minimum, -1, zero and maximum, sign-extended to i64 in order.
- // CHECK: [[STRIDED]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFF7F0000"
- strided: "0x01000000000000000080FFFFFFFFFFFF000000000000FFFFFFFFFF7F"
-} } #-}
-
-// -----
-
-// Repeated values must retain storage for every element in the resource.
-// CHECK-LABEL: func.func @repeated_resource()
-func.func @repeated_resource() -> tensor<2x2xi48> {
- // CHECK: tosa.const values(dense_resource<[[REPEATED:[a-zA-Z0-9_]+]]> : tensor<2x2xi64>)
- %0 = tosa.const values(dense_resource<repeated> : tensor<2x2xi48>) : () -> tensor<2x2xi48>
- return %0 : tensor<2x2xi48>
-}
-
-{-# dialect_resources: { builtin: {
- // Four copies of -1, each occupying six bytes in the input and eight in the output.
- // CHECK: [[REPEATED]]: "0x08000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- repeated: "0x01000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
-} } #-}
-
-// -----
-
// Widen each complete element in the resource without resolving its shape.
// CHECK-LABEL: func.func @dynamic_resource()
// CHECK-SAME: -> tensor<?xi64>
@@ -554,28 +297,6 @@ func.func @dynamic_resource() -> tensor<?xi48> {
// -----
-// Unranked tensors are valid in the dialect before shape inference. Widening
-// the resource's elements should preserve its unranked shape.
-// CHECK-LABEL: func.func @unranked_resource()
-// CHECK-SAME: -> tensor<*xi64>
-func.func @unranked_resource() -> tensor<*xi48> {
- // CHECK: %[[C:.*]] = tosa.const values(dense_resource<[[UNRANKED:[a-zA-Z0-9_]+]]> : tensor<*xi64>) : () -> tensor<*xi64>
- %0 = tosa.const values(dense_resource<unranked> : tensor<*xi48>) : () -> tensor<*xi48>
- // CHECK-NEXT: return %[[C]] : tensor<*xi64>
- return %0 : tensor<*xi48>
-}
-{-#
- dialect_resources: {
- builtin: {
- // Alignment 1 followed by one little-endian i48 value of -1.
- // CHECK: [[UNRANKED]]: "0x08000000FFFFFFFFFFFFFFFF"
- unranked: "0x01000000FFFFFFFFFFFF"
- }
- }
-#-}
-
-// -----
-
// Check that dense constants and resource payloads remain correct after
// one or two runs of the pass.
// The module attribute and both functions must share the widened resource.
@@ -622,6 +343,7 @@ module attributes {test.bytes = dense_resource<signed_data> : tensor<30xi8>, tes
}
}
#-}
+
// -----
// Unselected widths remain unchanged in payloads, including unresolved resources.
>From 96186c0406f0d61a5e2d26aaf2e0a01c6e3c6c9e Mon Sep 17 00:00:00 2001
From: Michael Platings <michael.platings at arm.com>
Date: Fri, 23 Oct 2026 13:34:01 +0100
Subject: [PATCH 3/4] fixup! [mlir][tosa] Add integer type conversion pass
---
.../mlir/Dialect/Tosa/Transforms/Passes.h | 2 --
.../mlir/Dialect/Tosa/Transforms/Passes.td | 14 ++++----
.../Dialect/Tosa/Transforms/CMakeLists.txt | 1 -
.../Transforms/TosaConvertIntegerTypes.cpp | 4 +--
...nvert-integer-types-bitwidth-invalid.mlir} | 4 +--
...vert-integer-types-bitwidth-mappings.mlir} | 4 +--
...onvert-integer-types-bitwidth-options.mlir | 33 +++++++++++++++++++
... tosa-convert-integer-types-bitwidth.mlir} | 4 +--
.../tosa-convert-integer-types-options.mlir | 33 -------------------
9 files changed, 47 insertions(+), 52 deletions(-)
rename mlir/test/Dialect/Tosa/{tosa-convert-integer-types-invalid.mlir => tosa-convert-integer-types-bitwidth-invalid.mlir} (93%)
rename mlir/test/Dialect/Tosa/{tosa-convert-integer-types-mappings.mlir => tosa-convert-integer-types-bitwidth-mappings.mlir} (94%)
create mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir
rename mlir/test/Dialect/Tosa/{tosa-convert-integer-types.mlir => tosa-convert-integer-types-bitwidth.mlir} (97%)
delete mode 100644 mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
index c75e0e41ea4255..e33d7c698856ce 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.h
@@ -17,8 +17,6 @@
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
#include "mlir/Pass/Pass.h"
-#include <utility>
-
namespace mlir {
class TypeConverter;
namespace tosa {
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 0f8250fb38c252..54d23177b4b3a3 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -217,21 +217,21 @@ def TosaGatherScatterHardeningPass
}];
}
-def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types", "ModuleOp"> {
+def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types-bitwidth", "ModuleOp"> {
let summary = "Convert selected TOSA integer types";
let description = [{
Converts integer types using `bitwidth-mappings`, a required list of
`<source>:<destination>` bitwidth pairs.
A typical use case is `bitwidth-mappings=4:8,48:64` which maps i4 and i48 to
- i8 and i64 respectively.
+ i8 and i64 respectively. This facilitates lowering to dialects that do not
+ support such non-standard bitwidths.
Source bitwidths 1, 32 and 64 are not supported because some attributes
require these fixed widths.
Mappings apply once to the original width, preserving signedness and
- leaving unmapped widths unchanged. Widening sign-extends signed and
- signless constants and zero-extends unsigned constants. Narrowing truncates
- without checking for data loss. The caller is responsible for ensuring the
- conversion is appropriate.
+ leaving unmapped widths unchanged. Widening expects signless constants and
+ treats them as signed. Narrowing truncates without checking for data loss.
+ The caller is responsible for ensuring the conversion is appropriate.
Conversion covers types, function signatures and attribute payloads
throughout the module, including non-TOSA operations.
@@ -247,8 +247,6 @@ def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types", "ModuleOp">
"Integer bitwidth mappings <source>:<destination> "
"(required, non-empty)">
];
-
- let dependentDialects = ["func::FuncDialect", "tosa::TosaDialect"];
}
def TosaNarrowI64ToI32Pass : Pass<"tosa-narrow-i64-to-i32", "func::FuncOp"> {
diff --git a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
index 9998f963ddbabb..96875e9005352c 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
@@ -29,7 +29,6 @@ add_mlir_dialect_library(MLIRTosaTransforms
MLIRFuncDialect
MLIRFuncTransformOps
MLIRPass
- MLIRTensorDialect
MLIRTosaDialect
MLIRTransformUtils
MLIRFuncTransforms
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
index 698417e60d30b6..39a336821e2316 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
@@ -58,14 +58,14 @@ namespace mlir::tosa {
using namespace mlir;
namespace {
-static APInt convertValue(const APInt &value, Type type, unsigned width) {
+APInt convertValue(const APInt &value, Type type, unsigned width) {
return type.isUnsignedInteger() ? value.zextOrTrunc(width)
: value.sextOrTrunc(width);
}
// Convert a validated byte-aligned payload, retaining storage for every
// element. In particular, each i4 element occupies one byte, not half a byte.
-static SmallVector<char> convertRawValues(ArrayRef<char> rawData,
+SmallVector<char> convertRawValues(ArrayRef<char> rawData,
IntegerType type,
IntegerType convertedType) {
const unsigned inputBytes = llvm::divideCeil(type.getWidth(), 8);
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
similarity index 93%
rename from mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
rename to mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
index 50a78a76a8f788..401de5de293e33 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-invalid.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
@@ -1,5 +1,5 @@
-// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64,8:4' %s
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=16:16' %s | FileCheck %s
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64,8:4' %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=16:16' %s | FileCheck %s
// Unselected widths must leave even missing or malformed resources untouched.
// CHECK-LABEL: func.func @missing_resource()
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
similarity index 94%
rename from mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
rename to mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
index 4a4ad6f49f1e6e..666334e36372a7 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-mappings.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
@@ -1,6 +1,6 @@
// REQUIRES: host-byteorder-little-endian
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=3:4,4:8,8:4,13:16,16:1,24:32,40:24,48:64,56:48,65:128,128:65' -mlir-print-op-generic %s | FileCheck %s
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=128:65,65:128,56:48,48:64,40:24,24:32,16:1,13:16,8:4,4:8,3:4' -mlir-print-op-generic %s | FileCheck %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=3:4,4:8,8:4,13:16,16:1,24:32,40:24,48:64,56:48,65:128,128:65' -mlir-print-op-generic %s | FileCheck %s
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=128:65,65:128,56:48,48:64,40:24,24:32,16:1,13:16,8:4,4:8,3:4' -mlir-print-op-generic %s | FileCheck %s
// Each mapping applies once to the original width, including cycles and
// destinations that are also sources. Reversing the list must have no effect.
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir
new file mode 100644
index 00000000000000..9b5a24ff641517
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir
@@ -0,0 +1,33 @@
+// RUN: mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=48:64,4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
+// RUN: mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8' %s | FileCheck %s '-D$I4=8' '-D$I48=48'
+// RUN: mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=48:64' %s | FileCheck %s '-D$I4=4' '-D$I48=64'
+//
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth %s 2>&1 | FileCheck %s --check-prefix=MISSING
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=' %s 2>&1 | FileCheck %s --check-prefix=MISSING
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=0:8,4:8' %s 2>&1 | FileCheck %s --check-prefix=ZERO
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=16777216:16777216' %s 2>&1 | FileCheck %s --check-prefix=TOO-LARGE
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=8388609:16777216' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:0' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION-ZERO
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,4:16' %s 2>&1 | FileCheck %s --check-prefix=CONFLICT
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8:16' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
+
+// Check selected and unselected widths in both types and constant values.
+// CHECK-LABEL: module attributes {
+// CHECK-SAME: test.i4 = dense<[-7, 7]> : tensor<2xi[[$I4]]>
+// CHECK-SAME: test.i48 = dense<[-1, 140737488355327]> : tensor<2xi[[$I48]]>
+// CHECK-SAME: test.types = [i[[$I4]], i[[$I48]], i1, i32, i64]
+module attributes {
+ test.i4 = dense<[-7, 7]> : tensor<2xi4>,
+ test.i48 = dense<[-1, 140737488355327]> : tensor<2xi48>,
+ test.types = [i4, i48, i1, i32, i64]
+} {}
+
+// MISSING: error: The 'bitwidth-mappings' option must be specified and non-empty.
+// ZERO: error: invalid source integer bitwidth 0: expected a value between 1 and 16777215
+// TOO-LARGE: error: invalid source integer bitwidth 16777216: expected a value between 1 and 16777215
+// DESTINATION: error: invalid destination integer bitwidth 16777216: expected a value between 1 and 16777215
+// DESTINATION-ZERO: error: invalid destination integer bitwidth 0: expected a value between 1 and 16777215
+// CONFLICT: error: conflicting mappings for source integer bitwidth 4: 8 and 16
+// SYNTAX: expected <source>:<destination> integer bitwidths
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
similarity index 97%
rename from mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
rename to mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
index 8838428de3f94a..66802ecacc90d1 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
@@ -1,6 +1,6 @@
// REQUIRES: host-byteorder-little-endian
-// RUN: mlir-opt -split-input-file -pass-pipeline='builtin.module(tosa-convert-integer-types{bitwidth-mappings=4:8,48:64})' %s | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
-// RUN: mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' %s | mlir-opt -split-input-file -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
+// RUN: mlir-opt -split-input-file -pass-pipeline='builtin.module(tosa-convert-integer-types-bitwidth{bitwidth-mappings=4:8,48:64})' %s | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64' %s | mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64' | FileCheck %s --implicit-check-not=i4 --implicit-check-not=unrealized_conversion_cast --implicit-check-not=tosa.cast
// CHECK-LABEL: func.func @clamp
func.func @clamp(%arg: tensor<4xi48>) -> tensor<4xi48> {
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
deleted file mode 100644
index e6dcae5e87c1c1..00000000000000
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-options.mlir
+++ /dev/null
@@ -1,33 +0,0 @@
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64,4:8,48:64' %s | FileCheck %s '-D$I4=8' '-D$I48=64'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8' %s | FileCheck %s '-D$I4=8' '-D$I48=48'
-// RUN: mlir-opt -tosa-convert-integer-types='bitwidth-mappings=48:64' %s | FileCheck %s '-D$I4=4' '-D$I48=64'
-//
-// RUN: not mlir-opt -tosa-convert-integer-types %s 2>&1 | FileCheck %s --check-prefix=MISSING
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=' %s 2>&1 | FileCheck %s --check-prefix=MISSING
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=0:8,4:8' %s 2>&1 | FileCheck %s --check-prefix=ZERO
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=16777216:16777216' %s 2>&1 | FileCheck %s --check-prefix=TOO-LARGE
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=8388609:16777216' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:0' %s 2>&1 | FileCheck %s --check-prefix=DESTINATION-ZERO
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8,4:16' %s 2>&1 | FileCheck %s --check-prefix=CONFLICT
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-// RUN: not mlir-opt -tosa-convert-integer-types='bitwidth-mappings=4:8:16' %s 2>&1 | FileCheck %s --check-prefix=SYNTAX
-
-// Check selected and unselected widths in both types and constant values.
-// CHECK-LABEL: module attributes {
-// CHECK-SAME: test.i4 = dense<[-7, 7]> : tensor<2xi[[$I4]]>
-// CHECK-SAME: test.i48 = dense<[-1, 140737488355327]> : tensor<2xi[[$I48]]>
-// CHECK-SAME: test.types = [i[[$I4]], i[[$I48]], i1, i32, i64]
-module attributes {
- test.i4 = dense<[-7, 7]> : tensor<2xi4>,
- test.i48 = dense<[-1, 140737488355327]> : tensor<2xi48>,
- test.types = [i4, i48, i1, i32, i64]
-} {}
-
-// MISSING: error: The 'bitwidth-mappings' option must be specified and non-empty.
-// ZERO: error: invalid source integer bitwidth 0: expected a value between 1 and 16777215
-// TOO-LARGE: error: invalid source integer bitwidth 16777216: expected a value between 1 and 16777215
-// DESTINATION: error: invalid destination integer bitwidth 16777216: expected a value between 1 and 16777215
-// DESTINATION-ZERO: error: invalid destination integer bitwidth 0: expected a value between 1 and 16777215
-// CONFLICT: error: conflicting mappings for source integer bitwidth 4: 8 and 16
-// SYNTAX: expected <source>:<destination> integer bitwidths
>From f26f9ce19be2fb786fd1c5ea2fb69c66bea249d6 Mon Sep 17 00:00:00 2001
From: Michael Platings <michael.platings at arm.com>
Date: Fri, 23 Oct 2026 13:48:25 +0100
Subject: [PATCH 4/4] fixup! [mlir][tosa] Add integer type conversion pass
---
.../mlir/Dialect/Tosa/Transforms/Passes.td | 4 +-
.../Dialect/Tosa/Transforms/CMakeLists.txt | 2 +-
...pp => TosaConvertIntegerTypesBitwidth.cpp} | 14 +-
...nvert-integer-types-bitwidth-mappings.mlir | 57 ++++---
.../tosa-convert-integer-types-bitwidth.mlir | 140 +++++++-----------
5 files changed, 90 insertions(+), 127 deletions(-)
rename mlir/lib/Dialect/Tosa/Transforms/{TosaConvertIntegerTypes.cpp => TosaConvertIntegerTypesBitwidth.cpp} (95%)
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 54d23177b4b3a3..2c8788bc0bb85c 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -217,8 +217,8 @@ def TosaGatherScatterHardeningPass
}];
}
-def TosaConvertIntegerTypesPass : Pass<"tosa-convert-integer-types-bitwidth", "ModuleOp"> {
- let summary = "Convert selected TOSA integer types";
+def TosaConvertIntegerTypesBitwidthPass : Pass<"tosa-convert-integer-types-bitwidth", "ModuleOp"> {
+ let summary = "Convert selected TOSA integer types bitwidth";
let description = [{
Converts integer types using `bitwidth-mappings`, a required list of
`<source>:<destination>` bitwidth pairs.
diff --git a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
index 96875e9005352c..896985154076f1 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
@@ -16,7 +16,7 @@ add_mlir_dialect_library(MLIRTosaTransforms
TosaProfileCompliance.cpp
TosaValidation.cpp
TosaNarrowTypes.cpp
- TosaConvertIntegerTypes.cpp
+ TosaConvertIntegerTypesBitwidth.cpp
TosaInputShape.cpp
ADDITIONAL_HEADER_DIRS
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
similarity index 95%
rename from mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
rename to mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
index 39a336821e2316..92c750edfc403d 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
@@ -1,4 +1,4 @@
-//===- TosaConvertIntegerTypes.cpp - Convert TOSA integers ----------------===//
+//===- TosaConvertIntegerTypesBitwidth.cpp - Convert TOSA integers --------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -51,7 +51,7 @@ class parser<std::pair<unsigned, unsigned>>
} // namespace llvm::cl
namespace mlir::tosa {
-#define GEN_PASS_DEF_TOSACONVERTINTEGERTYPESPASS
+#define GEN_PASS_DEF_TOSACONVERTINTEGERTYPESBITWIDTHPASS
#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
} // namespace mlir::tosa
@@ -59,8 +59,7 @@ using namespace mlir;
namespace {
APInt convertValue(const APInt &value, Type type, unsigned width) {
- return type.isUnsignedInteger() ? value.zextOrTrunc(width)
- : value.sextOrTrunc(width);
+ return value.sextOrTrunc(width);
}
// Convert a validated byte-aligned payload, retaining storage for every
@@ -93,8 +92,7 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
auto mapping = mappings.find(type.getWidth());
if (mapping == mappings.end())
return std::nullopt;
- return IntegerType::get(type.getContext(), mapping->second,
- type.getSignedness());
+ return IntegerType::get(type.getContext(), mapping->second);
});
addReplacement(
[this](IntegerAttr attr) { return skip(convertInteger(attr)); });
@@ -206,8 +204,8 @@ class IntegerTypeReplacer : public AttrTypeReplacer {
bool conversionFailed = false;
};
-struct TosaConvertIntegerTypes
- : tosa::impl::TosaConvertIntegerTypesPassBase<TosaConvertIntegerTypes> {
+struct TosaConvertIntegerTypesBitwidth
+ : tosa::impl::TosaConvertIntegerTypesBitwidthPassBase<TosaConvertIntegerTypesBitwidth> {
using Base::Base;
void runOnOperation() override {
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
index 666334e36372a7..6d9ea00720e9f1 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
@@ -4,33 +4,33 @@
// Each mapping applies once to the original width, including cycles and
// destinations that are also sources. Reversing the list must have no effect.
-// CHECK: "func.func"() <{function_type = (i4, si4, ui4, i8, i4, i16, i1, i32, i24, i64, i48, i128, i65) -> (), sym_name = "types", sym_visibility = "private"}>
-func.func private @types(i3, si3, ui3, i4, i8, i13, i16, i24, i40, i48, i56, i65, i128)
+// CHECK: "func.func"() <{function_type = (i4, i8, i4, i16, i1, i32, i24, i64, i48, i128, i65) -> (), sym_name = "types", sym_visibility = "private"}>
+func.func private @types(i3, i4, i8, i13, i16, i24, i40, i48, i56, i65, i128)
// -----
// Narrow scalar, dense, sparse and array payloads, including i1.
// CHECK-LABEL: "builtin.module"() <{sym_name = "narrow_constants"}>
-// CHECK: test.a_scalar = -7 : si4
-// CHECK-SAME: test.b_unsigned = 15 : ui4
+// CHECK: test.a_scalar = -7 : i4
+// CHECK-SAME: test.b_all_ones = -1 : i4
// CHECK-SAME: test.c_array = array<i24: -8388608, 8388607, -1, 0>
-// CHECK-SAME: test.d_splat = dense<-7> : tensor<3xsi4>
-// CHECK-SAME: test.e_unsigned = dense<[8, 7, 15, 0]> : tensor<4xui4>
+// CHECK-SAME: test.d_splat = dense<-7> : tensor<3xi4>
+// CHECK-SAME: test.e_high_bits = dense<[-8, 7, -1, 0]> : tensor<4xi4>
// CHECK-SAME: test.f_sparse = sparse<{{\[\[}}1], [3]], [-1, 0]> : tensor<4xi48>
// CHECK-SAME: test.g_i1_array = array<i1: true, false, true>
// CHECK-SAME: test.h_i1_dense = dense<[true, false, true]> : tensor<3xi1>
-// CHECK-SAME: test.i_types = [i24, si48, ui65]
+// CHECK-SAME: test.i_types = [i24, i48, i65]
// CHECK-SAME: test.j_wide_sparse = sparse<{{\[\[}}1], [3]], [-1, 18446744073709551615]> : tensor<4xi65>
module @narrow_constants attributes {
- test.a_scalar = 25 : si8,
- test.b_unsigned = 255 : ui8,
+ test.a_scalar = 25 : i8,
+ test.b_all_ones = 255 : i8,
test.c_array = array<i40: 8388608, 25165823, -1, -16777216>,
- test.d_splat = dense<25> : tensor<3xsi8>,
- test.e_unsigned = dense<[8, 23, 255, 16]> : tensor<4xui8>,
+ test.d_splat = dense<25> : tensor<3xi8>,
+ test.e_high_bits = dense<[8, 23, 255, 16]> : tensor<4xi8>,
test.f_sparse = sparse<[[1], [3]], [-1, 281474976710656]> : tensor<4xi56>,
test.g_i1_array = array<i16: -1, 2, 3>,
test.h_i1_dense = dense<[-1, 2, 3]> : tensor<3xi16>,
- test.i_types = [i40, si56, ui128],
+ test.i_types = [i40, i56, i128],
test.j_wide_sparse = sparse<[[1], [3]], [-1, 18446744073709551615]> : tensor<4xi128>
} {}
@@ -39,26 +39,26 @@ module @narrow_constants attributes {
// Widen sub-byte, non-power-of-two and greater-than-64-bit payloads.
// CHECK-LABEL: "builtin.module"() <{sym_name = "widen_constants"}>
// CHECK: test.array = array<i32: -8388608, 8388607>
-// CHECK-SAME: test.array_unsigned = array<ui32: 16777215>
+// CHECK-SAME: test.array_ones = array<i32: -1>
// CHECK-SAME: test.dense = dense<[-4, -1, 0, 3]> : tensor<4xi4>
-// CHECK-SAME: test.mid_signed = dense<[-4096, 4095]> : tensor<2xi16>
-// CHECK-SAME: test.mid_unsigned = 8191 : ui16
-// CHECK-SAME: test.scalar = -4 : si4
+// CHECK-SAME: test.mid_bounds = dense<[-4096, 4095]> : tensor<2xi16>
+// CHECK-SAME: test.mid_ones = -1 : i16
+// CHECK-SAME: test.scalar = -4 : i4
// CHECK-SAME: test.sparse = sparse<{{\[\[}}1], [3]], [-2, 1]> : tensor<4xi4>
-// CHECK-SAME: test.unsigned = 7 : ui4
+// CHECK-SAME: test.top_bits = -1 : i4
// CHECK-SAME: test.wide = dense<[-1, 18446744073709551615]> : tensor<2xi128>
-// CHECK-SAME: test.wide_unsigned = 36893488147419103231 : ui128
+// CHECK-SAME: test.wide_ones = -1 : i128
module @widen_constants attributes {
test.array = array<i24: -8388608, 8388607>,
- test.array_unsigned = array<ui24: 16777215>,
+ test.array_ones = array<i24: 16777215>,
test.dense = dense<[-4, -1, 0, 3]> : tensor<4xi3>,
- test.mid_signed = dense<[-4096, 4095]> : tensor<2xi13>,
- test.mid_unsigned = 8191 : ui13,
- test.scalar = -4 : si3,
+ test.mid_bounds = dense<[-4096, 4095]> : tensor<2xi13>,
+ test.mid_ones = 8191 : i13,
+ test.scalar = -4 : i3,
test.sparse = sparse<[[1], [3]], [-2, 1]> : tensor<4xi3>,
- test.unsigned = 7 : ui3,
+ test.top_bits = 7 : i3,
test.wide = dense<[-1, 18446744073709551615]> : tensor<2xi65>,
- test.wide_unsigned = 36893488147419103231 : ui65
+ test.wide_ones = 36893488147419103231 : i65
} {}
// -----
@@ -109,13 +109,11 @@ module @narrow_resources attributes {
// CHECK-SAME: test.b_i16 = dense_resource<[[I16:[a-zA-Z0-9_]+]]> : tensor<2xi16>
// CHECK-SAME: test.c_i32 = dense_resource<[[I32:[a-zA-Z0-9_]+]]> : tensor<2xi32>
// CHECK-SAME: test.d_i128 = dense_resource<[[I128:[a-zA-Z0-9_]+]]> : tensor<2xi128>
-// CHECK-SAME: test.e_unsigned = dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui4>
module @widen_resources attributes {
test.a_subbyte = dense_resource<subbyte> : tensor<4xi3, {test.widths = [i3, i4, i8]}>,
test.b_i16 = dense_resource<to16> : tensor<2xi13>,
test.c_i32 = dense_resource<to32> : tensor<2xi24>,
- test.d_i128 = dense_resource<to128> : tensor<2xi65>,
- test.e_unsigned = dense_resource<unsigned_data> : tensor<3xui3>
+ test.d_i128 = dense_resource<to128> : tensor<2xi65>
} {}
{-# dialect_resources: { builtin: {
@@ -126,12 +124,9 @@ module @widen_resources attributes {
// [-8388608, 8388607], with three bytes per i24 value.
to32: "0x01000000000080FFFF7F",
// [-1, 2^64 - 1], with nine bytes per i65 value.
- to128: "0x01000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00",
- // [0, 4, 7].
- unsigned_data: "0x01000000000407"
+ to128: "0x01000000FFFFFFFFFFFFFFFF01FFFFFFFFFFFFFFFF00"
// CHECK-DAG: [[SUBBYTE]]: "0x010000000C0F0003"
// CHECK-DAG: [[I16]]: "0x0200000000F0FF0F"
// CHECK-DAG: [[I32]]: "0x04000000000080FFFFFF7F00"
// CHECK-DAG: [[I128]]: "0x10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000"
- // CHECK-DAG: [[UNSIGNED]]: "0x01000000000407"
} } #-}
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
index 66802ecacc90d1..b9131b1c24d3d5 100644
--- a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
@@ -57,8 +57,8 @@ func.func @conv_rescale(%input: tensor<1x3x3x1xi16>, %weight: tensor<1x1x1x1xi8>
// Recurse into nested attributes even when operand and result types are unchanged.
// CHECK-LABEL: func.func @attributes_only
func.func @attributes_only(%arg: tensor<2xi32>) -> tensor<2xi32> {
- // CHECK: tosa.identity %arg0 {test.nested = [{signed = -140737488355328 : si64, type = tensor<2xi64>, unsigned = 281474976710655 : ui64}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
- %0 = tosa.identity %arg {test.nested = [{signed = -140737488355328 : si48, type = tensor<2xi48>, unsigned = 281474976710655 : ui48}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
+ // CHECK: tosa.identity %arg0 {test.nested = [{min = -140737488355328 : i64, top_bits = -1 : i64, type = tensor<2xi64>}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
+ %0 = tosa.identity %arg {test.nested = [{min = -140737488355328 : i48, top_bits = -1 : i48, type = tensor<2xi48>}], test.unchanged = 48 : i32} : (tensor<2xi32>) -> tensor<2xi32>
return %0 : tensor<2xi32>
}
@@ -66,18 +66,18 @@ func.func @attributes_only(%arg: tensor<2xi32>) -> tensor<2xi32> {
// Mixed types and multiple results; preserve function and call attributes.
// CHECK-LABEL: func.func private @mixed(
-// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xui64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>)
-func.func private @mixed(%a: tensor<?xi48>, %b: tensor<*xui48>, %c: tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>) {
- // CHECK: return %[[B]], %[[A]], %[[C]] : tensor<*xui64>, tensor<?xi64>, tensor<2xi32>
- return %b, %a, %c : tensor<*xui48>, tensor<?xi48>, tensor<2xi32>
+// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xi64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xi64>, tensor<?xi64>, tensor<2xi32>)
+func.func private @mixed(%a: tensor<?xi48>, %b: tensor<*xi48>, %c: tensor<2xi32>) -> (tensor<*xi48>, tensor<?xi48>, tensor<2xi32>) {
+ // CHECK: return %[[B]], %[[A]], %[[C]] : tensor<*xi64>, tensor<?xi64>, tensor<2xi32>
+ return %b, %a, %c : tensor<*xi48>, tensor<?xi48>, tensor<2xi32>
}
// CHECK-LABEL: func.func @mixed_caller(
-// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xui64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>) attributes {test.marker = "function"}
-func.func @mixed_caller(%a: tensor<?xi48>, %b: tensor<*xui48>, %c: tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>) attributes {test.marker = "function"} {
- // CHECK: %[[CALL:.*]]:3 = call @mixed(%[[A]], %[[B]], %[[C]]) {test.marker = "call"} : (tensor<?xi64>, tensor<*xui64>, tensor<2xi32>) -> (tensor<*xui64>, tensor<?xi64>, tensor<2xi32>)
- %0:3 = func.call @mixed(%a, %b, %c) {test.marker = "call"} : (tensor<?xi48>, tensor<*xui48>, tensor<2xi32>) -> (tensor<*xui48>, tensor<?xi48>, tensor<2xi32>)
- // CHECK: return %[[CALL]]#0, %[[CALL]]#1, %[[CALL]]#2 : tensor<*xui64>, tensor<?xi64>, tensor<2xi32>
- return %0#0, %0#1, %0#2 : tensor<*xui48>, tensor<?xi48>, tensor<2xi32>
+// CHECK-SAME: %[[A:.*]]: tensor<?xi64>, %[[B:.*]]: tensor<*xi64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xi64>, tensor<?xi64>, tensor<2xi32>) attributes {test.marker = "function"}
+func.func @mixed_caller(%a: tensor<?xi48>, %b: tensor<*xi48>, %c: tensor<2xi32>) -> (tensor<*xi48>, tensor<?xi48>, tensor<2xi32>) attributes {test.marker = "function"} {
+ // CHECK: %[[CALL:.*]]:3 = call @mixed(%[[A]], %[[B]], %[[C]]) {test.marker = "call"} : (tensor<?xi64>, tensor<*xi64>, tensor<2xi32>) -> (tensor<*xi64>, tensor<?xi64>, tensor<2xi32>)
+ %0:3 = func.call @mixed(%a, %b, %c) {test.marker = "call"} : (tensor<?xi48>, tensor<*xi48>, tensor<2xi32>) -> (tensor<*xi48>, tensor<?xi48>, tensor<2xi32>)
+ // CHECK: return %[[CALL]]#0, %[[CALL]]#1, %[[CALL]]#2 : tensor<*xi64>, tensor<?xi64>, tensor<2xi32>
+ return %0#0, %0#1, %0#2 : tensor<*xi48>, tensor<?xi48>, tensor<2xi32>
}
// -----
@@ -122,37 +122,35 @@ func.func @nested_tosa() {
// Only the selected i4 and i48 widths change. Arbitrary widths such as i3
// remain unchanged.
-// CHECK: func.func private @types(i1, i3, i8, i7, i8, i9, i16, i24, i32, i64, i64, i128) -> (si3, ui3)
-func.func private @types(i1, i3, i4, i7, i8, i9, i16, i24, i32, i48, i64, i128) -> (si3, ui3)
+// CHECK: func.func private @types(i1, i3, i8, i7, i8, i9, i16, i24, i32, i64, i64, i128) -> (i3, i7)
+func.func private @types(i1, i3, i4, i7, i8, i9, i16, i24, i32, i48, i64, i128) -> (i3, i7)
// -----
// Constant payloads and resource storage.
// CHECK-LABEL: func.func @subbyte_constants()
-// CHECK-SAME: -> (tensor<5xi8>, tensor<2x3xi8>, tensor<3xui8>)
-func.func @subbyte_constants() -> (tensor<5xi4>, tensor<2x3xi4>, tensor<3xui4>) {
- // CHECK: tosa.const values(dense<[-7, -1, 0, 1, 7]> : tensor<5xi8>)
- %a = tosa.const values(dense<[-7, -1, 0, 1, 7]> : tensor<5xi4>) : () -> tensor<5xi4>
+// CHECK-SAME: -> (tensor<6xi8>, tensor<2x3xi8>)
+func.func @subbyte_constants() -> (tensor<6xi4>, tensor<2x3xi4>) {
+ // CHECK: tosa.const values(dense<[-8, -7, -1, 0, 1, 7]> : tensor<6xi8>)
+ %a = tosa.const values(dense<[-8, -7, -1, 0, 1, 7]> : tensor<6xi4>) : () -> tensor<6xi4>
// CHECK: tosa.const values(dense<-7> : tensor<2x3xi8>)
%b = tosa.const values(dense<-7> : tensor<2x3xi4>) : () -> tensor<2x3xi4>
- // CHECK: tosa.const values(dense<[0, 8, 15]> : tensor<3xui8>)
- %c = tosa.const values(dense<[0, 8, 15]> : tensor<3xui4>) : () -> tensor<3xui4>
- return %a, %b, %c : tensor<5xi4>, tensor<2x3xi4>, tensor<3xui4>
+ return %a, %b : tensor<6xi4>, tensor<2x3xi4>
}
// -----
-// Widen scalar and dense attributes, including explicitly signed types.
+// Widen scalar and dense attributes, including nested scalar values.
// CHECK-LABEL: func.func @attributes(
// CHECK-SAME: tensor<2xi8, {type = i8}>) -> tensor<2xi8, {type = i8}>
func.func @attributes(%arg: tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i4}> {
// CHECK: tosa.identity %arg0
- // CHECK-SAME: test.nested = [{signed = -7 : si8, signless = -1 : i8, unsigned = 15 : ui8}]
- // CHECK-SAME: test.signed = dense<[-7, -1, 7]> : tensor<3xsi8>
+ // CHECK-SAME: test.nested = [{min = -7 : i8, negative = -1 : i8}]
+ // CHECK-SAME: test.values = dense<[-7, -1, 7]> : tensor<3xi8>
%0 = tosa.identity %arg {
- test.nested = [{signed = -7 : si4, signless = -1 : i4, unsigned = 15 : ui4}],
- test.signed = dense<[-7, -1, 7]> : tensor<3xsi4>
+ test.nested = [{min = -7 : i4, negative = -1 : i4}],
+ test.values = dense<[-7, -1, 7]> : tensor<3xi4>
} : (tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i4}>
return %0 : tensor<2xi4, {type = i4}>
}
@@ -161,35 +159,31 @@ func.func @attributes(%arg: tensor<2xi4, {type = i4}>) -> tensor<2xi4, {type = i
// i4 resources use one byte per element. Preserve the original blob for an i8
// view, share the widened resource, and retain storage for repeated values.
-// CHECK: module attributes {test.bytes = dense_resource<signed_data> : tensor<5xi8>, test.original = dense_resource<[[SIGNED:[a-zA-Z0-9_]+]]> : tensor<5xi8>}
+// CHECK: module attributes {test.bytes = dense_resource<values> : tensor<6xi8>, test.original = dense_resource<[[VALUES:[a-zA-Z0-9_]+]]> : tensor<6xi8>}
module attributes {
- test.bytes = dense_resource<signed_data> : tensor<5xi8>,
- test.original = dense_resource<signed_data> : tensor<5xi4>
+ test.bytes = dense_resource<values> : tensor<6xi8>,
+ test.original = dense_resource<values> : tensor<6xi4>
} {
// CHECK: func.func @resources()
- // CHECK-SAME: attributes {test.signed = dense_resource<[[REPEATED:[a-zA-Z0-9_]+]]> : tensor<3xsi8>}
- func.func @resources() attributes {test.signed = dense_resource<repeated> : tensor<3xsi4>} {
- // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi8>)
- %a = tosa.const values(dense_resource<signed_data> : tensor<5xi4>) : () -> tensor<5xi4>
- // CHECK: tosa.const values(dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui8>)
- %b = tosa.const values(dense_resource<unsigned_data> : tensor<3xui4>) : () -> tensor<3xui4>
+ // CHECK-SAME: attributes {test.repeated = dense_resource<[[REPEATED:[a-zA-Z0-9_]+]]> : tensor<3xi8>}
+ func.func @resources() attributes {test.repeated = dense_resource<repeated> : tensor<3xi4>} {
+ // CHECK: tosa.const values(dense_resource<[[VALUES]]> : tensor<6xi8>)
+ %a = tosa.const values(dense_resource<values> : tensor<6xi4>) : () -> tensor<6xi4>
return
}
// CHECK: func.func @shared_resource()
func.func @shared_resource() {
- // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi8>)
- %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi4>) : () -> tensor<5xi4>
+ // CHECK: tosa.const values(dense_resource<[[VALUES]]> : tensor<6xi8>)
+ %0 = tosa.const values(dense_resource<values> : tensor<6xi4>) : () -> tensor<6xi4>
return
}
}
{-# dialect_resources: { builtin: {
- // Alignment 1 followed by [-7, -1, 0, 1, 7], [0, 8, 15], and [-1, -1, -1].
- // CHECK-DAG: signed_data: "0x01000000090F000107"
- signed_data: "0x01000000090F000107",
- unsigned_data: "0x0100000000080F",
+ // Alignment 1 followed by [-8, -7, -1, 0, 1, 7] and [-1, -1, -1].
+ // CHECK-DAG: values: "0x0100000008090F000107"
+ values: "0x0100000008090F000107",
repeated: "0x010000000F0F0F"
- // CHECK-DAG: [[SIGNED]]: "0x01000000F9FF000107"
- // CHECK-DAG: [[UNSIGNED]]: "0x0100000000080F"
+ // CHECK-DAG: [[VALUES]]: "0x01000000F8F9FF000107"
// CHECK-DAG: [[REPEATED]]: "0x01000000FFFFFF"
} } #-}
@@ -233,31 +227,18 @@ func.func @constants() -> (tensor<5xi48>, tensor<2x3xi48>, tensor<i48>) {
// -----
-// CHECK-LABEL: func.func @unsigned_constants
-func.func @unsigned_constants() -> tensor<3xui48> {
- // CHECK: tosa.const values(dense<[0, 140737488355328, 281474976710655]> : tensor<3xui64>) : () -> tensor<3xui64>
- %0 = tosa.const values(dense<[0, 140737488355328, 281474976710655]> : tensor<3xui48>) : () -> tensor<3xui48>
- return %0 : tensor<3xui48>
-}
-
-// -----
-
// Widen packed dense arrays without reinterpreting their six-byte elements.
-// Cover signedness, empty arrays and repeated values that must remain arrays.
+// Cover boundary values, empty arrays and repeated values that remain arrays.
// CHECK-LABEL: func.func @dense_arrays(
func.func @dense_arrays(%arg: tensor<2xi48>) -> tensor<2xi48> {
// CHECK: tosa.identity %arg0
// CHECK-SAME: test.empty = array<i64>
// CHECK-SAME: test.repeated = array<i64: -1, -1, -1>
- // CHECK-SAME: test.signed = array<si64: -140737488355328, -1, 140737488355327>
- // CHECK-SAME: test.signless = array<i64: 1, -1, 2>
- // CHECK-SAME: test.unsigned = array<ui64: 0, 140737488355328, 281474976710655>
+ // CHECK-SAME: test.values = array<i64: -140737488355328, -1, 0, 1, 2, 140737488355327>
%0 = tosa.identity %arg {
test.empty = array<i48>,
test.repeated = array<i48: -1, -1, -1>,
- test.signed = array<si48: -140737488355328, -1, 140737488355327>,
- test.signless = array<i48: 1, -1, 2>,
- test.unsigned = array<ui48: 0, 140737488355328, 281474976710655>
+ test.values = array<i48: -140737488355328, -1, 0, 1, 2, 140737488355327>
} : (tensor<2xi48>) -> tensor<2xi48>
return %0 : tensor<2xi48>
}
@@ -302,26 +283,20 @@ func.func @dynamic_resource() -> tensor<?xi48> {
// The module attribute and both functions must share the widened resource.
// An i8 view of the same blob must retain the original bytes.
// CHECK-LABEL: module attributes
-// CHECK-SAME: {test.bytes = dense_resource<signed_data> : tensor<30xi8>, test.original = dense_resource<[[SIGNED:[a-zA-Z0-9_]+]]> : tensor<5xi64>}
-module attributes {test.bytes = dense_resource<signed_data> : tensor<30xi8>, test.original = dense_resource<signed_data> : tensor<5xi48>} {
- // CHECK: func.func @signed_resource
- func.func @signed_resource() {
- // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi64>) : () -> tensor<5xi64>
- %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi48>) : () -> tensor<5xi48>
+// CHECK-SAME: {test.bytes = dense_resource<values> : tensor<30xi8>, test.original = dense_resource<[[VALUES:[a-zA-Z0-9_]+]]> : tensor<5xi64>}
+module attributes {test.bytes = dense_resource<values> : tensor<30xi8>, test.original = dense_resource<values> : tensor<5xi48>} {
+ // CHECK: func.func @resource_values
+ func.func @resource_values() {
+ // CHECK: tosa.const values(dense_resource<[[VALUES]]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense_resource<values> : tensor<5xi48>) : () -> tensor<5xi48>
// CHECK: tosa.const values(dense<-1> : tensor<2xi64>) : () -> tensor<2xi64>
%dense = tosa.const values(dense<-1> : tensor<2xi48>) : () -> tensor<2xi48>
return
}
// CHECK: func.func @shared_resource
func.func @shared_resource() {
- // CHECK: tosa.const values(dense_resource<[[SIGNED]]> : tensor<5xi64>) : () -> tensor<5xi64>
- %0 = tosa.const values(dense_resource<signed_data> : tensor<5xi48>) : () -> tensor<5xi48>
- return
- }
- // CHECK: func.func @unsigned_resource
- func.func @unsigned_resource() {
- // CHECK: tosa.const values(dense_resource<[[UNSIGNED:[a-zA-Z0-9_]+]]> : tensor<3xui64>) : () -> tensor<3xui64>
- %0 = tosa.const values(dense_resource<unsigned_data> : tensor<3xui48>) : () -> tensor<3xui48>
+ // CHECK: tosa.const values(dense_resource<[[VALUES]]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense_resource<values> : tensor<5xi48>) : () -> tensor<5xi48>
return
}
}
@@ -331,15 +306,12 @@ module attributes {test.bytes = dense_resource<signed_data> : tensor<30xi8>, tes
builtin: {
// Each blob starts with a four-byte little-endian alignment header.
// 01000000 means byte alignment; the remaining bytes are packed i48s.
- // Signed values: [-140737488355328, -1, 0, 1, 140737488355327].
- // CHECK-DAG: signed_data: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F"
- signed_data: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F",
- // Unsigned values: [0, 140737488355328, 281474976710655].
- unsigned_data: "0x01000000000000000000000000000080FFFFFFFFFFFF"
+ // Values: [-140737488355328, -1, 0, 1, 140737488355327].
+ // CHECK-DAG: values: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F"
+ values: "0x01000000000000000080FFFFFFFFFFFF000000000000010000000000FFFFFFFFFF7F"
// Widened blobs have alignment 8 (08000000) and the same numeric values
- // stored as eight-byte little-endian integers, sign- or zero-extended.
- // CHECK-DAG: [[SIGNED]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF00000000000000000100000000000000FFFFFFFFFF7F0000"
- // CHECK-DAG: [[UNSIGNED]]: "0x0800000000000000000000000000000000800000FFFFFFFFFFFF0000"
+ // stored as eight-byte little-endian integers, sign-extended.
+ // CHECK-DAG: [[VALUES]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF00000000000000000100000000000000FFFFFFFFFF7F0000"
}
}
#-}
@@ -351,12 +323,10 @@ module attributes {test.bytes = dense_resource<signed_data> : tensor<30xi8>, tes
// CHECK-SAME: test.array = array<i128: -1>
// CHECK-SAME: test.resource = dense_resource<missing> : tensor<2xi128>
// CHECK-SAME: test.scalar = -1 : i128
-// CHECK-SAME: test.signed = dense<[-4, -1, 0, 3]> : tensor<4xi3>
-// CHECK-SAME: test.unsigned = dense<[0, 4, 7]> : tensor<3xui3>
+// CHECK-SAME: test.values = dense<[-4, -1, 0, 3]> : tensor<4xi3>
module @constants attributes {
test.array = array<i128: -1>,
test.resource = dense_resource<missing> : tensor<2xi128>,
test.scalar = -1 : i128,
- test.signed = dense<[-4, -1, 0, 3]> : tensor<4xi3>,
- test.unsigned = dense<[0, 4, 7]> : tensor<3xui3>
+ test.values = dense<[-4, -1, 0, 3]> : tensor<4xi3>
} {}
More information about the Mlir-commits
mailing list