open-standards/.github/workflows/wai-quantum-lang.yml
Transaction Science 888cc3ba20 CI: a check holds every workflow to the one pinned wasmtime and both its settings
The install steps' comment said every WAI workflow that runs wasmtime pins one
version and passes both determinism settings, but nothing enforced it: a step
that ran a stock or differently pinned wasmtime would have merged green.
scripts/check-wasmtime-pin.py now fails, in the repository checks, any workflow
that

1. downloads a wasmtime release other than 50.0.1, or checks it against any
   SHA-256 but that archive's;
2. installs wasmtime another way (cargo install, the install script, an action);
3. runs wasmtime (run, serve, compile, or a module given to it) or names it as
   a CARGO_TARGET_*_RUNNER without -W nan-canonicalization=y and
   -W relaxed-simd-deterministic=y.

It also fails when it finds no download or no run, so a check that looked in
the wrong place cannot pass. On this tree it finds the downloads and runs it
should. Its self-test (scripts/check-wasmtime-pin-selftest.sh) passes a scratch
workflow shaped like these and fails each way of breaking it that the
self-test lists, matching the reason in the output.

The comment's relaxed_madd claim was measured on arm64 only. It now rests on
both architectures, measured on an arm64 macOS host and an x86_64 Linux host
with a small probe (wasmtime 50.0.1's release for the host, checked against
the digest the release lists):

    host Darwin arm64; wasmtime 50.0.1 (c307ec81d 2026-10-29)
    f32.add(bits 0x7fa00001, 1.0)                     [no flags]: 0x7fe00001
    f32x4.relaxed_madd(1+2^-12, 1+2^-12, -(1+2^-11)) [no flags]: 0x33800000
    f32.add(bits 0x7fa00001, 1.0)                     [both flags]: 0x7fc00000
    f32x4.relaxed_madd(1+2^-12, 1+2^-12, -(1+2^-11)) [both flags]: 0x33800000
    an unknown -W option is refused: error: failed to parse -W / --wasm option:
      unknown option: no-such-option

    host Linux x86_64; wasmtime 50.0.1 (c307ec81d 2026-10-29)
    (the same five lines, bit for bit)

0x33800000 is 2^-24, the fused result; the unfused fadd(fmul) gives 0. So the
NaN setting takes effect on both, and relaxed_madd is fused on both with and
without the relaxed setting. That matches 50.0.1's source (the
wasmtime-internal-cranelift crate, read from the registry): the translator
lowers F32x4/F64x2RelaxedMadd through fma_f32x4/fma_f64x2
(src/translate/code_translator.rs L2530-2539), which emit a native fma on a
host that has one, a fused library call under relaxed-simd-deterministic on a
host that has none, and the unfused fmul and fadd only on a host with no FMA
and the setting off (src/func_environ.rs L5907-5925). The setting is what
makes the result fused on every host. And since an unknown -W option stops
wasmtime, a run that exits 0 with the two flags has parsed them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:28:47 -05:00

155 lines
7.2 KiB
YAML

name: wai-quantum OpenQASM reader
# The OpenQASM reader every surface uses (wai/wai-quantum/src/qasm.rs and
# src/qasm/): its unit tests (the lexer, the parser, the elaborator, the
# diagnostic table and the page that documents it, every modifier against the
# matrix it must equal, every condition compiled to steps against every value
# of its bits), and the OpenQASM 3 conformance corpus
# (wai/quantum-conformance/qasm3/: the specification's example programs and a
# program for each construct, each with what the reader must do with it).
#
# The corpus runs natively, where a second test holds its bundle equal to its
# files, and under wasm32-wasip2 by wasmtime, where the cases come from the
# bundle the test compiles in. The browser build runs the corpus too
# (wai-web-wasm.yml, wai-web's wasm32 tests). The reader is also built without
# the lowerings (features quantum_lang and quantum_toolchain), as the edge
# component builds it.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths:
- 'wai/wai-quantum/**'
- 'wai/quantum-conformance/qasm3/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-lang.yml'
pull_request:
paths:
- 'wai/wai-quantum/**'
- 'wai/quantum-conformance/qasm3/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-lang.yml'
jobs:
relevant:
name: does this change touch it
runs-on: linux
outputs:
run: ${{ steps.gate.outputs.run }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: gate
run: |
set -euo pipefail
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-lang.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches the OpenQASM reader: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
native:
name: reader and corpus · linux
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# A module filter is satisfied by any one test of the module, so each
# module's tests are required by name to have run.
- name: The reader's unit tests
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --release --features full --lib qasm 2>&1 | tee "$RUNNER_TEMP/qasm.log"
grep -qE '^test result: ok\. [1-9][0-9]+ passed; 0 failed' "$RUNNER_TEMP/qasm.log" || { echo "::error::the reader's tests did not pass"; exit 1; }
for m in qasm::diag qasm::lex qasm::parse qasm::value qasm::lang_tests qasm::tests quantum_toolchain::tests::qasm_round_trip; do
grep -q "^test $m.* \.\.\. ok$" "$RUNNER_TEMP/qasm.log" || { echo "::error::no test of $m ran"; exit 1; }
done
- name: The conformance corpus
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --release --features full --test qasm3_conformance 2>&1 | tee "$RUNNER_TEMP/corpus.log"
grep -q 'test result: ok. 2 passed; 0 failed' "$RUNNER_TEMP/corpus.log" || { echo "::error::the corpus did not pass"; exit 1; }
- name: The reader without the lowerings
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo check --locked --no-default-features --features quantum_lang --lib
cargo check --locked --no-default-features --features quantum_toolchain --lib
wasip2:
name: corpus under wasm32-wasip2 (wasmtime)
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0 with the wasm32-wasip2 target
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
rustup target add --toolchain 1.98.0 wasm32-wasip2
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# The release binary, pinned by version and SHA-256, rather than a build from source.
# Every WAI workflow that runs wasmtime pins this one version, and every run passes
# -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float
# instruction makes is the canonical one, and each relaxed SIMD instruction has one
# behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed
# behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and
# x86_64), where WebAssembly's deterministic profile prescribes the unfused result.
# The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another
# release or runs wasmtime without both settings.
- name: Install wasmtime 50.0.1
shell: bash
run: |
set -euo pipefail
[ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; }
if ! command -v xz >/dev/null 2>&1; then
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq xz-utils
fi
v=50.0.1
f="wasmtime-v$v-x86_64-linux.tar.xz"
curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \
"https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f"
echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c -
tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH"
# A debug build, so integer overflow panics instead of wrapping. The test
# that reads the corpus's files has no wasip2 build; the one that runs the
# cases from the compiled-in bundle does.
- name: The conformance corpus under wasmtime
shell: bash
working-directory: wai/wai-quantum
env:
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y
run: |
set -euo pipefail
cargo test --locked --target wasm32-wasip2 --features full --test qasm3_conformance 2>&1 | tee "$RUNNER_TEMP/wasip2.log"
grep -q 'test result: ok. 1 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the corpus did not pass under wasip2"; exit 1; }