Skip to content

fix(simd): correct big-endian bitmask in scalar v128 fallback#88

Merged
Brooooooklyn merged 4 commits into
napi-rs:mainfrom
satyamg1620:endianess-fix
Jul 9, 2026
Merged

fix(simd): correct big-endian bitmask in scalar v128 fallback#88
Brooooooklyn merged 4 commits into
napi-rs:mainfrom
satyamg1620:endianess-fix

Conversation

@satyamg1620

Copy link
Copy Markdown
Contributor

The v128 fallback (used on targets without a dedicated SIMD kernel, e.g. s390x) built its escape bitmask with reversed bit order on big-endian and first_offset applied swap_bytes — reordering bytes, not bits — so trailing_zeros landed on the wrong bit, causing subtract-overflow panics and wrong-lane escapes. The bitmask is computed in software and has no byte order, so use one canonical layout (lane i -> bit i) on all targets.

Also allow(dead_code) on BitMask items used only by the wide-SIMD kernels, which are dead on scalar-only targets and broke clippy -D warnings there.

Signed-off-by: satyamg1620 <Satyam.Gupta.3@ibm.com>
Signed-off-by: satyamg1620 <Satyam.Gupta.3@ibm.com>
@satyamg1620

Copy link
Copy Markdown
Contributor Author

@Brooooooklyn, can you please review this PR

Brooooooklyn and others added 2 commits July 9, 2026 17:41
The NEON `bitmask()` builds the escape mask with `vreinterpretq_u16_u8` +
`vshrn_n_u16`, packing one nibble per lane into a u64. On big-endian
(aarch64_be) the u16 reinterpret reverses the two byte-lanes inside each
pair, so `vshrn` emits swapped nibbles; `NeonBits::as_little_endian`'s
`swap_bytes()` only fixed the byte order, leaving every lane pair
(2i, 2i+1) transposed and `clear_high_bits` operating on non-canonical
bits — the same class of wrong-lane escapes / underflow the v128 fallback
had, but on the NEON path.

Canonicalize at construction in `NeonBits::new` (reverse all 16 nibbles on
big-endian) so every `NeonBits` is `lane i -> nibble i`, mirroring the
v128 fix; `as_little_endian` then becomes a no-op clone. Little-endian is
unchanged. Verified on real aarch64_be under qemu: the differential stress
test passes through the NEON kernel, and the LE NEON path still passes.

Signed-off-by: LongYinan <lynweklm@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`-C target-cpu=native` makes LLVM emit AVX-512-class instructions
unconditionally, based on whatever CPU the assigned CI runner exposes.
CodSpeed runs the benchmark under Valgrind (simulation mode), which cannot
execute those instructions, so the process dies with SIGILL (exit 132)
whenever CI lands on a newer runner. This is flaky by construction and
unrelated to any source change.

Cap the ISA at x86-64-v3 (AVX2 baseline): deterministic, within what
Valgrind supports, and still exercises the AVX2 escape kernel.

Signed-off-by: LongYinan <lynweklm@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 3 untouched benchmarks


Comparing satyamg1620:endianess-fix (8bebe1d) with main (b69cfcb)

Open in CodSpeed

@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 8bebe1de0e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn Brooooooklyn merged commit b07b55b into napi-rs:main Jul 9, 2026
18 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 9, 2026
IWANABETHATGUY pushed a commit to rolldown/rolldown that referenced this pull request Jul 10, 2026
…ix (#10211)

### What this solves

Bumps `json-escape-simd` to `3.1.1`, which fixes JSON string escaping on
big-endian targets (s390x).

The crate's portable SIMD fallback (used on s390x — no SSE2/AVX/NEON)
built its lane bitmask with a bit order inconsistent with how the
first-escape offset was read back. On big-endian this corrupted escaped
output — e.g. `"node:module"` → `"node:modle\0\4…"` — breaking
sourcemaps, ESM import paths, and asset/data-URL escaping. In debug
builds it panics (`attempt to subtract with overflow` in
`json-escape-simd`); in release it silently corrupts output.

It reaches rolldown two ways, both fixed by this single bump:
- **directly** — `crates/rolldown/src/ecmascript/format/esm.rs`,
`parse_to_ecma_ast.rs`
- **transitively** — `oxc_sourcemap` → `json-escape-simd`

Root cause fixed upstream in napi-rs/json-escape-simd#88 and released as
`3.1.1` (the earlier `3.1.0` is buggy, so the requirement is pinned to
`3.1.1`).

### Alternatives explored

Interim we vendored the crate and used `[patch.crates-io]` (first a
local copy, then a fixed fork). This PR drops all of that in favour of
the published `3.1.1`.

### For reviewers

- The bug only manifests on **big-endian**, so existing tests don't fail
on x86 CI.
- rolldown CI currently **cross-builds** s390x but never runs the suite
on big-endian — which is why this shipped undetected.

### Tests

The existing `utils::render_ecma_module::tests::*` (sourcemap) tests
already cover this — they **panic without this bump and pass with it
when run on s390x**. No new test is added because the failure is not
reproducible on x86 CI (byte-order specific). Verified natively on
s390x: `cargo test --workspace --exclude rolldown_binding` →
previously-panicking tests pass, 1805 passed.

---------

Signed-off-by: satyamg1620 <Satyam.Gupta.3@ibm.com>
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.

2 participants