From 3c482e854cd9f284ee8adf6cafbe34959b1daae3 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Thu, 30 Jul 2026 11:10:35 +0300 Subject: [PATCH] wasm32 regression test --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ iKeySort/tests/wasm32/Cargo.toml | 14 ++++++++++++++ iKeySort/tests/wasm32/run.mjs | 11 +++++++++++ iKeySort/tests/wasm32/src/lib.rs | 26 ++++++++++++++++++++++++++ iKeySort/tests/wasm32_i64.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 iKeySort/tests/wasm32/Cargo.toml create mode 100644 iKeySort/tests/wasm32/run.mjs create mode 100644 iKeySort/tests/wasm32/src/lib.rs create mode 100644 iKeySort/tests/wasm32_i64.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5002db..d22ea64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,3 +35,31 @@ jobs: - name: Test run: cargo test --manifest-path iKeySort/Cargo.toml ${{ matrix.feature_mode.features }} + + wasm32-regression: + name: wasm32 regression (Node.js) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Cache cargo registry + build + uses: Swatinem/rust-cache@v2 + + - name: Build debug wasm module + run: cargo build --manifest-path iKeySort/tests/wasm32/Cargo.toml --target wasm32-unknown-unknown + + - name: Run debug wasm module + run: node iKeySort/tests/wasm32/run.mjs iKeySort/tests/wasm32/target/wasm32-unknown-unknown/debug/wasm32_regression.wasm + + - name: Build release wasm module + run: cargo build --manifest-path iKeySort/tests/wasm32/Cargo.toml --target wasm32-unknown-unknown --release + + - name: Run release wasm module + run: node iKeySort/tests/wasm32/run.mjs iKeySort/tests/wasm32/target/wasm32-unknown-unknown/release/wasm32_regression.wasm diff --git a/iKeySort/tests/wasm32/Cargo.toml b/iKeySort/tests/wasm32/Cargo.toml new file mode 100644 index 0000000..a7a6033 --- /dev/null +++ b/iKeySort/tests/wasm32/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wasm32_regression" +version = "0.0.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +i_key_sort = { path = "../.." } + +[profile.release] +panic = "abort" diff --git a/iKeySort/tests/wasm32/run.mjs b/iKeySort/tests/wasm32/run.mjs new file mode 100644 index 0000000..aeae372 --- /dev/null +++ b/iKeySort/tests/wasm32/run.mjs @@ -0,0 +1,11 @@ +import { readFile } from "node:fs/promises"; + +const bytes = await readFile(process.argv[2]); +const { instance } = await WebAssembly.instantiate(bytes, {}); +const result = instance.exports.run_i64_32bit_boundary_regression(); + +if (result !== 0) { + throw new Error(`wasm32 i64 regression failed with code ${result}`); +} + +console.log("wasm32 i64 regression passed"); diff --git a/iKeySort/tests/wasm32/src/lib.rs b/iKeySort/tests/wasm32/src/lib.rs new file mode 100644 index 0000000..89b5ba7 --- /dev/null +++ b/iKeySort/tests/wasm32/src/lib.rs @@ -0,0 +1,26 @@ +use i_key_sort::sort::one_key::OneKeySort; + +#[unsafe(no_mangle)] +pub extern "C" fn run_i64_32bit_boundary_regression() -> i32 { + const BOUNDARY: i64 = 1i64 << 32; + + let mut values = [ + 0, + BOUNDARY - 1, + BOUNDARY, + 1, + BOUNDARY / 2, + BOUNDARY - 2, + 123, + BOUNDARY - 123, + ] + .repeat(128); + values.reverse(); + + let mut expected = values.clone(); + expected.sort_unstable(); + + values.sort_by_one_key(false, |value| *value); + + i32::from(values != expected) +} diff --git a/iKeySort/tests/wasm32_i64.rs b/iKeySort/tests/wasm32_i64.rs new file mode 100644 index 0000000..7865bbf --- /dev/null +++ b/iKeySort/tests/wasm32_i64.rs @@ -0,0 +1,28 @@ +use i_key_sort::sort::one_key::OneKeySort; + +#[test] +fn sort_i64_across_32_bit_boundary() { + const BOUNDARY: i64 = 1i64 << 32; + + // On wasm32, the old implementation truncated the full distance to zero + // while the intermediate distance became `usize::MAX`, escaping the layout. + let mut values = [ + 0, + BOUNDARY - 1, + BOUNDARY, + 1, + BOUNDARY / 2, + BOUNDARY - 2, + 123, + BOUNDARY - 123, + ] + .repeat(128); + values.reverse(); + + let mut expected = values.clone(); + expected.sort_unstable(); + + values.sort_by_one_key(false, |value| *value); + + assert_eq!(values, expected); +}