mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 06:58:54 +00:00
ci: add QEMU JIT stress tests when WebKit is updated (#26589)
## Summary Add a CI step that runs JSC JIT stress tests under QEMU when `SetupWebKit.cmake` is modified. This complements #26571 (basic baseline CPU verification) by also testing JIT-generated code. ## Motivation PR #26571 added QEMU-based verification that catches illegal instructions in: - Startup code - Static initialization - Basic interpreter execution However, JIT compilers (DFG, FTL, Wasm BBQ/OMG) generate code at runtime that could emit AVX or LSE instructions even if the compiled binary doesn't. The JSC stress tests from #26380 exercise all JIT tiers through hot loops that trigger tier-up. ## How it works 1. Detects if `cmake/tools/SetupWebKit.cmake` is modified in the PR 2. If WebKit changes are detected, runs `verify-jit-stress-qemu.sh` after the build 3. Executes all 78 JIT stress test fixtures under QEMU with restricted CPU features: - x64: `qemu-x86_64 -cpu Nehalem` (SSE4.2, no AVX) - aarch64: `qemu-aarch64 -cpu cortex-a53` (ARMv8.0-A, no LSE) 4. Any SIGILL from JIT-generated code fails the build ## Platforms tested | Target | CPU Model | What it catches | |---|---|---| | `linux-x64-baseline` | Nehalem | JIT emitting AVX/AVX2/AVX512 | | `linux-x64-musl-baseline` | Nehalem | JIT emitting AVX/AVX2/AVX512 | | `linux-aarch64` | Cortex-A53 | JIT emitting LSE atomics, SVE | | `linux-aarch64-musl` | Cortex-A53 | JIT emitting LSE atomics, SVE | ## Timeout The step has a 30-minute timeout since QEMU emulation is ~10-50x slower than native. This only runs on WebKit update PRs, so it won't affect most CI runs. ## Refs - #26380 - Added JSC JIT stress tests - #26571 - Added basic QEMU baseline verification
This commit is contained in:
@@ -597,6 +597,49 @@ function getVerifyBaselineStep(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the PR modifies SetupWebKit.cmake (WebKit version changes).
|
||||
* JIT stress tests under QEMU should run when WebKit is updated to catch
|
||||
* JIT-generated code that uses unsupported CPU instructions.
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasWebKitChanges(options) {
|
||||
const { changedFiles = [] } = options;
|
||||
return changedFiles.some(file => file.includes("SetupWebKit.cmake"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a step that runs JSC JIT stress tests under QEMU.
|
||||
* This verifies that JIT-compiled code doesn't use CPU instructions
|
||||
* beyond the baseline target (no AVX on x64, no LSE on aarch64).
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {Step}
|
||||
*/
|
||||
function getJitStressTestStep(platform, options) {
|
||||
const { arch } = platform;
|
||||
const targetKey = getTargetKey(platform);
|
||||
const archArg = arch === "x64" ? "x64" : "aarch64";
|
||||
|
||||
return {
|
||||
key: `${targetKey}-jit-stress-qemu`,
|
||||
label: `${getTargetLabel(platform)} - jit-stress-qemu`,
|
||||
depends_on: [`${targetKey}-build-bun`],
|
||||
agents: getLinkBunAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
// JIT stress tests are slow under QEMU emulation
|
||||
timeout_in_minutes: 30,
|
||||
command: [
|
||||
`buildkite-agent artifact download '*.zip' . --step ${targetKey}-build-bun`,
|
||||
`unzip -o '${getTargetTriplet(platform)}.zip'`,
|
||||
`chmod +x ${getTargetTriplet(platform)}/bun`,
|
||||
`./scripts/verify-jit-stress-qemu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}/bun`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
@@ -834,6 +877,7 @@ function getBenchmarkStep() {
|
||||
* @property {Platform[]} [buildPlatforms]
|
||||
* @property {Platform[]} [testPlatforms]
|
||||
* @property {string[]} [testFiles]
|
||||
* @property {string[]} [changedFiles]
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -1188,6 +1232,10 @@ async function getPipeline(options = {}) {
|
||||
|
||||
if (needsBaselineVerification(target)) {
|
||||
steps.push(getVerifyBaselineStep(target, options));
|
||||
// Run JIT stress tests under QEMU when WebKit is updated
|
||||
if (hasWebKitChanges(options)) {
|
||||
steps.push(getJitStressTestStep(target, options));
|
||||
}
|
||||
}
|
||||
|
||||
return getStepWithDependsOn(
|
||||
@@ -1287,6 +1335,7 @@ async function main() {
|
||||
console.log(`- PR is only docs, skipping tests!`);
|
||||
return;
|
||||
}
|
||||
options.changedFiles = allFiles;
|
||||
}
|
||||
|
||||
startGroup("Generating pipeline...");
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# NOTE: Changes to this file trigger QEMU JIT stress tests in CI.
|
||||
# See scripts/verify-jit-stress-qemu.sh for details.
|
||||
|
||||
option(WEBKIT_VERSION "The version of WebKit to use")
|
||||
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
|
||||
|
||||
|
||||
148
scripts/verify-jit-stress-qemu.sh
Executable file
148
scripts/verify-jit-stress-qemu.sh
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run JSC JIT stress tests under QEMU to verify that JIT-compiled code
|
||||
# doesn't use CPU instructions beyond the baseline target.
|
||||
#
|
||||
# This script exercises all JIT tiers (DFG, FTL, Wasm BBQ/OMG) and catches
|
||||
# cases where JIT-generated code emits AVX instructions on x64 or LSE
|
||||
# atomics on aarch64.
|
||||
#
|
||||
# See: test/js/bun/jsc-stress/ for the test fixtures.
|
||||
|
||||
ARCH=""
|
||||
BINARY=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--arch) ARCH="$2"; shift 2 ;;
|
||||
--binary) BINARY="$2"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$ARCH" ] || [ -z "$BINARY" ]; then
|
||||
echo "Usage: $0 --arch <x64|aarch64> --binary <path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "ERROR: Binary not found: $BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Convert to absolute path for use after pushd
|
||||
BINARY="$(cd "$(dirname "$BINARY")" && pwd)/$(basename "$BINARY")"
|
||||
|
||||
# Select QEMU binary and CPU model
|
||||
if [ "$ARCH" = "x64" ]; then
|
||||
QEMU_BIN="qemu-x86_64"
|
||||
if [ -f "/usr/bin/qemu-x86_64-static" ]; then
|
||||
QEMU_BIN="qemu-x86_64-static"
|
||||
fi
|
||||
QEMU_CPU="Nehalem"
|
||||
CPU_DESC="Nehalem (SSE4.2, no AVX/AVX2/AVX512)"
|
||||
elif [ "$ARCH" = "aarch64" ]; then
|
||||
QEMU_BIN="qemu-aarch64"
|
||||
if [ -f "/usr/bin/qemu-aarch64-static" ]; then
|
||||
QEMU_BIN="qemu-aarch64-static"
|
||||
fi
|
||||
QEMU_CPU="cortex-a53"
|
||||
CPU_DESC="Cortex-A53 (ARMv8.0-A+CRC, no LSE/SVE)"
|
||||
else
|
||||
echo "ERROR: Unknown arch: $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v "$QEMU_BIN" &>/dev/null; then
|
||||
echo "ERROR: $QEMU_BIN not found. It must be pre-installed in the CI image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BINARY_NAME=$(basename "$BINARY")
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
FIXTURES_DIR="$REPO_ROOT/test/js/bun/jsc-stress/fixtures"
|
||||
WASM_FIXTURES_DIR="$FIXTURES_DIR/wasm"
|
||||
PRELOAD_PATH="$REPO_ROOT/test/js/bun/jsc-stress/preload.js"
|
||||
|
||||
echo "--- Running JSC JIT stress tests on $CPU_DESC"
|
||||
echo " Binary: $BINARY"
|
||||
echo " QEMU: $QEMU_BIN -cpu $QEMU_CPU"
|
||||
echo ""
|
||||
|
||||
SIGILL_FAILURES=0
|
||||
OTHER_FAILURES=0
|
||||
PASSED=0
|
||||
|
||||
run_fixture() {
|
||||
local fixture="$1"
|
||||
local fixture_name
|
||||
fixture_name=$(basename "$fixture")
|
||||
|
||||
echo "+++ $fixture_name"
|
||||
if "$QEMU_BIN" -cpu "$QEMU_CPU" "$BINARY" --preload "$PRELOAD_PATH" "$fixture" 2>&1; then
|
||||
echo " PASS"
|
||||
((PASSED++))
|
||||
return 0
|
||||
else
|
||||
local exit_code=$?
|
||||
if [ $exit_code -eq 132 ]; then
|
||||
echo " FAIL: Illegal instruction (SIGILL)"
|
||||
echo ""
|
||||
echo " JIT-compiled code in $fixture_name uses CPU instructions not available on $QEMU_CPU."
|
||||
if [ "$ARCH" = "x64" ]; then
|
||||
echo " The baseline x64 build targets Nehalem (SSE4.2)."
|
||||
echo " JIT must not emit AVX, AVX2, or AVX512 instructions."
|
||||
else
|
||||
echo " The aarch64 build targets Cortex-A53 (ARMv8.0-A+CRC)."
|
||||
echo " JIT must not emit LSE atomics, SVE, or dotprod instructions."
|
||||
fi
|
||||
((SIGILL_FAILURES++))
|
||||
else
|
||||
# Non-SIGILL failures are warnings (test issues, not CPU instruction issues)
|
||||
echo " WARN: exit code $exit_code (not a CPU instruction issue)"
|
||||
((OTHER_FAILURES++))
|
||||
fi
|
||||
return $exit_code
|
||||
fi
|
||||
}
|
||||
|
||||
# Run JS fixtures (DFG/FTL)
|
||||
echo "--- JS fixtures (DFG/FTL)"
|
||||
for fixture in "$FIXTURES_DIR"/*.js; do
|
||||
if [ -f "$fixture" ]; then
|
||||
run_fixture "$fixture" || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Run Wasm fixtures (BBQ/OMG)
|
||||
echo "--- Wasm fixtures (BBQ/OMG)"
|
||||
for fixture in "$WASM_FIXTURES_DIR"/*.js; do
|
||||
if [ -f "$fixture" ]; then
|
||||
# Wasm tests need to run from the wasm fixtures directory
|
||||
# because they reference .wasm files relative to the script
|
||||
pushd "$WASM_FIXTURES_DIR" > /dev/null
|
||||
run_fixture "$fixture" || true
|
||||
popd > /dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- Summary"
|
||||
echo " Passed: $PASSED"
|
||||
echo " SIGILL failures: $SIGILL_FAILURES"
|
||||
echo " Other failures: $OTHER_FAILURES (warnings, not CPU instruction issues)"
|
||||
echo ""
|
||||
|
||||
if [ $SIGILL_FAILURES -gt 0 ]; then
|
||||
echo " FAILED: JIT-generated code uses unsupported CPU instructions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $OTHER_FAILURES -gt 0 ]; then
|
||||
echo " Some tests failed for reasons unrelated to CPU instructions."
|
||||
echo " These are warnings and do not indicate JIT instruction issues."
|
||||
fi
|
||||
|
||||
echo " All JIT stress tests passed on $QEMU_CPU (no SIGILL)."
|
||||
Reference in New Issue
Block a user