fix(wasm): disable Wasm OSR on Linux x64 to prevent crashes

Disables useWasmOSR on Linux x64 to work around a JavaScriptCore bug
that causes crashes (SIGILL/segfault) when calling Emscripten-exported
Wasm functions via direct method calls after many iterations.

The crash occurs specifically with direct method call patterns like
`module._func(arg)` vs working alternatives like `const fn = module._func; fn(arg)`.

This workaround trades some Wasm performance (no on-stack replacement
from interpreter to JIT) for stability. Wasm code still gets JIT-compiled
after enough executions via the normal tiering mechanism.

Fixes #26366
Fixes #26444
Fixes #17841

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-25 15:41:27 +00:00
parent bfe40e8760
commit 1003db2d6c
3 changed files with 77 additions and 4 deletions

View File

@@ -293,6 +293,14 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
JSC::Options::useJIT() = true;
JSC::Options::useBBQJIT() = true;
JSC::Options::useConcurrentJIT() = true;
#if OS(LINUX) && CPU(X86_64)
// Workaround for JSC Wasm OSR bug on Linux x64 that causes crashes
// with repeated direct method calls on Emscripten WASM modules.
// https://github.com/oven-sh/bun/issues/26366
// https://bugs.webkit.org/show_bug.cgi?id=289009
JSC::Options::useWasmOSR() = false;
#endif
// JSC::Options::useSigillCrashAnalyzer() = true;
JSC::Options::useSourceProviderCache() = true;
// JSC::Options::useUnlinkedCodeBlockJettisoning() = false;

View File

@@ -1,11 +1,12 @@
import { PGlite } from "@electric-sql/pglite";
import { isCI, isLinux } from "harness";
describe("pglite", () => {
// TODO(@190n) linux-x64 sometimes fails due to JavaScriptCore bug
// https://github.com/oven-sh/bun/issues/17841#issuecomment-2695792567
// This test previously failed on linux-x64 due to a JavaScriptCore Wasm OSR bug.
// Fixed by disabling useWasmOSR on linux-x64 in ZigGlobalObject.cpp.
// https://github.com/oven-sh/bun/issues/17841
// https://github.com/oven-sh/bun/issues/26366
// https://bugs.webkit.org/show_bug.cgi?id=289009
it.todoIf(isCI && isLinux && process.arch == "x64")("can initialize successfully", async () => {
it("can initialize successfully", async () => {
const db = new PGlite();
expect(await db.query("SELECT version()")).toEqual({
rows: [

View File

@@ -0,0 +1,64 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// Regression test for https://github.com/oven-sh/bun/issues/26366
// Bun crashes with SIGILL/segfault when calling an Emscripten-exported Wasm function
// via direct method call on the module object after many iterations.
// This is caused by a JSC Wasm OSR/JIT bug, fixed by disabling useWasmOSR on Linux x64.
describe("issue #26366: JSC Wasm OSR crash with direct method calls", () => {
// The original bug only manifested on Linux x64, but this test validates
// the Wasm instantiation and direct method call pattern works on all platforms.
test("repeated wasm module instantiation with direct method calls", async () => {
// Create a minimal test that exercises the code path that would crash:
// - Multiple Wasm module instantiations
// - Direct method calls on the module object (module.export())
const script = `
// Minimal Wasm module: exports a function that returns its argument + 1
// Generated with: (module (func (export "add1") (param i32) (result i32) local.get 0 i32.const 1 i32.add))
const wasmBytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // magic + version
0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f, // type section: (i32) -> i32
0x03, 0x02, 0x01, 0x00, // function section
0x07, 0x08, 0x01, 0x04, 0x61, 0x64, 0x64, 0x31, 0x00, 0x00, // export "add1"
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x41, 0x01, 0x6a, 0x0b, // code
]);
// Use higher iterations on Linux x64 where the original crash occurred.
// Allow override via env var for CI stress testing.
const baseIterations = process.platform === 'linux' && process.arch === 'x64' ? 4000 : 500;
const iterations = parseInt(process.env.TEST_ITERATIONS, 10) || baseIterations;
async function runTest() {
for (let i = 0; i < iterations; i++) {
const wasmModule = await WebAssembly.compile(wasmBytes);
const instance = await WebAssembly.instantiate(wasmModule);
// Direct method call - this was the pattern that triggered the crash
const result = instance.exports.add1(i);
if (result !== i + 1) {
throw new Error(\`Unexpected result at iteration \${i}: expected \${i + 1}, got \${result}\`);
}
}
console.log(\`Completed \${iterations} iterations successfully\`);
}
await runTest();
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toMatch(/Completed \d+ iterations successfully/);
expect(stderr).toBe("");
// The key assertion: process should exit normally without crash (SIGILL/SIGSEGV)
expect(exitCode).toBe(0);
});
});