rust-lang / rust-lang/rust

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

Open
#157,456 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-SIMD C-bug I-miscompile llvm-fixed-upstream O-wasm P-high regression-from-stable-to-stable T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

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

For example, u8x16_narrow_i16x8() takes an array of i16 values and converts it to an array 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 an array of u16 values. If the source array 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided narrow_with_min reproducer and compare its optimized WASM output between Rust 1.94.1 and Rust 1.95+, using the Godbolt example as the entry point. Done means the u16x8_min operation is preserved for the narrowing intrinsics and the test returns the expected saturated values in optimized builds.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, wasm
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.