Skip to content

WASM: use relaxed_laneselect for Select in relaxed builds - #20

Merged
Auburn merged 1 commit into
masterfrom
perf/wasm-relaxed-select
Oct 17, 2026
Merged

Auburn merged 1 commit into
masterfrom
perf/wasm-relaxed-select

Conversation

@Auburn

@Auburn Auburn commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Change

In relaxed builds, the WASM f32x4 and i32x4 Select (ToolSet/WASM/128/f32x4.h, i32x4.h) now use wasm_i32x4_relaxed_laneselect instead of wasm_v128_bitselect. Non relaxed builds still use bitselect, so their code doesn't change.

Why

On x64, V8 compiles v128.bitselect to and, andnot and or. i32x4.relaxed_laneselect can use just the top bit of each lane, so V8 compiles it to a single vblendvps (blendvps without AVX). On arm64, V8 compiles both to bsl, so ARM hosts get the same code as before.

The relaxed SIMD spec only leaves the result up to the engine when a mask lane is neither all ones nor all zeros. FastSIMD masks are always all ones or all zeros: they come from comparisons and mask logic, and SelectHighBit builds its mask with >> 31. The SSE4.1 Select (_mm_blendv_ps) already relies on this.

Effect

In FastNoise2 master's WASM object (Emscripten 6.0.12, -O3 -ffast-math -msimd128 -mrelaxed-simd), v128.bitselect goes from 1712 to 90 and i32x4.relaxed_laneselect from 0 to 1622. The total instruction count stays at 118,596. LLVM emits the 90 bitselects left itself, for example for and/andnot masking with a comparison. Per Gen function (2D/3D/4D), bitselect count before → after:

  • Perlin: 16/48/96 → 0
  • Simplex: 14/26/46 → 2/2/16
  • SuperSimplex: 26/86/190 → 4/2/2
  • CellularValue: 8 → 0
  • Value, White and CellularDistance have none, so they act as a control.

I built FastNoise2 master (8c2d080) for WASM against this branch and against FastSIMD master, each linked into a small benchmark that times GenUniformGrid. I ran it in node 24.19 on a Ryzen 9 7950X, alternating the two builds across 15 node processes each, pinned to one core. Each figure is new/old of the fastest time across processes. Running master against itself the same way gave a geomean of 1.001, with all but one case within 1.5%.

Case 2D 3D 4D
Perlin 0.906 0.886 0.886
SuperSimplex 0.907 0.896 0.904
Simplex 0.945 0.951 0.942
CellularValue 0.926 0.901 0.923
FBm Simplex 0.939 0.954 0.960
Domain warped Simplex 0.994 0.921 0.894
Demo MountainTerrain 0.929 0.913 0.904
Demo CellularCaves 0.986 0.989 0.990
Value, White, CellularDistance 0.99-1.02 0.99-1.01 1.00-1.01

The geomean over all 36 cases is 0.954.

An earlier run on a FastNoise2 development branch (2D and 3D, 7 processes, on a busy machine) also gave a geomean of 0.954: Perlin 0.87-0.88, Simplex 0.91-0.92, FBm Simplex 0.92, Value/White/CellularDistance about 1.00. On that branch bitselect went from 3966 to 98.

I didn't measure on an ARM host, since node isn't installed on my M1. Going by V8's arm64 instruction selection, the code there is the same as before.

Testing

  • FastSIMD tests, Release, full count (4,194,304 inputs), seeds 24301, 1 and 2. All runs exit 0 with the same pass/known lists as master:
    • Emscripten 6.0.12 under emsdk's node 24.19: 628 passed, 12 known failures.
    • clang-cl 23 and MSVC 19.51: 628 passed, 52 known failures. These don't compile the WASM headers.
  • The WASM RELAXED tier covers the change at every register width:
    • f32 select, f32 select high bit, f32 masked mul, f32 inv masked mul, f32 operators keep lhs, and the trig tests that go through Select (acos, asin, atan, atan2, cos, sin, tan).
    • i32 select, i32 select int mask, i32 select high bit, i32 masked mul, i32 inv masked mul and i32 operators keep lhs.
    • As a check, I swapped the two laneselect inputs. That gave 68 failures for f32x4 and 24 for i32x4, all in WASM RELAXED, at 128, 256, 512 and 1024 bit. The non relaxed WASM tier still passed.
  • FastNoise2 master's WASM output is bit-identical in all 1128 cases (47 node setups × 2D/3D/4D × 4 position sets × 2 seeds).
  • FastNoise2's x86 DLLs (clang-cl 23, MSVC 19.51) only differ in their PE timestamp.

Other PRs

This only touches the two WASM headers. None of the other open performance PRs (#13, #14, #15, #16, #17, #18, #19) change WASM code. It merges cleanly with each of them (checked with git merge-tree).

FastSIMD masks are all ones or all zeros per lane, so i32x4.relaxed_laneselect
matches v128.bitselect and is a single blend on x64 hosts instead of
and/andnot/or. Strict builds keep bitselect.
@Auburn
Auburn merged commit 6fcb022 into master Oct 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant