Skip to content

Regression: Wasm32 SIMD128 narrow intrinsics stopped working correctly in Rust 1.95+ #157456

@Cykooz

Description

@Cykooz

I found that starting with Rust 1.95, all Wasm32 Simd128 narrowing intrinsics ("narrow" functions) stopped working correctly.

For example, u8x16_narrow_i16x8() takes a vector of i16 values and converts it to a vector of u8 values using saturating narrowing. In other words, negative values are clamped to 0, while values that are too large are clamped to 255.

However, there is no equivalent function that accepts a vector of u16 values. If the source vector contains u16s, the caller must manually clamp the upper bound first, for example with u16x8_min(x, 255). Otherwise, all values greater than i16::MAX will be interpreted as negative numbers and will be converted to 0 instead of 255.

This worked correctly before Rust 1.95. Starting with Rust 1.95, the compiler somehow decides that the u16x8_min(x, 255) operation is unnecessary in this case and safely removes it during optimization.

Other functions from the "narrow" group exhibit the same issue.

Here is a Godbolt example demonstrating the problem: https://rust.godbolt.org/z/1KeKzhe74

Code

I tried this code:

use core::arch::wasm32::*;

#[target_feature(enable = "simd128")]
pub unsafe fn narrow_with_min(data: [u16; 8]) -> [u8; 16] {
    let mut result = [0u8; 16];
    let mut data_u16x8 = v128_load(data.as_ptr() as *const v128);
    // This command will be removed from WASM in Rust v1.95+
    data_u16x8 = u16x8_min(data_u16x8, u16x8_splat(0xff));
    let data_u8x16 = u8x16_narrow_i16x8(data_u16x8, data_u16x8);
    v128_store(result.as_mut_ptr() as *mut v128, data_u8x16);
    result
}

#[test]
fn test_narrow_with_min() {
    let res = unsafe {narrow_with_min([0, 1, 255, 0x7fff, 0x8000, 0xffff, 0, 0])};
    // Next line fails in Rust 1.95+ because res[0..8] will be [0, 1, 255, 255, 0, 0, 0, 0]
    assert_eq!(res[0..8], [0, 1, 255, 255, 255, 255, 0, 0]);
}

WASM code from Rust 1.94:

narrow_with_min
        local.get       0
        local.get       1
        v128.load       0:p2align=1
        v128.const      255, 255, 255, 255, 255, 255, 255, 255
        i16x8.min_u
        local.tee       2
        local.get       2
        i8x16.narrow_i16x8_u
        v128.store      0:p2align=0
end_function

WASM code from Rust 1.95:

narrow_with_min
        local.get       0
        local.get       1
        v128.load       0:p2align=1
        local.tee       2
        local.get       2
        i8x16.narrow_i16x8_u
        v128.store      0:p2align=0
end_function

Version it worked on

It most recently worked on: Rust 1.94.1

Version with regression

It is broken in Rust 1.95 and 1.96

But if I use a debug build without any optimizations, everything works correctly even in these versions of Rust. This is because the compiler doesn't remove u16x8_min() from WASM.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-SIMDArea: SIMD (Single Instruction Multiple Data)C-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchI-prioritizeIssue: Indicates that prioritization has been requested for this issue.regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions