[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