diff --git a/.buildkite/Dockerfile b/.buildkite/Dockerfile index 2b5f944834..7f97965191 100644 --- a/.buildkite/Dockerfile +++ b/.buildkite/Dockerfile @@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget curl git python3 python3-pip ninja-build \ software-properties-common apt-transport-https \ ca-certificates gnupg lsb-release unzip \ - libxml2-dev ruby ruby-dev bison gawk perl make golang ccache \ + libxml2-dev ruby ruby-dev bison gawk perl make golang ccache qemu-user-static \ && add-apt-repository ppa:ubuntu-toolchain-r/test \ && apt-get update \ && apt-get install -y gcc-13 g++-13 libgcc-13-dev libstdc++-13-dev \ diff --git a/.buildkite/ci.mjs b/.buildkite/ci.mjs index 88e879a806..543375a0ad 100755 --- a/.buildkite/ci.mjs +++ b/.buildkite/ci.mjs @@ -537,6 +537,66 @@ function getLinkBunStep(platform, options) { }; } +/** + * Returns the artifact triplet for a platform, e.g. "bun-linux-aarch64" or "bun-linux-x64-musl-baseline". + * Matches the naming convention in cmake/targets/BuildBun.cmake. + * @param {Platform} platform + * @returns {string} + */ +function getTargetTriplet(platform) { + const { os, arch, abi, baseline } = platform; + let triplet = `bun-${os}-${arch}`; + if (abi === "musl") { + triplet += "-musl"; + } + if (baseline) { + triplet += "-baseline"; + } + return triplet; +} + +/** + * Returns true if a platform needs QEMU-based baseline CPU verification. + * x64 baseline builds verify no AVX/AVX2 instructions snuck in. + * aarch64 builds verify no LSE/SVE instructions snuck in. + * @param {Platform} platform + * @returns {boolean} + */ +function needsBaselineVerification(platform) { + const { os, arch, baseline } = platform; + if (os !== "linux") return false; + return (arch === "x64" && baseline) || arch === "aarch64"; +} + +/** + * @param {Platform} platform + * @param {PipelineOptions} options + * @returns {Step} + */ +function getVerifyBaselineStep(platform, options) { + const { arch } = platform; + const targetKey = getTargetKey(platform); + const archArg = arch === "x64" ? "x64" : "aarch64"; + + return { + key: `${targetKey}-verify-baseline`, + label: `${getTargetLabel(platform)} - verify-baseline`, + depends_on: [`${targetKey}-build-bun`], + agents: getLinkBunAgent(platform, options), + retry: getRetry(), + cancel_on_build_failing: isMergeQueue(), + timeout_in_minutes: 5, + command: [ + `buildkite-agent artifact download '*.zip' . --step ${targetKey}-build-bun`, + `unzip -o '${getTargetTriplet(platform)}.zip'`, + `unzip -o '${getTargetTriplet(platform)}-profile.zip'`, + `chmod +x ${getTargetTriplet(platform)}/bun ${getTargetTriplet(platform)}-profile/bun-profile`, + `./scripts/verify-baseline-cpu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}/bun`, + `./scripts/verify-baseline-cpu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}-profile/bun-profile`, + ], + }; +} + /** * @param {Platform} platform * @param {PipelineOptions} options @@ -1126,6 +1186,10 @@ async function getPipeline(options = {}) { steps.push(getBuildZigStep(target, options)); steps.push(getLinkBunStep(target, options)); + if (needsBaselineVerification(target)) { + steps.push(getVerifyBaselineStep(target, options)); + } + return getStepWithDependsOn( { key: getTargetKey(target), diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 5deb20a7c7..43aa7fad7d 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Version: 26 +# Version: 27 # A script that installs the dependencies needed to build and test Bun. # This should work on macOS and Linux with a POSIX shell. @@ -1061,6 +1061,11 @@ install_build_essentials() { go \ xz install_packages apache2-utils + # QEMU user-mode for baseline CPU verification in CI + case "$arch" in + x64) install_packages qemu-x86_64 ;; + aarch64) install_packages qemu-aarch64 ;; + esac ;; esac diff --git a/scripts/verify-baseline-cpu.sh b/scripts/verify-baseline-cpu.sh new file mode 100755 index 0000000000..d86a744f27 --- /dev/null +++ b/scripts/verify-baseline-cpu.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Verify that a Bun binary doesn't use CPU instructions beyond its baseline target. +# Uses QEMU user-mode emulation with restricted CPU features. +# Any illegal instruction (SIGILL) causes exit code 132 and fails the build. +# +# QEMU must be pre-installed in the CI image (see .buildkite/Dockerfile and +# scripts/bootstrap.sh). + +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 + +# Select QEMU binary and CPU model +HOST_ARCH=$(uname -m) +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 + # cortex-a53 is ARMv8.0-A (no LSE atomics, no SVE). It's the most widely + # supported ARMv8.0 model across QEMU versions. + 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") + +echo "--- Verifying $BINARY_NAME on $CPU_DESC" +echo " Binary: $BINARY" +echo " QEMU: $QEMU_BIN -cpu $QEMU_CPU" +echo " Host: $HOST_ARCH" +echo "" + +run_test() { + local label="$1" + shift + echo "+++ $BINARY_NAME: $label" + if "$QEMU_BIN" -cpu "$QEMU_CPU" "$@"; then + echo " PASS" + return 0 + else + local exit_code=$? + echo "" + if [ $exit_code -eq 132 ]; then + echo " FAIL: Illegal instruction (SIGILL)" + echo "" + echo " The $BINARY_NAME binary uses CPU instructions not available on $QEMU_CPU." + if [ "$ARCH" = "x64" ]; then + echo " The baseline x64 build targets Nehalem (SSE4.2)." + echo " AVX, AVX2, and AVX512 instructions are not allowed." + else + echo " The aarch64 build targets Cortex-A53 (ARMv8.0-A+CRC)." + echo " LSE atomics, SVE, and dotprod instructions are not allowed." + fi + else + echo " FAIL: exit code $exit_code" + fi + exit $exit_code + fi +} + +run_test "bun --version" "$BINARY" --version +run_test "bun -e eval" "$BINARY" -e "console.log(JSON.stringify({ok:1+1}))" + +echo "" +echo " All checks passed for $BINARY_NAME on $QEMU_CPU."