mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +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 {Platform} platform
|
||||||
* @param {PipelineOptions} options
|
* @param {PipelineOptions} options
|
||||||
@@ -834,6 +877,7 @@ function getBenchmarkStep() {
|
|||||||
* @property {Platform[]} [buildPlatforms]
|
* @property {Platform[]} [buildPlatforms]
|
||||||
* @property {Platform[]} [testPlatforms]
|
* @property {Platform[]} [testPlatforms]
|
||||||
* @property {string[]} [testFiles]
|
* @property {string[]} [testFiles]
|
||||||
|
* @property {string[]} [changedFiles]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1188,6 +1232,10 @@ async function getPipeline(options = {}) {
|
|||||||
|
|
||||||
if (needsBaselineVerification(target)) {
|
if (needsBaselineVerification(target)) {
|
||||||
steps.push(getVerifyBaselineStep(target, options));
|
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(
|
return getStepWithDependsOn(
|
||||||
@@ -1287,6 +1335,7 @@ async function main() {
|
|||||||
console.log(`- PR is only docs, skipping tests!`);
|
console.log(`- PR is only docs, skipping tests!`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
options.changedFiles = allFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
startGroup("Generating pipeline...");
|
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_VERSION "The version of WebKit to use")
|
||||||
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
|
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