Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
10 changes: 7 additions & 3 deletions .buildkite/ci.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@
*/
function hasWebKitChanges(options) {
const { changedFiles = [] } = options;
return changedFiles.some(file => file.includes("SetupWebKit.cmake"));
return changedFiles.some(file => file.includes("scripts/build/deps/webkit.ts") || file.startsWith("vendor/WebKit/"));

Check warning on line 749 in .buildkite/ci.mjs

View check run for this annotation

Claude / Claude Code Review

vendor/WebKit/ clause in hasWebKitChanges is dead code

The `file.startsWith("vendor/WebKit/")` clause can never match: `vendor/` is gitignored (`.gitignore:156`) and WebKit lives there as a separate clone fetched by the build, so the GitHub PR-files API that populates `changedFiles` will never return a path under it. The `scripts/build/deps/webkit.ts` clause (which holds `WEBKIT_VERSION`) is the live trigger, so WebKit bumps still enable `--jit-stress` correctly — but the `vendor/WebKit/` clause is dead and misleading. Consider dropping it, or add a
Comment thread
robobun marked this conversation as resolved.
Outdated
}

/**
Expand All @@ -759,7 +759,11 @@
const targetKey = getTargetKey(platform);
const triplet = getTargetTriplet(platform);
const emulator = getEmulatorBinary(platform);
const jitStressFlag = hasWebKitChanges(options) ? " --jit-stress" : "";
// Windows SDE (Pin) pays ~45s startup per fixture; 80+ serial fixtures would
// take ~1h. qemu-Nehalem on Linux already verifies the same x64-no-AVX JIT
// output at ~1s/fixture, so Windows keeps only the static scan + SIMD test.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const wantJitStress = hasWebKitChanges(options) && os !== "windows";
const jitStressFlag = wantJitStress ? " --jit-stress" : "";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
// Android binaries need /system/bin/linker64 + a bionic sysroot, neither of which exist on the
// build host, so qemu-user cannot load them; only the static instruction scan is meaningful.
const skipEmulationFlag = abi === "android" ? " --skip-emulation" : "";
Expand Down Expand Up @@ -814,7 +818,7 @@
agents,
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
timeout_in_minutes: hasWebKitChanges(options) ? 30 : 10,
timeout_in_minutes: wantJitStress ? 30 : 10,
command: [
...setupCommands,
`cargo build --release --manifest-path scripts/verify-baseline-static/Cargo.toml${os === "windows" ? " || exit /b 1" : ""}`,
Expand Down
40 changes: 34 additions & 6 deletions scripts/verify-baseline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import { readdirSync } from "node:fs";
import { basename, dirname, join, resolve } from "node:path";
import { parseJSCFlags, wasmSIMDFixtures } from "../test/js/bun/jsc-stress/jsc-flags";
// @ts-ignore — utils.mjs has JSDoc types but no .d.ts
import { markBuildkiteStepReported } from "./utils.mjs";

Expand Down Expand Up @@ -92,12 +93,25 @@ console.log();
let instructionFailures = 0;
let otherFailures = 0;
let passed = 0;
let skipped = 0;
const failedTests: string[] = [];

// Mirrors the relevant bits of test/harness.ts's bunEnv so fixtures behave the
// same here as under jsc-stress.test.ts.
const fixtureBaseEnv = {
...process.env,
BUN_DEBUG_QUIET_LOGS: "1",
NO_COLOR: "1",
BUN_GARBAGE_COLLECTOR_LEVEL: process.env.BUN_GARBAGE_COLLECTOR_LEVEL || "0",
BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: "1",
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

interface RunTestOptions {
cwd?: string;
/** Tee output live to the console while still capturing it for analysis */
live?: boolean;
/** Extra environment (e.g. parsed `//@` JSC flags) for the spawned binary */
env?: Record<string, string>;
}

