From 3feea91087a414ec07ab40404724291bbb625b89 Mon Sep 17 00:00:00 2001 From: SUZUKI Sosuke Date: Fri, 30 Jan 2026 14:12:36 +0900 Subject: [PATCH] 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 --- .buildkite/ci.mjs | 49 ++++++++++ cmake/tools/SetupWebKit.cmake | 3 + scripts/verify-jit-stress-qemu.sh | 148 ++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100755 scripts/verify-jit-stress-qemu.sh diff --git a/.buildkite/ci.mjs b/.buildkite/ci.mjs index 543375a0ad..4e420a9a08 100755 --- a/.buildkite/ci.mjs +++ b/.buildkite/ci.mjs @@ -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..."); diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 9763f3cf38..46444d0789 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -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") diff --git a/scripts/verify-jit-stress-qemu.sh b/scripts/verify-jit-stress-qemu.sh new file mode 100755 index 0000000000..d05421b0bc --- /dev/null +++ b/scripts/verify-jit-stress-qemu.sh @@ -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 --binary " + 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)."