Skip to content

Fail fast at startup when simdutf has no usable implementation for this CPU - #30642

Open
robobun wants to merge 1 commit into
mainfrom
farm/80eb7318/simdutf-unsupported-cpu-guard
Open

Fail fast at startup when simdutf has no usable implementation for this CPU#30642
robobun wants to merge 1 commit into
mainfrom
farm/80eb7318/simdutf-unsupported-cpu-guard

Conversation

@robobun

@robobun robobun commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • On an x64 CPU without SSE4.2 (QEMU's default TCG vCPU, pre-Nehalem hardware) Bun does not start cleanly: bun app.js hangs forever, or fails with a bogus error: Invalid UTF-8 byte sequence on a valid file, or (on the v1.3.9 release in the original report) segfaults after ~16 seconds and ~4 GB of allocations. There is no hint that the CPU is the problem.
  • Cause: every x64 build is compiled with -march=nehalem (scripts/build/flags.ts), which makes simdutf compile out its scalar fallback on the assumption that its SSE4.2 kernel can always run. On a CPU without SSE4.2, simdutf's dispatcher finds no usable kernel and installs an unsupported_implementation stub whose functions all return 0/false. Bun trusts those answers: validate_utf8 rejects every file, and first_non_ascii (src/bun_core/lib.rs) reports a non-ASCII byte at index 0 for any input longer than its 32-byte scalar fast path, so the scan loops built on it never advance.
  • Regressed in v1.3.9, when the prebuilt WebKit (which is where simdutf is compiled) started receiving explicit -march flags (oven-sh/WebKit@596e48e); before that the scalar fallback was compiled in.

Fix

  • bun_simdutf_sys::simdutf::has_any_implementation() validates one ASCII byte. This forces simdutf's lazy dispatch to run, and the stub's validate_ascii unconditionally returns false, so the result distinguishes a real kernel from the stub without touching simdutf internals.
  • main() in src/bun_bin/lib.rs calls it immediately after capturing argv, before the crash handler and the Windows environment conversion (both push strings through simdutf). On failure it calls bun_abort_missing_simd (src/jsc/bindings/c-bindings.cpp), which prints which instruction set is missing (SSE4.2 on x64, NEON on arm64), a VM hint, and the value of SIMDUTF_FORCE_IMPLEMENTATION if set, then exits 134. It uses the C runtime because Output is not initialized yet.
  • Verified with test/regression/issue/30613.test.ts: setting SIMDUTF_FORCE_IMPLEMENTATION to an unknown name makes simdutf install the same stub as an unsupported CPU, so the test reproduces the bug on any x64 machine. Without the fix the child hangs or fails with the UTF-8 error; with it, stderr names the requirement and the exit code is 134. A second test confirms a normal run is unaffected. The forced-stub test is skipped on arm64, where simdutf compiles a single kernel and bypasses dispatch entirely.
  • Also run: the probe against the unfixed debug build (bun app.js under the forced stub hangs until killed; bun --version and short -e scripts still work because they stay under the 32-byte scalar path), and cargo clippy --workspace.

Background

  • simdutf is the SIMD UTF-8/UTF-16/base64 library used by both WebKit and Bun (Bun reaches it through wtf/SIMDUTF.h, so it is compiled once, inside the prebuilt WebKit). It ships several kernels (icelake, haswell, westmere, fallback) and picks one at first use by reading CPUID. Kernels that the compile-time -march proves redundant are removed: with -march=nehalem, __SSE4_2__ is defined, the westmere kernel "can always run", and the scalar fallback is dropped.
  • unsupported_implementation is simdutf's placeholder for "no kernel matched". It is also what SIMDUTF_FORCE_IMPLEMENTATION=<unknown name> selects, which is what makes the bug testable without special hardware.
  • Exit on startup if SSE4.2 is not available #14745 asks for exactly this startup check. One limitation: like the rest of the binary, the probe and the diagnostic are compiled for nehalem. That is sufficient for the failures seen in practice (the CPU in Bun crashes on non-AVX2 CPUs (all versions after v1.3.8) #30613 executed Bun for 16 seconds before dying, so reaching main() is not the problem), but a CPU so old that nehalem codegen itself faults before main() would still die with SIGILL. Moving the check into a separately compiled object, as Exit on startup if SSE4.2 is not available #14745 suggests, would be a build-system change and is out of scope here.
  • Restoring actual support for pre-SSE4.2 CPUs would mean building WebKit's simdutf with SIMDUTF_IMPLEMENTATION_FALLBACK=1; that is a separate oven-sh/WebKit change. This PR only makes the failure immediate and explicit.
Earlier revisions of this PR

Previous iterations also added a Rosetta 2 recovery path, a bun_warn_avx_missing suppression under Rosetta, and a hint pointing users of the default (-march=haswell) x64 build at the baseline download. #34782 made every x64 build nehalem and removed bun_warn_avx_missing and Environment::BASELINE, which made all of that unreachable: under Rosetta 2 the translated CPUID still advertises SSE4.2 and PCLMULQDQ, so the westmere kernel matches and the stub is never installed. Those pieces were dropped when rebasing onto that change; the remaining diff is the startup check alone. Earlier .zig reference-file edits were likewise dropped when #32621 removed those files; the Rust first_non_ascii has no equivalent of the Zig slice underflow.

Fixes #30613
Fixes #14745

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a runtime probe and early startup guard that aborts with a platform-specific SIMDUTF requirement message (exit 134) when no real SIMDUTF implementation is selected; includes a low-level abort routine, a defensive empty-slice fix, a Cargo dependency, and two regression tests.

Changes

SIMDUTF CPU support validation and early failure

Layer / File(s) Summary
SIMDUTF implementation detection API
src/simdutf_sys/bun-simdutf.cpp, src/simdutf_sys/simdutf.zig
Adds simdutf__has_implementation() C probe that calls simdutf::validate_ascii on a known byte; exposes hasAnyImplementation() Zig wrapper returning that boolean.
Early startup check and abort helper
src/main.zig, src/bun_bin/lib.rs, src/bun_bin/Cargo.toml
Insert early runtime check in Zig and Rust entrypoints calling hasAnyImplementation(); if false, compute requirement/hint and call external bun_abort_missing_simd(requirement, hint) which prints to raw stderr and exits with code 134; add bun_simdutf_sys workspace dependency.
Low-level abort implementation
src/jsc/bindings/c-bindings.cpp
Add exported bun_abort_missing_simd no-return entrypoint that prints the SIMDUTF requirement message, optionally notes SIMDUTF_FORCE_IMPLEMENTATION, flushes stderr, and exits(134).
Empty slice defensive handling
src/string/immutable.zig
firstNonASCII() now returns null for empty slices to avoid false non-ASCII detection from a SIMDUTF stub.
Regression tests
test/regression/issue/30613.test.ts
Add two tests: one forcing an unknown SIMDUTF implementation and asserting fast-fail with exit 134 and diagnostic, and one asserting normal execution when a supported implementation exists.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses all requirements from issues #30613 and #14745: early startup detection of missing SIMD features with a diagnostic message and clean exit, plus defensive fixes to prevent underflow in downstream code.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the stated objectives: early SIMDUTF detection, diagnostic error output, empty-slice guards, and regression tests. No unrelated modifications detected.
Title check ✅ Passed The title clearly and concisely describes the main change: failing fast when SIMDUTF has no usable CPU implementation.
Description check ✅ Passed The description explains the problem, fix, background, limitations, and verification, including regression tests and clippy results.

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented May 13, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:15 PM PT - Aug 15th, 2026

@robobun, your commit 0cf6ac1 is building: #98597

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Exit on startup if SSE4.2 is not available #14745 - Feature request for exactly what this PR implements: detect missing SSE4.2 at startup and exit with a clear error instead of crashing with SIGILL
  2. linux baseline build has illegal instruction on bun --version #7179 - Linux baseline build crashes with "Illegal instruction" on bun --version, which is the exact symptom of running on a CPU lacking SSE4.2 with no simdutf fallback

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #14745
Fixes #7179

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/regression/issue/30613.test.ts`:
- Around line 42-43: Remove the brittle negative assertions that check stderr
for crash words and instead assert on the process termination status: delete the
two expect(...) calls referencing stderr and replace them with assertions that
validate the child/process finished cleanly using signalCode and/or exitCode
(e.g., expect(signalCode).toBeNull() or expect(exitCode).toBe(0)) so the test
verifies termination status rather than text content; update the test in
issue/30613.test.ts to reference the existing stderr, signalCode and exitCode
variables accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 730dd3f2-63b2-41b8-82dc-cb25184e786e

📥 Commits

Reviewing files that changed from the base of the PR and between b9c757b and b03a7eb.

📒 Files selected for processing (5)
  • src/main.zig
  • src/simdutf_sys/bun-simdutf.cpp
  • src/simdutf_sys/simdutf.zig
  • src/string/immutable.zig
  • test/regression/issue/30613.test.ts

Comment thread test/regression/issue/30613.test.ts Outdated
Comment thread src/main.zig Outdated
Comment thread src/main.zig Outdated
Comment thread test/regression/issue/30613.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all three points — the reorder before convertEnvToWTF8/initArgv and the raw-stderr/std.c.getenv path look right, and the Environment.baseline branch fixes the diagnostic wording. I'm not auto-approving only because this touches main() startup ordering on every platform; worth a human sign-off (and confirming the Windows shards are green on 6671584, since the CI comment above is still pinned to e03c408).

Extended reasoning...

Overview

This PR adds a fail-fast probe at the very top of main() that calls simdutf::validate_ascii("a", 1) to detect when simdutf has fallen back to its unsupported_implementation stub (which happens on CPUs below the build's compiled-in ISA floor — SSE4.2 for baseline, AVX2 for default). On detection it prints a build-variant-specific diagnostic to raw stderr and exits 134, replacing a ~16 s / ~4 GB death spiral ending in SIGSEGV. It also adds a defensive empty-slice short-circuit to firstNonASCII and a regression test that simulates the condition via SIMDUTF_FORCE_IMPLEMENTATION.

Files touched: src/main.zig (entry point), src/simdutf_sys/bun-simdutf.cpp + simdutf.zig (new probe), src/string/immutable.zig (one-line guard), test/regression/issue/30613.test.ts (new).

Security risks

None identified. The new code reads one environment variable (SIMDUTF_FORCE_IMPLEMENTATION) for diagnostic display only, writes a fixed-format message to stderr, and exits. No parsing of untrusted input, no auth/crypto/permissions surface.

Level of scrutiny

High. src/main.zig:main() is the absolute entry point — every Bun invocation runs this code, and the probe is now the first non-trivial call after crash_handler.init(). Startup ordering here is platform-sensitive: my earlier review caught that the original placement ran after Windows convertEnvToWTF8/initArgv (which themselves use simdutf), causing a panic before the diagnostic could print. That was fixed in 6671584 by moving the probe earlier and switching to raw std.fs.File.stderr().writerStreaming(&.{}) + std.c.getenv (both established patterns — the crash handler uses the same writer construction). The fix looks correct, but changes to process-startup ordering across Windows/POSIX deserve a maintainer's eyes rather than bot approval.

Other factors

  • All three of my prior inline comments (Windows ordering 🔴, baseline-vs-default diagnostic wording 🟡, negative stderr assertions 🟡) were addressed in b03a7eb / e03c408 / 6671584 and are marked resolved.
  • The robobun CI status comment is still showing results for e03c408 (which had the expected Windows failures I'd flagged); 6671584 is the fix for those, so the Windows shards should be confirmed green on the latest commit before merge. The musl build failures listed there look like infra and are likely unrelated.
  • The firstNonASCII empty-slice guard is a one-line, zero-risk hardening on a hot path.
  • Test coverage is reasonable: one case forces the stub and asserts the diagnostic + clean exit, one case asserts normal runs are unaffected.
  • Exit code 134 (SIGABRT-like) is a minor design choice a maintainer may want to weigh in on vs. a plain non-zero exit.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — all three of my earlier comments are addressed and the current revision looks correct to me, but since this adds a hard-exit probe to main() that runs before Output/env/argv init on every process start, I'd like a human to sanity-check the startup ordering rather than auto-approving.

Extended reasoning...

Overview

This PR adds an early-startup probe (simdutf__has_implementation()validate_ascii("a", 1)) in src/main.zig that detects when simdutf has fallen back to its unsupported_implementation stub (host CPU below the build's SIMD floor) and exits with a clear diagnostic via a new bun_abort_missing_simd C helper, instead of the prior ~16 s / ~4 GB heap-walk segfault. It also adds a defensive empty-slice short-circuit to firstNonASCII, the Zig binding hasAnyImplementation(), and a regression test that forces the stub via SIMDUTF_FORCE_IMPLEMENTATION. Six files touched: src/main.zig, src/jsc/bindings/c-bindings.cpp, src/simdutf_sys/{bun-simdutf.cpp,simdutf.zig}, src/string/immutable.zig, and test/regression/issue/30613.test.ts.

Security risks

None identified. The new code reads one environment variable (SIMDUTF_FORCE_IMPLEMENTATION) for diagnostic display only and otherwise just calls fprintf(stderr) + exit(134) with compile-time-constant strings. No user-controlled data flows into format strings, allocations, or control flow.

Level of scrutiny

High. The probe is inserted into main() immediately after crash_handler.init() and before Windows convertEnvToWTF8/initArgv and Output.Source.Stdio.init(). It executes on every Bun invocation on every platform; a false negative from validate_ascii("a", 1) (or any future simdutf change that makes a real implementation return false for a 1-byte input) would brick every process with exit 134. The ordering relative to other early-startup steps is subtle — I already caught one ordering bug here (probe originally ran after Windows UTF-16→UTF-8 env conversion, which itself uses simdutf), and the fix routed the diagnostic through raw C-runtime stderr/getenv to avoid depending on uninitialized Output/converted env. That interaction is exactly the kind of thing a maintainer familiar with Bun's startup sequencing should eyeball.

Other factors

All prior review feedback (mine and CodeRabbit's) has been addressed in commits e03c408, b03a7eb, 6671584, and 92c1aa5: the brittle not.toContain("panic") assertions were removed, the diagnostic now branches on Environment.baseline to name AVX2 vs SSE4.2 correctly and point non-baseline users at the baseline download, and the probe was hoisted before the Windows env/argv conversion with output rerouted through fprintf(stderr). The regression test covers both the failure and the happy path. The change is well-reasoned and a strict improvement over the status quo; my deferral is purely about the blast radius of main(), not any specific concern with the current code.

@robobun

robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

CI status

Rebuilt on current main at ad9b670 (single commit, 6 files, +121). The rebase conflicted with two upstream changes that obsoleted parts of the earlier revisions, so rather than carry dead code through 13 commits the branch was squashed and re-derived:

What remains is the startup check, bun_abort_missing_simd, and two regression tests. PR title and description rewritten to match; the superseded design is summarized in a collapsed section of the description.

Verification on this revision:

  • test/regression/issue/30613.test.ts fails against the unfixed build on current main (the forced stub now makes Bun reject bunfig.toml as invalid UTF-8; bun app.js hangs in a scan loop) and passes with the fix (diagnostic + exit 134).
  • cargo clippy --workspace clean.
  • All review threads resolved. claude[bot]'s one finding on this revision was that the description still described the dropped pieces, which the rewrite addresses. Its suggestion to drop Fixes #14745 was based on a misread of that issue (Exit on startup if SSE4.2 is not available #14745 is the request for exactly this startup check, not the Rosetta hang), so the link stays.

Rebased onto main at 0cf6ac1 (the commit's diff is byte-identical to ad9b670; only the base moved). Reason: build 98222 for the previous head finished with 177/177 real jobs passed and zero test failures, but ended CANCELED because its two darwin 14 aarch64 - test-bun shards could never start (no release=14 aarch64 agent was connected; they expired three times in a row). Main has since landed #39191 and #39195, which give PR builds a darwin aarch64 lane any connected mac agent can take, so the rebase picks that up and lets the aggregate go green. The only test annotation on 98222 was a child_process_ipc_handle.test.ts flake on linux aarch64 that passed on retry.

30613.test.ts has passed on every lane on every build of this revision, including build 98597 for 0cf6ac1 (the rebase worked: the darwin lane is now darwin any aarch64 and gets picked up).

Build 98597 is now complete: 174 of 179 jobs passed; every failure is a shard of the single debian-13 x64-asan lane. All other lanes, including the restored darwin aarch64 lane, are green. Each failing test is also failing on other branches' concurrent builds, so this is a regression in the ASAN lane on main (which is PR-only, so main's own builds stay green while every PR's ASAN lane fails). All six are reported to main-break triage:

Test (all on x64-asan) Also failing on
test/cli/run/require-cache.test.ts builds 98600, 98601, 98602, 98604, 98606
test/js/bun/util/inspect-error-leak.test.js builds 98600, 98601, 98602, 98604, 98606
test/js/web/timers/setInterval.test.js builds 98600, 98601, 98608
test/js/node/vm/sourcetextmodule-leak.test.ts builds 98600, 98601
test/js/workerd/html-rewriter-leak.test.ts build 98601
vendor/elysia/test/response/stream.test.ts build 98602

This diff adds one validate_ascii call on a single byte at the top of main(); it allocates nothing and cannot move a leak delta, a timer, or the require cache. If it were aborting child processes, every spawning test on every lane would fail with the new diagnostic; 174 shards are green and 30613.test.ts itself passes on every lane including x64-asan.

Ready for a maintainer to merge.

@robobun

robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (post-Rust-rewrite) and ported:

  • src/simdutf_sys/simdutf.rs: simdutf__has_implementation extern + has_any_implementation() wrapper
  • src/bun_bin/lib.rs: probe at the very top of main() (before init_argv/convert_env_to_wtf8) + abort_for_unsupported_simdutf() helper
  • src/bun_bin/Cargo.toml: add bun_simdutf_sys dep
  • src/runtime/cli/upgrade_command.rs: SIMDUTF_BASELINE_HINT CStr constant so the hint URL stays in sync with BASELINE_ZIP_FILENAME

The firstNonASCII empty-slice guard isn't needed in Rust — bun_core::strings_impl::first_non_ascii already has a len() <= 32 scalar fast path that correctly returns None for empty input. C++ hunks (c-bindings.cpp, bun-simdutf.cpp) applied as-is.

Gate verified: without the .rs change, stderr is empty and the first test fails; with it, both tests pass. .zig kept as reference.

@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch from de746bc to f079b91 Compare May 14, 2026 17:16
Comment thread src/jsc/bindings/c-bindings.cpp Outdated
@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch from 082c451 to 1726642 Compare May 21, 2026 01:45
Comment thread src/runtime/cli/upgrade_command.rs Outdated
@robobun robobun changed the title Fail fast on CPUs below the SSE4.2 baseline instead of crashing in simdutf Fail fast on CPUs below the SIMD baseline and recover simdutf dispatch under Rosetta 2 Jun 5, 2026
Comment thread src/bun_bin/lib.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback is addressed and I didn't find anything new, but this touches the very first code in main() on every invocation plus platform-specific Rosetta 2 dispatch recovery that relies on simdutf's internal kernel ordering — worth a human pass before merge.

Extended reasoning...

Overview

This PR adds an early-startup probe in src/bun_bin/lib.rs::main() that detects simdutf's unsupported_implementation stub (selected when the host CPU lacks every ISA the binary was compiled for) and either (a) aborts with a clear diagnostic via a new bun_abort_missing_simd C function, or (b) on macOS x64 under Rosetta 2, forces simdutf onto its least-demanding compiled kernel and continues. Supporting changes span bun-simdutf.cpp (probe + recovery), c-bindings.cpp (abort routine + Rosetta-aware suppression of the pre-existing AVX warning), upgrade_command.rs (baseline-download hint constant), Rust/Zig FFI wrappers, a defensive empty-slice guard in firstNonASCII, and a regression test.

Security risks

None identified. The new code reads SIMDUTF_FORCE_IMPLEMENTATION and sysctl.proc_translated, prints static C-string diagnostics to stderr, and calls exit(134). No untrusted input is parsed; no auth/crypto/permissions surfaces are touched.

Level of scrutiny

High. The probe runs as the very first statement in main() on every Bun invocation across all platforms — before argv capture, before the crash handler, before Windows env conversion. The Rosetta recovery path mutates process-global simdutf dispatch state based on a non-obvious invariant ("the last entry in get_available_implementations() requires no more than the binary's compiled ISA") that depends on simdutf's internal CAN_ALWAYS_RUN_* pruning behavior. The recovery branch is only exercised on the x64-under-Rosetta CI lane, not by the env-var simulation in the regression test. These are exactly the kinds of cross-platform, hard-to-test-locally startup changes that benefit from a maintainer's eye.

Other factors

This PR has been through six rounds of inline review (Windows ordering, baseline-vs-default diagnostic wording, test assertion style, stale comment cross-reference, pre-existing musl URL bug, and the spurious AVX warning under Rosetta) — all resolved, the last one in the current HEAD commit 1c90755. CI was reported green on the diff with only unrelated infra flakes remaining. The implementation is well-commented and the PR description is thorough. My hesitation to auto-approve is purely about scope (process entry point + platform-specific runtime dispatch manipulation), not about any specific concern with the code as written.

@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch 2 times, most recently from 97d49af to d89628d Compare June 5, 2026 07:37

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my prior feedback has been addressed and I have no further findings, but this touches the process entry point and adds platform-specific CPU-dispatch recovery that relies on simdutf's internal implementation ordering — worth a human sign-off.

Extended reasoning...

Overview

This PR adds a simdutf probe at the very top of main() (before argv/env capture, before the crash handler), a Rosetta 2 recovery path that force-selects a simdutf kernel via get_active_implementation() = ..., a new [[noreturn]] C entry point (bun_abort_missing_simd), a sysctl-gated early-return in bun_warn_avx_missing, and a baseline-URL hint constant in upgrade_command.rs. It spans Rust, C++, and reference-only Zig across 11 files, plus a 3-case regression test.

Security risks

None identified. The only new untrusted input is the SIMDUTF_FORCE_IMPLEMENTATION env var, which is read via CRT getenv and printed back through fprintf("%s") — no format-string or injection surface. The sysctl read is fixed-name with a stack-local int.

Level of scrutiny

High. The probe runs unconditionally on every process start, before any other initialization, and a false negative would make Bun refuse to launch on valid hardware. The Rosetta recovery depends on two simdutf-internal invariants (the available-implementations list is ordered most→least demanding, and CAN_ALWAYS_RUN_* pruning leaves the last entry no stricter than the TU's own -march); both are well-argued in the comments and re-probed defensively, but they're assumptions about a vendored dependency's internals that a maintainer should ratify. The change also reorders main() so the probe precedes init_argv / the crash handler — intentional and explained, but entry-point ordering is exactly the kind of thing that benefits from a second pair of eyes.

Other factors

I've reviewed this PR across five revisions; every issue I raised (Windows ordering, baseline-vs-AVX2 wording, stale Zig cross-reference, AVX warning under Rosetta, pre-existing musl URL) has been addressed or explicitly deferred. The bug-hunting system found nothing this run. CI is green on the new test across all lanes; the remaining failures are documented as repo-wide and unrelated. The change is well-reasoned and well-tested — I'm deferring purely on scope (process entry point + cross-language FFI + platform-specific dispatch), not on any open concern.

@hen-corix

Copy link
Copy Markdown

Is there a chance that this could be merged soon?

Comment thread src/bun_bin/lib.rs Outdated
Comment thread src/bun_bin/lib.rs Outdated
Comment thread src/jsc/bindings/c-bindings.cpp Outdated
Comment thread src/simdutf_sys/simdutf.rs Outdated
Comment thread src/bun_bin/lib.rs
@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch from a8b03a0 to 4de8c29 Compare August 15, 2026 14:53
Comment thread src/bun_bin/lib.rs Outdated
Comment thread src/simdutf_sys/simdutf.rs Outdated
@robobun robobun changed the title Fail fast on CPUs below the SIMD baseline and recover simdutf dispatch under Rosetta 2 Fail fast at startup when simdutf has no usable implementation for this CPU Aug 15, 2026
@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch from 4de8c29 to ad9b670 Compare August 15, 2026 14:58
Comment thread src/simdutf_sys/simdutf.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, well-scoped startup diagnostic that's been through several review rounds; all prior threads are resolved.

What was reviewed:

  • Probe placement in main(): init_argv only stores raw pointers (no simdutf), and the check runs before the crash handler / Windows env conversion, both of which do reach simdutf.
  • False-positive risk on supported CPUs: validate::ascii(b"a") can only return false via the unsupported stub; the second regression test guards this on every CI lane.
  • bun_abort_missing_simd uses CRT fprintf/getenv only, so it works before Output and the WTF-8 env block are initialized (including on Windows).
  • Test follows harness conventions (concurrent pipe drain, bunEnv spread, skipIf(isArm64) with a stated reason, stderr asserted before exit code).
Extended reasoning...

Overview

Adds a one-byte simdutf probe at the top of main() (src/bun_bin/lib.rs) that aborts with a clear diagnostic when simdutf's runtime dispatcher has installed its unsupported_implementation stub — the case hit on pre-SSE4.2 x64 CPUs (or QEMU's default TCG vCPU) where the -march=nehalem build has compiled out the scalar fallback. The abort path is a new #[cold] Rust helper calling a new C++ bun_abort_missing_simd in c-bindings.cpp that writes to CRT stderr and exits 134. has_any_implementation() is a 3-line wrapper over the existing validate::ascii FFI in src/simdutf_sys/simdutf.rs. Cargo.toml/Cargo.lock add the bun_simdutf_sys dep to bun_bin. A two-test regression file reproduces the stub via SIMDUTF_FORCE_IMPLEMENTATION and confirms the happy path is unaffected.

Security risks

None. No user input is parsed; the only external read is getenv("SIMDUTF_FORCE_IMPLEMENTATION"), printed via %s (not a format string). No auth, crypto, or permission surfaces touched.

Level of scrutiny

Moderate. The change sits in main(), so a false positive would break every invocation — but the addition is a single validate_ascii(b"a") call whose only false path is the stub itself, and the second regression test (runs normally when a supported simdutf implementation is available) directly covers that on every platform lane. init_argv (called before the probe) was verified to only store atomic pointers with no simdutf dependency. The C++ side deliberately uses CRT primitives so it has no dependency on Output::Source or the converted Windows env block; the Rust extern signature matches the C++ definition ([[noreturn]]-> !, two const char*).

Other factors

This PR has been through seven-plus review iterations; every prior finding (test anti-patterns, stale comment cross-references, description/diff mismatch after the haswell-build removal, comment-cop verbosity flags) has been addressed and the threads resolved. The one candidate issue raised this run — that the SSE4.2 wording is technically imprecise on genuine Nehalem (westmere kernel also needs PCLMULQDQ) — was examined and ruled out as a diagnostic-wording nit that doesn't affect correctness. No CODEOWNERS cover the touched paths. The test file follows repo conventions: test.concurrent, Promise.all pipe drain, {...bunEnv, ...}, skipIf(isArm64) with the single-implementation rationale, and content assertions before exit-code assertions.

…is CPU

Bun's x64 builds are compiled with -march=nehalem, which makes simdutf drop
its scalar fallback on the assumption that the SSE4.2 kernel can always run.
On a host without SSE4.2 (QEMU's default TCG vCPU, very old hardware)
simdutf's dispatcher installs an unsupported_implementation stub whose
functions all return 0/false. Bun and WebKit trust those answers, so valid
files fail UTF-8 validation and the scan loops built on first_non_ascii never
advance: running any script hangs or dies with no useful output.

Probe simdutf once at the top of main() by validating a single ASCII byte
(the stub unconditionally returns false) and, if it is the stub, print which
instruction set is missing and exit 134. The probe runs before the crash
handler and the Windows environment conversion, both of which route strings
through simdutf, so the diagnostic is written via the C runtime rather than
Output. The regression test reaches the same stub by pointing
SIMDUTF_FORCE_IMPLEMENTATION at an implementation name that does not exist.

Fixes #30613
Fixes #14745
@robobun
robobun force-pushed the farm/80eb7318/simdutf-unsupported-cpu-guard branch from ad9b670 to 0cf6ac1 Compare August 15, 2026 19:14

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the descoped diff is small and all prior review threads are resolved.

Checked that has_any_implementation() cannot false-negative on a working CPU (validate_ascii(b"a") is true on every real simdutf kernel; only the unsupported_implementation stub returns false), and the second regression test guards this on every CI lane. Verified the probe's placement before bun_crash_handler::init() and convert_env_to_wtf8() matches the stated ordering constraint, and that bun_abort_missing_simd using CRT getenv/fprintf is correct given Output isn't initialized yet. Test follows harness conventions (bunEnv spread, concurrent pipe drain, skipIf(isArm64) with a stated reason, positive assertions on stderr/exitCode/signalCode).

Extended reasoning...

Overview

Adds a one-shot startup probe in main() (src/bun_bin/lib.rs) that calls bun_simdutf_sys::simdutf::has_any_implementation() — a thin wrapper over the existing validate::ascii FFI — and aborts via a new C++ bun_abort_missing_simd (src/jsc/bindings/c-bindings.cpp) with a diagnostic naming the missing ISA when simdutf has installed its unsupported_implementation stub. Also adds the bun_simdutf_sys dep to bun_bin and a two-test regression file. Total ~+120 lines across 6 files.

Security risks

None. No user input reaches the new code; the probe validates a single hard-coded ASCII byte and the diagnostic prints only compile-time C string literals plus the value of one env var to stderr.

Level of scrutiny

Moderate-to-high, because the probe runs on the cold-start path of every Bun invocation and a false negative would make Bun refuse to start everywhere. That specific risk is what I focused on: validate_ascii("a", 1) returns true on every real simdutf kernel and false only from the stub, and the "runs normally" regression test exercises exactly this on every CI lane. The abort path is #[cold], diverges, and uses only CRT primitives (fprintf/getenv/exit), so it has no dependency on Bun's own initialization order — appropriate given it runs before Output and (on Windows) before the WTF-8 env conversion.

Other factors

This PR has been through five prior review passes from me; every thread is marked resolved. The earlier Rosetta-recovery / baseline-hint / bun_warn_avx_missing pieces were dropped after #34782 unified x64 on -march=nehalem (making them unreachable), and the title/description were rewritten to match. The remaining code comments were trimmed per the comment-cop bot. CI build #98222 passed on all lanes that ran (the two stuck darwin-14 aarch64 shards were a fleet issue unrelated to this diff). Given the small final surface, the explicit negative-and-positive test coverage, and the fully resolved review history, I don't see anything left for a human pass to add.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bun crashes on non-AVX2 CPUs (all versions after v1.3.8) Exit on startup if SSE4.2 is not available

2 participants