#!/usr/bin/env bash # The same Mandelbrot frame from the native kernel and the wasm kernel, # compared byte for byte. # # The point is not that floating point is deterministic in the abstract — it # is that these two builds go through different compilers, different libm # implementations (glibc vs emscripten's musl) and different word sizes # (64-bit vs wasm32), and still have to agree, or "the same Nim code everywhere" # is a slogan rather than a fact. set -euo pipefail cd "$(dirname "$0")/.." [ -f build/libnimflutter.so ] || { echo "run ./tool/build.sh first"; exit 1; } [ -f example/web/nimflutter.js ] || { echo "run ./tool/build_wasm.sh first"; exit 1; } work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT cat > "$work/native_frame.c" <<'EOF' #include "nimflutter.h" #include #include int main(int argc, char **argv) { (void)argc; const int W = 240, H = 180, ITER = 300; size_t n = (size_t)W * H * 4; unsigned char *b = calloc(n, 1); nf_init(); nf_mandelbrot(b, n, W, H, -0.5, 0.0, 3.0, ITER, 0, H); FILE *f = fopen(argv[1], "wb"); fwrite(b, 1, n, f); fclose(f); return 0; } EOF cc -O2 -I src -o "$work/native_frame" "$work/native_frame.c" -L build -lnimflutter LD_LIBRARY_PATH=build "$work/native_frame" "$work/native.bin" node tool/test_wasm.mjs "$work/wasm.bin" > /dev/null python3 - "$work/native.bin" "$work/wasm.bin" <<'EOF' import sys a = open(sys.argv[1], 'rb').read() b = open(sys.argv[2], 'rb').read() if len(a) != len(b): sys.exit(f"FAIL size {len(a)} != {len(b)}") bad = [(i, x, y) for i, (x, y) in enumerate(zip(a, b)) if x != y] if bad: worst = max(abs(x - y) for _, x, y in bad) print(f"FAIL {len(bad)} of {len(a)} bytes differ (max delta {worst})") for i, x, y in bad[:5]: print(f" byte {i} (px {i//4}, ch {i%4}): native={x} wasm={y}") sys.exit(1) print(f"ok native and wasm kernels agree on all {len(a)} bytes") EOF