/** Read a stream, write each chunk to a writable, and return the full text. */
Expand All @@ -118,6 +132,7 @@ async function runTest(label: string, binaryArgs: string[], options?: RunTestOpt
const proc = Bun.spawn([...config.runnerCmd, binary, ...binaryArgs], {
// config.cwd takes priority — SDE on Windows must run from its own directory for Pin DLL resolution
cwd: config.cwd ?? options?.cwd,
env: { ...fixtureBaseEnv, ...(options?.env ?? {}) },
stdout: "pipe",
stderr: "pipe",
});
Expand Down Expand Up @@ -241,7 +256,10 @@ if (values["skip-emulation"]) {
console.log(`--- JS fixtures (DFG/FTL) — ${jsFixtures.length} tests`);
for (let i = 0; i < jsFixtures.length; i++) {
const fixture = jsFixtures[i];
await runTest(`[${i + 1}/${jsFixtures.length}] ${fixture}`, ["--preload", preloadPath, join(fixturesDir, fixture)]);
const fixturePath = join(fixturesDir, fixture);
await runTest(`[${i + 1}/${jsFixtures.length}] ${fixture}`, ["--preload", preloadPath, fixturePath], {
env: parseJSCFlags(fixturePath),
});
}

const wasmFixtures = readdirSync(wasmFixturesDir)
Expand All @@ -251,11 +269,20 @@ if (values["skip-emulation"]) {
console.log(`--- Wasm fixtures (BBQ/OMG) — ${wasmFixtures.length} tests`);
for (let i = 0; i < wasmFixtures.length; i++) {
const fixture = wasmFixtures[i];
await runTest(
`[${i + 1}/${wasmFixtures.length}] ${fixture}`,
["--preload", preloadPath, join(wasmFixturesDir, fixture)],
{ cwd: wasmFixturesDir },
);
const fixturePath = join(wasmFixturesDir, fixture);
// Nehalem has no AVX; JSC's recomputeDependentOptions() force-disables
// useWasmSIMD there, so v128-typed modules fail to parse. A real baseline
// CPU never runs the wasm-SIMD JIT path, so there is nothing to verify.
if (!isAarch64 && wasmSIMDFixtures.has(fixture)) {
console.log(`--- [${i + 1}/${wasmFixtures.length}] ${fixture}`);
console.log(" SKIP (uses wasm v128; JSC disables wasm SIMD on x64 without AVX)");
skipped++;
continue;
}
await runTest(`[${i + 1}/${wasmFixtures.length}] ${fixture}`, ["--preload", preloadPath, fixturePath], {
cwd: wasmFixturesDir,
env: parseJSCFlags(fixturePath),
});
}
} else {
console.log();
Expand All @@ -266,6 +293,7 @@ if (values["skip-emulation"]) {
console.log();
console.log("--- Summary");
console.log(` Passed: ${passed}`);
if (skipped) console.log(` Skipped: ${skipped}`);
console.log(` Instruction failures: ${instructionFailures}`);
console.log(` Other failures: ${otherFailures} (not CPU instruction issues)`);
console.log();
Expand Down
94 changes: 94 additions & 0 deletions test/js/bun/jsc-stress/jsc-flags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Covers the shared helper that both jsc-stress.test.ts and
// scripts/verify-baseline.ts use to spawn JSC stress fixtures. Keeps
// verify-baseline's Nehalem skip list in sync with the fixtures: if a new
// wasm fixture starts using v128 and isn't listed, or a listed one stops,
// this test fails.

import { describe, expect, test } from "bun:test";
import { readdirSync } from "fs";
import { bunEnv, bunExe, isDebug } from "harness";
import path from "path";
import { parseJSCFlags, wasmSIMDFixtures } from "./jsc-flags";

const fixturesDir = path.join(import.meta.dir, "fixtures");
const wasmFixturesDir = path.join(fixturesDir, "wasm");
const preloadPath = path.join(import.meta.dir, "preload.js");
// Same headroom jsc-stress.test.ts gives debug builds for JIT tier-up loops.
const fixtureTimeout = isDebug ? 180_000 : undefined;

describe("parseJSCFlags", () => {
test("runDefaultWasm", () => {
expect(parseJSCFlags(path.join(wasmFixturesDir, "bbq-osr-with-exceptions.js"))).toEqual({
BUN_JSC_useDollarVM: "1",
BUN_JSC_jitPolicyScale: "0.1",
});
});

test("runDefault", () => {
expect(parseJSCFlags(path.join(wasmFixturesDir, "omg-tail-call-clobber-scratch-register.js"))).toEqual({
BUN_JSC_jitPolicyScale: "0",
});
});

test("runFTLNoCJIT implies useFTLJIT / !useConcurrentJIT", () => {
expect(parseJSCFlags(path.join(fixturesDir, "licm-no-pre-header.js"))).toEqual({
BUN_JSC_useFTLJIT: "true",
BUN_JSC_useConcurrentJIT: "false",
BUN_JSC_createPreHeaders: "false",
});
});

test("no directive", () => {
expect(parseJSCFlags(path.join(wasmFixturesDir, "ipint-bbq-osr-with-try2.js"))).toEqual({});
});
});

// verify-baseline.ts emulates a Nehalem CPU (no AVX) on x64. JSC's
// recomputeDependentOptions() sets `useWasmSIMD = false` there, which makes
// v128 an invalid type and any module declaring one fail to parse. Assert
// wasmSIMDFixtures lists exactly those fixtures, so the verify-baseline
// skip list stays correct as fixtures are added or changed.
describe.concurrent("wasmSIMDFixtures matches fixtures that require wasm SIMD", () => {
const allWasmFixtures = readdirSync(wasmFixturesDir)
.filter(f => f.endsWith(".js"))
.sort();

test("every listed fixture exists on disk", () => {
const onDisk = new Set(allWasmFixtures);
expect([...wasmSIMDFixtures].filter(f => !onDisk.has(f))).toEqual([]);
});

for (const fixture of allWasmFixtures) {
test(
fixture,
async () => {
const fixturePath = path.join(wasmFixturesDir, fixture);
await using proc = Bun.spawn({
cmd: [bunExe(), "--preload", preloadPath, fixturePath],
// Simulate the Nehalem path: no AVX => JSC disables wasm SIMD.
env: { ...bunEnv, ...parseJSCFlags(fixturePath), BUN_JSC_useWasmSIMD: "false" },
cwd: wasmFixturesDir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

if (wasmSIMDFixtures.has(fixture)) {
// Must fail to parse with the characteristic error; if it passes, it
// no longer needs SIMD and should be removed from wasmSIMDFixtures.
expect(stderr).toContain("WebAssembly.Module doesn't parse");
expect(exitCode).not.toBe(0);
} else {
// Must pass without SIMD; if it fails with a parse error, add it to
// wasmSIMDFixtures so verify-baseline skips it under Nehalem.
if (exitCode !== 0) {
console.log("stdout:", stdout);
console.log("stderr:", stderr);
}
expect(exitCode).toBe(0);
}
},
fixtureTimeout,
);
}
});
53 changes: 53 additions & 0 deletions test/js/bun/jsc-stress/jsc-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Shared between jsc-stress.test.ts and scripts/verify-baseline.ts so both
// spawn fixtures with the same JSC options.

import fs from "fs";

/**
* Parse JSC option flags from //@ directives at the top of a test file.
* Converts --flag=value to BUN_JSC_flag=value environment variables.
*
* Supported directives:
* //@ runDefault("--flag=value", ...)
* //@ runFTLNoCJIT("--flag=value", ...)
* //@ runDefaultWasm("--flag=value", ...)
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
export function parseJSCFlags(filePath: string): Record<string, string> {
const content = fs.readFileSync(filePath, "utf-8");
const env: Record<string, string> = {};

for (const line of content.split("\n")) {
if (line === "// @bun" || line.trim() === "") continue;
if (!line.startsWith("//@")) break;

const match = line.match(/^\/\/@ (runDefault|runFTLNoCJIT|runDefaultWasm)\((.*)\)/);
if (!match) continue;

const [, mode, argsStr] = match;

// runFTLNoCJIT implies these flags (from WebKit's run-jsc-stress-tests)
if (mode === "runFTLNoCJIT") {
env["BUN_JSC_useFTLJIT"] = "true";
env["BUN_JSC_useConcurrentJIT"] = "false";
}

// Parse explicit flags: "--key=value"
const flagPattern = /"--(\w+)=([^"]+)"/g;
let flagMatch;
while ((flagMatch = flagPattern.exec(argsStr)) !== null) {
env[`BUN_JSC_${flagMatch[1]}`] = flagMatch[2];
}
}

return env;
}

/**
* Wasm fixtures whose modules declare v128 (0x7B) types. JSC force-disables
* `useWasmSIMD` on x86_64 without AVX (Options.cpp: `isX86_64() && !isX86_64_AVX()`),
* so under Nehalem emulation these fail to parse with
* `CompileError: WebAssembly.Module doesn't parse ... can't get ... Type`.
* verify-baseline.ts skips them on x64 since a real Nehalem CPU could never
* run the wasm-SIMD JIT path anyway.
*/
export const wasmSIMDFixtures = new Set(["bbq-osr-with-exceptions.js", "omg-tail-call-clobber-scratch-register.js"]);
41 changes: 1 addition & 40 deletions test/js/bun/jsc-stress/jsc-stress.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,11 @@
import { describe, expect, test } from "bun:test";
import fs from "fs";
import { bunEnv, bunExe, isDebug } from "harness";
import path from "path";
import { parseJSCFlags } from "./jsc-flags";

const fixturesDir = path.join(import.meta.dir, "fixtures");
const wasmFixturesDir = path.join(fixturesDir, "wasm");

/**
* Parse JSC option flags from //@ directives at the top of a test file.
* Converts --flag=value to BUN_JSC_flag=value environment variables.
*
* Supported directives:
* //@ runDefault("--flag=value", ...)
* //@ runFTLNoCJIT("--flag=value", ...)
* //@ runDefaultWasm("--flag=value", ...)
*/
function parseJSCFlags(filePath: string): Record<string, string> {
const content = fs.readFileSync(filePath, "utf-8");
const env: Record<string, string> = {};

for (const line of content.split("\n")) {
if (line === "// @bun" || line.trim() === "") continue;
if (!line.startsWith("//@")) break;

const match = line.match(/^\/\/@ (runDefault|runFTLNoCJIT|runDefaultWasm)\((.*)\)/);
if (!match) continue;

const [, mode, argsStr] = match;

// runFTLNoCJIT implies these flags (from WebKit's run-jsc-stress-tests)
if (mode === "runFTLNoCJIT") {
env["BUN_JSC_useFTLJIT"] = "true";
env["BUN_JSC_useConcurrentJIT"] = "false";
}

// Parse explicit flags: "--key=value"
const flagPattern = /"--(\w+)=([^"]+)"/g;
let flagMatch;
while ((flagMatch = flagPattern.exec(argsStr)) !== null) {
env[`BUN_JSC_${flagMatch[1]}`] = flagMatch[2];
}
}

return env;
}

const jsFixtures = [
// FTL - Math intrinsics
"ftl-arithsin.js",
Expand Down
Loading