[Mlir-commits] [mlir] 3bad592 - [mlir][tosa] Add integer bitwidth conversion pass (#232853)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Oct 30 06:16:52 PDT 2026
Author: Michael Platings
Date: 2026-10-30T13:16:47Z
New Revision: 3bad5925856f89ecc934510eace22a6898a718a9
URL: https://github.com/llvm/llvm-project/commit/3bad5925856f89ecc934510eace22a6898a718a9
DIFF: https://github.com/llvm/llvm-project/commit/3bad5925856f89ecc934510eace22a6898a718a9.diff
LOG: [mlir][tosa] Add integer bitwidth conversion pass (#232853)
Add tosa-convert-integer-types-bitwidth to convert i4 to i8 and/or i48
to i64. 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
Added:
mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir
mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
Modified:
mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
mlir/lib/Dialect/Tosa/Transforms/CMakeLists.txt
mlir/test/Dialect/Tosa/tosa-layerwise-constant-fold-dense-resource.mlir
mlir/test/lit.cfg.py
Removed:
################################################################################
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 0ce6c4a059c60d..bfa2aef90aaf5b 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -217,6 +217,36 @@ def TosaGatherScatterHardeningPass
}];
}
+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.
+ Only `4:8` and `48:64` are supported, mapping i4 to i8 and i48 to i64.
+ Specify either mapping or both with `bitwidth-mappings=4:8,48:64`.
+ This facilitates lowering to dialects that do not support such
+ non-standard bitwidths.
+
+ Mappings apply once to the original width, leaving unmapped widths unchanged.
+ Signless values are expected. Widening sign-extends constants.
+ 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; supported: 4:8, 48:64)">
+ ];
+}
+
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..896985154076f1 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
+ TosaConvertIntegerTypesBitwidth.cpp
TosaInputShape.cpp
ADDITIONAL_HEADER_DIRS
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
new file mode 100644
index 00000000000000..c6220c27db7355
--- /dev/null
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypesBitwidth.cpp
@@ -0,0 +1,229 @@
+//===- 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.
+// 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/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/Support/CommandLine.h"
+#include "llvm/Support/MathExtras.h"
+
+#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_TOSACONVERTINTEGERTYPESBITWIDTHPASS
+#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
+} // namespace mlir::tosa
+
+using namespace mlir;
+
+namespace {
+// Convert a validated byte-aligned payload, retaining storage for every
+// element. In particular, each i4 element occupies one byte, not half a byte.
+SmallVector<char> convertRawValues(ArrayRef<char> rawData, IntegerType type,
+ IntegerType convertedType) {
+ const unsigned inputElementSizeInBytes = llvm::divideCeil(type.getWidth(), 8);
+ const unsigned outputElementSizeInBytes = convertedType.getWidth() / 8;
+ const size_t numElements = rawData.size() / inputElementSizeInBytes;
+ SmallVector<char> data(numElements * outputElementSizeInBytes);
+ 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(inputElementSizeInBytes * 8, 0);
+ llvm::LoadIntFromMemory(value, input + i * inputElementSizeInBytes,
+ inputElementSizeInBytes);
+ // Discard padding bits before interpreting the source sign bit.
+ value = value.trunc(type.getWidth());
+ llvm::StoreIntToMemory(value.sext(convertedType.getWidth()),
+ output + i * outputElementSizeInBytes,
+ outputElementSizeInBytes);
+ }
+ 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);
+ });
+ 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](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 (type == convertedType)
+ return attr;
+ return IntegerAttr::get(
+ convertedType,
+ attr.getValue().sext(cast<IntegerType>(convertedType).getWidth()));
+ }
+
+ Attribute convertDenseArray(DenseArrayAttr attr) {
+ Type type = attr.getElementType();
+ Type convertedType = replace(type);
+ if (type == convertedType)
+ return attr;
+ // Unlike dense elements, arrays always store every element, even splats.
+ const 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();
+ Type convertedElementType = convertedType.getElementType();
+ if (elementType == convertedElementType) {
+ // reshape is still necessary to handle any encoding changes.
+ return attr.reshape(convertedType);
+ }
+
+ auto convertedIntegerType = dyn_cast<IntegerType>(convertedElementType);
+ if (!convertedIntegerType)
+ return nullptr;
+ return attr
+ .mapValues(convertedElementType,
+ [convertedIntegerType](const APInt &value) {
+ return value.sext(convertedIntegerType.getWidth());
+ })
+ .reshape(convertedType);
+ }
+
+ 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 convertedIntegerType = dyn_cast<IntegerType>(convertedElementType);
+ if (!convertedIntegerType)
+ return nullptr;
+ auto integerType = cast<IntegerType>(elementType);
+ const AsmResourceBlob *blob = attr.getRawHandle().getBlob();
+ if (!blob)
+ return nullptr;
+ ArrayRef<char> rawData = blob->getData();
+ const unsigned elementSizeInBytes =
+ llvm::divideCeil(integerType.getWidth(), 8);
+ if (rawData.size() % elementSizeInBytes != 0)
+ return nullptr;
+ // Check resource is correct size.
+ if (type.hasStaticShape() &&
+ rawData.size() !=
+ elementSizeInBytes * static_cast<uint64_t>(type.getNumElements()))
+ return nullptr;
+ const SmallVector<char> data =
+ convertRawValues(rawData, integerType, convertedIntegerType);
+ const unsigned alignment = convertedIntegerType.getWidth() / 8;
+ return DenseResourceElementsAttr::get(
+ convertedType, attr.getRawHandle().getKey(),
+ HeapAsmResourceBlob::allocateAndCopyWithAlign(data, alignment));
+ }
+
+ // Payload handlers already converted their subelements.
+ 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 TosaConvertIntegerTypesBitwidth
+ : tosa::impl::TosaConvertIntegerTypesBitwidthPassBase<
+ TosaConvertIntegerTypesBitwidth> {
+ 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) {
+ if (!((source == 4 && destination == 8) ||
+ (source == 48 && destination == 64))) {
+ getOperation().emitError("unsupported integer bitwidth mapping ")
+ << source << ":" << destination << ": expected 4:8 or 48:64";
+ return signalPassFailure();
+ }
+ mappings.try_emplace(source, destination);
+ }
+ IntegerTypeReplacer replacer(mappings);
+ 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-bitwidth-invalid.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
new file mode 100644
index 00000000000000..4af5d83b9a344f
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-invalid.mlir
@@ -0,0 +1,55 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64' %s
+
+// Complete elements must match the element count of a static shape.
+// 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.
+ short: "0x01000000FFFFFFFFFFFF"
+} } #-}
+
+// -----
+
+// An unresolved shape must not allow a payload containing an incomplete i48.
+// 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.
+ partial: "0x010000000102"
+} } #-}
+
+// -----
+
+// Report missing i48 resources nested in attributes even when the operation's
+// operand and result types need no widening.
+// 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.
+// 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"
+} } #-}
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
new file mode 100644
index 00000000000000..065184d9102bf9
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-mappings.mlir
@@ -0,0 +1,28 @@
+// REQUIRES: host-byteorder-little-endian
+// RUN: mlir-opt -split-input-file -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4:8,48:64' -mlir-print-op-generic %s | FileCheck %s
+
+// Convert nested types even when resource data is unavailable and its element
+// type is unchanged. Reversing the mapping list must have no effect.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "mapping_encodings"}>
+// CHECK: test.type = tensor<2xi8, {test.widths = [i8, i64, i8]}>
+// CHECK-SAME: test.unresolved_resource = dense_resource<missing> : tensor<2xi7, {test.widths = [i8, i64, i8]}>
+module @mapping_encodings attributes {
+ test.type = tensor<2xi4, {test.widths = [i4, i48, i8]}>,
+ test.unresolved_resource = dense_resource<missing> : tensor<2xi7, {test.widths = [i4, i48, i8]}>
+} {}
+
+// -----
+
+// Widen resource elements and their encodings together. Discard padding bits
+// before interpreting the i4 sign bit.
+// CHECK-LABEL: "builtin.module"() <{sym_name = "widen_resources"}>
+// CHECK: test.values = dense_resource<[[VALUES:[a-zA-Z0-9_]+]]> : tensor<4xi8, {test.widths = [i8, i64, i8]}>
+module @widen_resources attributes {
+ test.values = dense_resource<values> : tensor<4xi4, {test.widths = [i4, i48, i8]}>
+} {}
+
+{-# dialect_resources: { builtin: {
+ // [-8, -1, 0, 7], with one byte per i4 value and nonzero padding bits.
+ values: "0x01000000F8FFB0A7"
+ // CHECK: [[VALUES]]: "0x01000000F8FF0007"
+} } #-}
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..7021d64e660d99
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth-options.mlir
@@ -0,0 +1,22 @@
+// 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=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=4:16' %s 2>&1 | FileCheck %s --check-prefix=BAD-MAPPING
+// RUN: not mlir-opt -tosa-convert-integer-types-bitwidth='bitwidth-mappings=4' %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.
+// BAD-MAPPING: error: unsupported integer bitwidth mapping 4:16: expected 4:8 or 48:64
+// SYNTAX: expected <source>:<destination> integer bitwidths
diff --git a/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
new file mode 100644
index 00000000000000..41dcb03b19974b
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-convert-integer-types-bitwidth.mlir
@@ -0,0 +1,332 @@
+// REQUIRES: host-byteorder-little-endian
+// 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> {
+ // CHECK: tosa.clamp %{{.*}} min_val(-140737488355328 : i64) max_val(140737488355327 : i64) {test.unrelated_attribute_that_must_be_preserved = "keep"} : (tensor<4xi64>) -> tensor<4xi64>
+ %0 = tosa.clamp %arg min_val(-140737488355328 : i48) max_val(140737488355327 : i48) {test.unrelated_attribute_that_must_be_preserved = "keep"} : (tensor<4xi48>) -> tensor<4xi48>
+ return %0 : tensor<4xi48>
+}
+
+// -----
+
+// 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 @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>
+}
+
+// -----
+
+// 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 = [{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>
+}
+
+// -----
+
+// Mixed types and multiple results; preserve function and call attributes.
+// CHECK-LABEL: func.func private @mixed(
+// 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<*xi64>, %[[C:.*]]: tensor<2xi32>) -> (tensor<*xi64>, tensor<?xi64>, tensor<2xi32>) attributes {test.unrelated_attribute_that_must_be_preserved = "function"}
+func.func @mixed_caller(%a: tensor<?xi48>, %b: tensor<*xi48>, %c: tensor<2xi32>) -> (tensor<*xi48>, tensor<?xi48>, tensor<2xi32>) attributes {test.unrelated_attribute_that_must_be_preserved = "function"} {
+ // CHECK: %[[CALL:.*]]:3 = call @mixed(%[[A]], %[[B]], %[[C]]) {test.unrelated_attribute_that_must_be_preserved = "call"} : (tensor<?xi64>, tensor<*xi64>, tensor<2xi32>) -> (tensor<*xi64>, tensor<?xi64>, tensor<2xi32>)
+ %0:3 = func.call @mixed(%a, %b, %c) {test.unrelated_attribute_that_must_be_preserved = "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>
+}
+
+// -----
+
+// 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>
+}
+
+// -----
+
+// 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>>
+}
+
+// -----
+
+// 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) -> (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<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>
+ return %a, %b : tensor<6xi4>, tensor<2x3xi4>
+}
+
+// -----
+
+// 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 = [{min = -7 : i8, negative = -1 : i8}]
+ // CHECK-SAME: test.values = dense<[-7, -1, 7]> : tensor<3xi8>
+ %0 = tosa.identity %arg {
+ 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}>
+}
+
+// -----
+
+// 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<values> : tensor<6xi8>, test.original = dense_resource<[[VALUES:[a-zA-Z0-9_]+]]> : tensor<6xi8>}
+module attributes {
+ test.bytes = dense_resource<values> : tensor<6xi8>,
+ test.original = dense_resource<values> : tensor<6xi4>
+} {
+ // CHECK: func.func @resources()
+ // 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<[[VALUES]]> : tensor<6xi8>)
+ %0 = tosa.const values(dense_resource<values> : tensor<6xi4>) : () -> tensor<6xi4>
+ return
+ }
+}
+{-# dialect_resources: { builtin: {
+ // Alignment 1 followed by [-8, -7, -1, 0, 1, 7] and [-1, -1, -1].
+ // CHECK-DAG: values: "0x0100000008090F000107"
+ values: "0x0100000008090F000107",
+ repeated: "0x010000000F0F0F"
+ // CHECK-DAG: [[VALUES]]: "0x01000000F8F9FF000107"
+ // CHECK-DAG: [[REPEATED]]: "0x01000000FFFFFF"
+} } #-}
+
+// -----
+
+// 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>
+}
+
+// -----
+
+// Widen packed dense arrays without reinterpreting their six-byte elements.
+// 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.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.values = array<i48: -140737488355328, -1, 0, 1, 2, 140737488355327>
+ } : (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"}>
+}
+
+// -----
+
+// 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"
+ }
+ }
+#-}
+
+// -----
+
+// 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<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<[[VALUES]]> : tensor<5xi64>) : () -> tensor<5xi64>
+ %0 = tosa.const values(dense_resource<values> : tensor<5xi48>) : () -> tensor<5xi48>
+ 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.
+ // 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-extended.
+ // CHECK-DAG: [[VALUES]]: "0x08000000000000000080FFFFFFFFFFFFFFFFFFFF00000000000000000100000000000000FFFFFFFFFF7F0000"
+ }
+ }
+#-}
+
+// -----
+
+// 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.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.values = dense<[-4, -1, 0, 3]> : tensor<4xi3>
+} {}
diff --git a/mlir/test/Dialect/Tosa/tosa-layerwise-constant-fold-dense-resource.mlir b/mlir/test/Dialect/Tosa/tosa-layerwise-constant-fold-dense-resource.mlir
index 9b9f203759bc82..59688cac27ad9a 100644
--- a/mlir/test/Dialect/Tosa/tosa-layerwise-constant-fold-dense-resource.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-layerwise-constant-fold-dense-resource.mlir
@@ -1,9 +1,6 @@
+// REQUIRES: host-byteorder-little-endian
// RUN: mlir-opt --split-input-file --tosa-layerwise-constant-fold %s | FileCheck %s
-// Skip big-endian platforms for dense resources
-// XFAIL: target={{(s390x|sparc.*)-.*}}
-// XFAIL: system-aix
-
// CHECK-LABEL: @transpose_fold_dense_resource
func.func @transpose_fold_dense_resource() -> tensor<2x2xf32> {
%0 = "tosa.const"() <{values = dense_resource<resource> : tensor<2x2xf32>}> : () -> tensor<2x2xf32>
diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py
index 8b732213668a45..ca070a37ebda50 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:
More information about the Mlir-commits
mailing list