Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/bun_bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ libc.workspace = true
# global allocator, crash handler hooks).
bun_alloc.workspace = true
bun_core.workspace = true
bun_simdutf_sys.workspace = true
bun_sys.workspace = true
bun_crash_handler.workspace = true
bun_mimalloc_sys.workspace = true
Expand Down
33 changes: 33 additions & 0 deletions src/bun_bin/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ pub(crate) unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) ->
// for the entire process.
unsafe { bun_core::init_argv(argc, argv) };

// Everything below this point (crash handler included) calls into simdutf.
if !bun_simdutf_sys::simdutf::has_any_implementation() {
abort_for_unsupported_simdutf();
}
Comment thread
robobun marked this conversation as resolved.

// 1. Crash handler first so anything below gets a usable trace.
bun_crash_handler::init();

Expand Down Expand Up @@ -209,3 +214,31 @@ pub(crate) unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) ->
// `Global::exit` is `-> !`; it coerces to the `c_int` return type.
Global::exit(0)
}

unsafe extern "C" {
fn bun_abort_missing_simd(
requirement: *const core::ffi::c_char,
hint: *const core::ffi::c_char,
) -> !;
}

#[cold]
fn abort_for_unsupported_simdutf() -> ! {
// x64 builds are -march=nehalem, so simdutf's lowest kernel is SSE4.2.
let requirement: &core::ffi::CStr = if cfg!(target_arch = "x86_64") {
c"SSE4.2"
} else if cfg!(target_arch = "aarch64") {
c"NEON"
} else {
c"SIMD"
};

let hint: &core::ffi::CStr = if cfg!(target_arch = "x86_64") {
c" Bun's x64 builds target Nehalem-class (2008+) CPUs.\n If this is a VM, enable host CPU passthrough (e.g. -cpu host for QEMU/KVM).\n"
} else {
c""
};

// SAFETY: both arguments are NUL-terminated static C strings.
unsafe { bun_abort_missing_simd(requirement.as_ptr(), hint.as_ptr()) }
}
12 changes: 12 additions & 0 deletions src/jsc/bindings/c-bindings.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// when we don't want to use @cInclude, we can just stick wrapper functions here
#include "root.h"
#include <cstdio>
#include <cstdlib>

#if !OS(WINDOWS)
#include <wtf/WTFConfig.h>
Expand All @@ -23,6 +24,17 @@
#endif // !OS(WINDOWS)
#include <lshpack.h>

// Runs before Output and the Windows env block are initialized, hence the CRT.
extern "C" [[noreturn]] void bun_abort_missing_simd(const char* requirement, const char* hint)
{
fprintf(stderr, "error: this CPU is missing %s support, which Bun requires for UTF-8 processing.\n%s", requirement, hint);
if (const char* forced = getenv("SIMDUTF_FORCE_IMPLEMENTATION")) {
fprintf(stderr, " note: SIMDUTF_FORCE_IMPLEMENTATION is set to \"%s\"\n", forced);
}
fflush(stderr);
exit(134);
}

// Error condition is encoded as max int32_t.
// The only error in this function is ESRCH (no process found)
extern "C" int32_t get_process_priority(int32_t pid)
Expand Down
6 changes: 6 additions & 0 deletions src/simdutf_sys/simdutf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,9 @@ pub mod base64 {
}
}
}

/// False when no compiled-in kernel runs on this CPU: simdutf's
/// `unsupported_implementation` stub returns false for every query.
Comment thread
robobun marked this conversation as resolved.
pub fn has_any_implementation() -> bool {
validate::ascii(b"a")
}
68 changes: 68 additions & 0 deletions test/regression/issue/30613.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// https://github.com/oven-sh/bun/issues/30613
//
// On CPUs below Bun's x64 baseline (e.g. QEMU's default TCG vCPU, which only
// advertises SSE3), simdutf's runtime dispatcher finds no usable kernel:
// the build is compiled with -march=nehalem, which defines __SSE4_2__ and
// therefore compiles out simdutf's scalar fallback. It then dispatches to an
// `unsupported_implementation` stub whose methods all return 0/false/{OTHER,0}.
//
// Before the fix, Bun trusted those return values: validate_utf8 rejects
// every file (bunfig.toml fails to load with "Invalid UTF-8 byte sequence"),
// and first_non_ascii reports a non-ASCII byte at offset 0 for any input
// longer than its 32-byte scalar fast path, so the scan loops built on it
// never advance and `bun app.js` hangs forever. The original report on
// v1.3.9 hit a slice-length underflow on the same stub and segfaulted after
// ~16 seconds instead.
//
// We simulate that CPU by forcing simdutf onto a nonexistent implementation
// name; simdutf's set_best() treats an unknown name exactly like an
// unsupported CPU and installs the same stub.

import { expect, test } from "bun:test";
import { bunEnv, bunExe, isArm64 } from "harness";

// On arm64 simdutf compiles exactly one kernel (NEON, which is mandatory on
// aarch64), so SIMDUTF_SINGLE_IMPLEMENTATION == 1 and runtime dispatch is
// bypassed entirely: SIMDUTF_FORCE_IMPLEMENTATION is ignored and there is no
// way to reach the unsupported stub from the outside. The startup probe still
// runs there; it just can never fail on real arm64 hardware.
test.concurrent.skipIf(isArm64)(
"fails fast with a clear error when simdutf has no supported implementation",
async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", "console.log('unreachable')"],
env: {
...bunEnv,
// Any name not in simdutf's compiled-in list selects the unsupported
// stub, identical to running on a pre-SSE4.2 host.
SIMDUTF_FORCE_IMPLEMENTATION: "none-for-issue-30613",
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

// The unfixed build either hangs in a first_non_ascii scan loop or fails
// with a bogus "Invalid UTF-8" error depending on what it reads first; the
// fixed build prints a diagnostic and exits cleanly before touching input.
expect(stderr).toContain("Bun requires");
expect(stderr).toContain("SIMDUTF_FORCE_IMPLEMENTATION");
expect(stdout).toBe("");
expect(proc.signalCode).toBeNull();
expect(exitCode).toBe(134);
},
);

test.concurrent("runs normally when a supported simdutf implementation is available", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", "console.log('ok')"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).toBe("");
expect(stdout).toBe("ok\n");
expect(exitCode).toBe(0);
});
Loading