1 #!/bin/bash
2 # Validate the wasm target against native.
3 #
4 # Every program with a main() is compiled twice - once natively and once for
5 # -target wasm - then both are run and their stdout compared. Native is the
6 # reference: the wasm module must produce identical output in a real browser.
7 # This is the evidence that the wasm target is correct, not merely that it
8 # links.
9 set -uo pipefail
10 11 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
12 MOXIE="${MOXIE:-$ROOT/moxie}"
13 WORK="${WORK:-$ROOT/tests/wasm/tmp}"
14 PASS=0
15 FAIL=0
16 17 # Programs to validate: each is a directory or .mx file containing a main().
18 TARGETS=()
19 if [ $# -gt 0 ]; then
20 TARGETS=("$@")
21 else
22 for f in "$ROOT"/tests/regressions/should_compile/*.mx; do
23 [ -f "$f" ] || continue
24 grep -q 'func main' "$f" && TARGETS+=("$f")
25 done
26 # Directory cases: main.mx plus a lib/ subpackage. A cross-package call is
27 # not inlined and crosses the return codec, so whole classes of layout bug
28 # are only reachable this way.
29 for d in "$ROOT"/tests/regressions/should_compile/*/; do
30 [ -f "$d/main.mx" ] || continue
31 TARGETS+=("${d%/}")
32 done
33 fi
34 35 rm -rf "$WORK"; mkdir -p "$WORK"
36 37 # Start from an empty package cache: each run measures a cold compile, not
38 # whatever an earlier run happened to cache. The cross-target corruption this
39 # used to also guard against ("Cannot select: intrinsic %llvm.wasm.memory.size"
40 # from a native build linking a wasm object) is fixed at the source: a compiler
41 # process now owns a private scratch directory (buildScratchDir), so concurrent
42 # builds cannot overwrite each other's IR between clang writing it and the read
43 # that caches it, and each target has its own cache root.
44 rm -rf "$HOME/moxie/cache" 2>/dev/null
45 if [ -n "${MOXIEPATH:-}" ]; then
46 rm -rf "$MOXIEPATH/cache" 2>/dev/null
47 fi
48 49 for t in "${TARGETS[@]}"; do
50 name="$(basename "$t")"; name="${name%.mx}"
51 d="$WORK/$name"
52 mkdir -p "$d/pkg"
53 if [ -f "$t" ]; then
54 cp "$t" "$d/pkg/main.mx"
55 else
56 cp "$t"/*.mx "$d/pkg/" 2>/dev/null
57 fi
58 src="$d/pkg"
59 60 # native reference
61 if ! (cd "$ROOT" && MOXIEROOT="$ROOT" "$MOXIE" build -o "$d/native" "$src") >"$d/native.log" 2>&1; then
62 printf '%-32s SKIP (native build failed)\n' "$name"
63 continue
64 fi
65 "$d/native" >"$d/native.out" 2>&1
66 nrc=$?
67 68 # wasm module
69 if ! (cd "$ROOT" && MOXIEROOT="$ROOT" "$MOXIE" build -target wasm -o "$d/app.wasm" "$src") >"$d/wasm.log" 2>&1; then
70 printf '%-32s FAIL (wasm build failed)\n' "$name"
71 tail -3 "$d/wasm.log" | sed 's/^/ /'
72 FAIL=$((FAIL+1))
73 continue
74 fi
75 76 # Run in a browser. A module that spins never yields, so the driver cannot
77 # deliver run.py's own wait_for_function timeout and the call blocks
78 # forever: bound each run here, or one bad module stalls the whole suite
79 # and hides every later result. RUN_TIMEOUT is seconds.
80 timeout "${RUN_TIMEOUT:-60}" python3 "$ROOT/tests/wasm/run.py" -q "$d/app.wasm" >"$d/wasm.out" 2>&1
81 rrc=$?
82 if [ "$rrc" -ne 0 ]; then
83 if [ "$rrc" -eq 124 ]; then
84 printf '%-32s FAIL (browser run timed out after %ss)\n' "$name" "${RUN_TIMEOUT:-60}"
85 else
86 printf '%-32s FAIL (browser run failed)\n' "$name"
87 fi
88 tail -3 "$d/wasm.out" | sed 's/^/ /'
89 FAIL=$((FAIL+1))
90 continue
91 fi
92 cp "$d/wasm.out" "$d/wasm.clean"
93 94 if [ "$nrc" != "0" ]; then
95 printf '%-32s SKIP (native exited %s)\n' "$name" "$nrc"
96 continue
97 fi
98 if diff -q "$d/native.out" "$d/wasm.clean" >/dev/null; then
99 printf '%-32s PASS\n' "$name"
100 PASS=$((PASS+1))
101 else
102 printf '%-32s FAIL (output differs)\n' "$name"
103 diff "$d/native.out" "$d/wasm.clean" | head -6 | sed 's/^/ /'
104 FAIL=$((FAIL+1))
105 fi
106 done
107 108 echo
109 echo "wasm validation: $PASS passed, $FAIL failed"
110 [ "$FAIL" -eq 0 ]
111