open-standards/scripts/check-wasmtime-pin-selftest.sh
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

107 lines
5.1 KiB
Shell
Executable file

#!/usr/bin/env bash
#
# check-wasmtime-pin-selftest.sh — check-wasmtime-pin.py can fail.
#
# A check that cannot fail is decoration. This builds a scratch tree with one workflow
# that installs the pinned wasmtime and runs it the ways the repository's workflows do,
# asserts that the check passes it, and then breaks it each way the property can break
# and asserts that the check fails, saying why.
set -uo pipefail
here=$(cd "$(dirname "$0")" && pwd)
check="$here/check-wasmtime-pin.py"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
W="$tmp/.github/workflows/w.yml"
mk() {
rm -rf "$tmp/.github"
mkdir -p "$tmp/.github/workflows"
cat > "$W" <<'YML'
jobs:
t:
steps:
# Every run passes -W nan-canonicalization=y -W relaxed-simd-deterministic=y.
- name: Install wasmtime 50.0.1
run: |
v=50.0.1
f="wasmtime-v$v-x86_64-linux.tar.xz"
curl -sSfL -o "$RUNNER_TEMP/$f" \
"https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f"
echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c -
- name: tests under wasmtime
env:
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y
run: cargo test --target wasm32-wasip2
- name: the corpus under wasmtime
run: |
W=$(pwd)
CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir $W::$W" \
cargo test --release --target wasm32-wasip2
wasmtime --version
wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir .::/w \
x.wasm verify /w
wasmtime run \
-W nan-canonicalization=y -W relaxed-simd-deterministic=y x.wasm
cmp a b || { echo "::error::the output differs under wasmtime"; exit 1; }
YML
}
rep() { # rep <old> <new>: replace the first occurrence in the scratch workflow
python3 -I -c 'import sys; p, a, b = sys.argv[1:]; s = open(p).read(); assert a in s, a; open(p, "w").write(s.replace(a, b, 1))' "$W" "$1" "$2"
}
fails=0
expect() { # expect <pass|fail> <what> [text the output must hold]
local want=$1 what=$2 needle=${3:-} out rc
out=$(python3 -I "$check" --root "$tmp" 2>&1); rc=$?
if [ "$want" = pass ] && [ $rc -ne 0 ]; then echo "FAIL $what: the check failed"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi
if [ "$want" = fail ] && [ $rc -eq 0 ]; then echo "FAIL $what: the check passed"; fails=$((fails+1)); return; fi
if [ -n "$needle" ] && ! grep -qF -- "$needle" <<<"$out"; then echo "FAIL $what: the output does not say '$needle'"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi
echo "ok $what"
}
mk; expect pass "the pinned release, run every way with both settings, passes"
mk; rep "v=50.0.1" "v=49.0.2"
expect fail "another version" "downloads wasmtime 49.0.2"
mk; rep '/download/v$v/$f"' '/download/v38.0.3/wasmtime-v38.0.3-x86_64-linux.tar.xz"'; rep "v=50.0.1" ":"
expect fail "another version, named in the URL" "downloads wasmtime 38.0.3"
mk; rep "685466455dd4" "685466455dd5"
expect fail "another digest" "not its pinned digest"
mk; rep '| sha256sum -c -' '> /dev/null'
expect fail "no digest checked" "checks no SHA-256"
mk; rep "x86_64-linux.tar.xz" "aarch64-linux.tar.xz"
expect fail "an archive with no pinned digest" "has no pinned digest here"
mk; rep " steps:" " steps:
- run: cargo install wasmtime-cli --locked"
expect fail "wasmtime built from source" "installs wasmtime some other way"
mk; rep "RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y" "RUNNER: wasmtime"
expect fail "a cargo runner with neither setting" "runs wasmtime without -W nan-canonicalization=y and -W relaxed-simd-deterministic=y"
mk; rep '--dir $W::$W"' '--dir $W::$W -W nan-canonicalization=y"'; rep 'RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' 'RUNNER="wasmtime run'
expect fail "a runner with one setting" "runs wasmtime without -W relaxed-simd-deterministic=y"
mk; rep "-W relaxed-simd-deterministic=y --dir .::/w" "--dir .::/w"
expect fail "a run without deterministic relaxed SIMD" "runs wasmtime without -W relaxed-simd-deterministic=y"
mk; rep " -W nan-canonicalization=y -W relaxed-simd-deterministic=y x.wasm" " x.wasm"
expect fail "a run continued over two lines, with neither setting" "runs wasmtime without"
mk; rep "wasmtime --version" "wasmtime x.wasm"
expect fail "a module given to wasmtime with no subcommand" "runs wasmtime without"
mk; rep "wasmtime --version" "wasmtime serve -W nan-canonicalization=y app.wasm"
expect fail "a serve without deterministic relaxed SIMD" "runs wasmtime without -W relaxed-simd-deterministic=y"
mk; printf 'jobs:\n t:\n steps:\n - run: true\n' > "$W"
expect fail "no workflow that runs wasmtime" "looked in the wrong place"
if [ $fails -ne 0 ]; then echo "check-wasmtime-pin-selftest: $fails failure(s)"; exit 1; fi
echo "check-wasmtime-pin-selftest: the check passes the pinned, flagged runs and fails each other."