diff --git a/.github/workflows/wai-quantum-processors.yml b/.github/workflows/wai-quantum-processors.yml new file mode 100644 index 000000000..0a1cdd7ce --- /dev/null +++ b/.github/workflows/wai-quantum-processors.yml @@ -0,0 +1,141 @@ +name: wai-quantum on every processor + +# The crate promises the same bits on every processor. Native legs run elsewhere +# (x86_64 Linux, aarch64 macOS, the arm64 Linux VM, wasm32, wasip2); this +# workflow runs the crate's byte-pinning tests under user-mode QEMU on the +# instruction sets no runner has, the big-endian ones among them: +# +# bit_identity 23 digests across every numeric layer, taken natively and +# confirmed under wasip2; +# frame_reference the noisy-Clifford fixtures written by an independent tool; +# the library the modules that pin exact bytes: the core and its corpora, +# the fabric and its certificates, the strict JSON reader, +# receipts, energy labels and the bill of materials. +# +# These are emulator results: they prove what each target's binary computes +# under QEMU, not what that silicon does. i686 is the exception: it runs +# directly on the x86_64 host in 32-bit mode, so that leg is silicon. +# +# Each leg's job container is that target's cross image (linker, sysroot and +# QEMU, the runner preset in its environment), pinned by digest, capped at +# 6 GiB: an uncapped emulated run once took the 15 GiB host down. The job +# installs the pinned Rust into the runner's persistent cache, builds in a +# target directory of its own there (a build script compiled in one image fails +# in another with an older glibc), and holds a host-wide lock, so one leg runs +# at a time across every run of this workflow. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + paths: + - 'wai/wai-quantum/**' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-processors.yml' + pull_request: + paths: + - 'wai/wai-quantum/**' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-processors.yml' + +jobs: + emulated: + name: emulated ยท ${{ matrix.target }} + runs-on: linux + timeout-minutes: 120 + container: + image: ${{ matrix.image }} + options: --memory=6g --memory-swap=6g ${{ matrix.options }} + strategy: + fail-fast: false + max-parallel: 1 + matrix: + include: + - target: s390x-unknown-linux-gnu + image: ghcr.io/cross-rs/s390x-unknown-linux-gnu@sha256:0d8edc92c39158abe211fddfc9617bf5b7865c2ae0f58873f9cf0c58bb315271 + how: emulated, qemu-user + - target: sparc64-unknown-linux-gnu + image: ghcr.io/cross-rs/sparc64-unknown-linux-gnu@sha256:2d04e507ee8bbb1ba61d483d22cf5839a0f138678f71facb40a08a9dffecf316 + how: emulated, qemu-user + - target: powerpc64-unknown-linux-gnu + image: ghcr.io/cross-rs/powerpc64-unknown-linux-gnu@sha256:db02ecb2429912a6e939cc3d5cfe00aa20d7423ff484980f1fbfee4a3f2683cf + how: emulated, qemu-user + - target: powerpc64le-unknown-linux-gnu + image: ghcr.io/cross-rs/powerpc64le-unknown-linux-gnu@sha256:de5114adcd786f6a41eb07379459aaae94de73670303e202ea3603d250c19091 + how: emulated, qemu-user + - target: riscv64gc-unknown-linux-gnu + image: ghcr.io/cross-rs/riscv64gc-unknown-linux-gnu@sha256:67ee97dd4719390d299edfcf39f226360ccfb1539e08b078ec187d4848415794 + how: emulated, qemu-user + # QEMU 5.1's 32-bit ARM guest grows the emulator's own memory with the + # guest's thread count: with all 20 host CPUs, bit_identity's threaded + # state-vector run took qemu-arm past 6 GiB (a 32-bit guest addresses at + # most 4 GiB, so the excess is the emulator's). Four CPUs keep it under + # 1 GiB and still take the threaded paths. + - target: armv7-unknown-linux-gnueabihf + image: ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf@sha256:4a08bebb60f08d52c1885b643a768f163c7318c05f91cf119f6c02855ea52699 + options: --cpuset-cpus=0-3 + how: emulated, qemu-user + # Runs directly on the x86_64 host in 32-bit mode. + - target: i686-unknown-linux-gnu + image: ghcr.io/cross-rs/i686-unknown-linux-gnu@sha256:2647400f884dd399a3ce6f6d918fec0776d43e73f471f52e310cbe664ac1671f + how: silicon, x86_64 host in 32-bit mode + - target: aarch64-unknown-linux-gnu + image: ghcr.io/cross-rs/aarch64-unknown-linux-gnu@sha256:7f8308a8734d9fcd2ebbe9a3e4bdea74af293f0799d80c3cc341e340cda49a4c + how: emulated, qemu-user + # cross 0.2.5 has no LoongArch image; this is cross's development image. + - target: loongarch64-unknown-linux-gnu + image: ghcr.io/cross-rs/loongarch64-unknown-linux-gnu@sha256:8968bd3b2c3b7b6d73c48e10faa605e14b40ce0080579a06402ea9fdfbc2a5c0 + how: emulated, qemu-user + steps: + - name: Fetch the source + shell: bash + run: | + set -euo pipefail + want="${{ github.event.pull_request.head.sha || github.sha }}" + # The commit itself, not the ref: a later commit on the ref that this + # workflow's paths do not match would otherwise be tested instead. The + # archive carries its commit id, checked before anything is built. + rm -rf src src.tar.gz src.tar && mkdir src + curl -sSf -o src.tar.gz "http://100.64.224.0:3400/Transaction-Science/open-standards/archive/$want.tar.gz" + # Decompressed to a file, not a pipe: get-tar-commit-id stops reading at the + # header, and the writer's broken pipe would fail the step under pipefail. + gzip -dc src.tar.gz > src.tar + got="$(git get-tar-commit-id < src.tar)" + [ "$got" = "$want" ] || { echo "::error::the archive is of $got, not $want"; exit 1; } + tar -xf src.tar -C src --strip-components=1 + rm src.tar.gz src.tar + - name: The byte-pinning tests + shell: bash + working-directory: src/wai/wai-quantum + run: | + set -euo pipefail + t="${{ matrix.target }}" + cache=/ci-cache/wai-quantum-processors + mkdir -p "$cache" + # One leg at a time on this host, across every run of this workflow: the + # emulated tests are heavy, and the toolchain cache is shared. + exec 9>"$cache/.lock" + flock 9 + export RUSTUP_HOME="$cache/rustup" CARGO_HOME="$cache/cargo" + export PATH="$CARGO_HOME/bin:$PATH" + if ! command -v rustup >/dev/null; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain none + fi + rustup toolchain install + rustup target add "$t" + export CARGO_TARGET_DIR="$cache/target/$t" + logs="$(mktemp -d)" + run() { + local name="$1"; shift + timeout 2700 cargo test --locked --release -j 6 --target "$t" --features full "$@" 2>&1 | tee "$logs/$name.log" + } + run bit_identity --test bit_identity + grep -qE '^test result: ok\. 23 passed; 0 failed' "$logs/bit_identity.log" || { echo "::error::$t: the 23 bit-identity digests did not all pass"; exit 1; } + run frame --test frame_reference + grep -qE '^test result: ok\. 2 passed; 0 failed' "$logs/frame.log" || { echo "::error::$t: the fixtures did not pass"; exit 1; } + run lib --lib -- quantum:: quantum_fabric:: ijson:: quantum_receipt quantum_energy quantum_qbom merkle + grep -qE '^test result: ok\. [1-9][0-9]+ passed; 0 failed' "$logs/lib.log" || { echo "::error::$t: the byte-pinning library tests did not pass"; exit 1; } + echo "processor: $t (${{ matrix.how }}) bit_identity 23/23, fixtures 2/2, $(grep -E '^test result' "$logs/lib.log" | tail -1)"