commit 6e16d41536712479f63a338cbacd62feca3dfddf
parent 60a5068f6d4ba9b42645f2f1b01fabe1a9bf6a6e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 26 May 2026 17:50:55 -0700
test: add C and W paths to default test-toy; add W path to parse runner
toy/run.sh: default PATHS from "RL" to "RLCW". C and W are already
gated to opt=0 only inside the runner, so no duplicate work at higher
opt levels.
parse/run.sh: add W path (cc -target wasm32-none + cfree run -e
test_main). Phased-rollout panics surface as SKIP. Adds .wasm.skip
sidecar support and T_W timing. Also plumbs CFREE into the runner's
env default (was hardcoded to build/cfree).
test.mk: add test-wasm-c target (parse corpus under W path, ALLOW_SKIP
until the corpus is mostly green); add it to TEST_TARGETS.
Diffstat:
3 files changed, 93 insertions(+), 9 deletions(-)
diff --git a/test/parse/run.sh b/test/parse/run.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# test/parse/run.sh — file-driven C-parser test harness.
#
-# For each test/parse/cases/*.c, runs up to five paths (the test/cg path
-# matrix minus W; DWARF directives may be added later via .dwarf sidecars):
+# For each test/parse/cases/*.c, runs up to six paths (DWARF directives may
+# be added later via .dwarf sidecars):
#
# D in-process JIT — parse-runner --jit FILE.c → exit code matches
# expected. No file I/O. aarch64 host only.
@@ -16,6 +16,13 @@
# Host arch must match cross target. Cases that hit an
# unimplemented C-target method are reported as SKIP
# (not FAIL) so phased backend rollout is tolerated.
+# W wasm roundtrip — cfree cc -target wasm32-none -c case.c -> .wasm, then
+# cfree run -e test_main on it (the lang/wasm frontend
+# re-lowers to native CG, JITs, calls test_main).
+# Exercises the Wasm CGTarget (C -> wasm). Opt-in and
+# opt=0 only; host arch must match cross target (the
+# re-lowering JITs for the host). Phased-rollout panics
+# ("wasm: ... not yet implemented") report SKIP.
#
# Reuses the test/link harness binaries (cfree-roundtrip, link-exe-runner,
# jit-runner) and test/link/harness/start.c verbatim.
@@ -25,11 +32,12 @@
# <name>.skip — single-line reason. Treated as failure unless
# CFREE_TEST_ALLOW_SKIP=1 (matching the rest of
# the test suite).
+# <name>.wasm.skip — single-line reason; opts the case out of path W only.
#
# Filtering:
# ./run.sh [name_filter] [paths]
# name_filter substring match against case basename
-# paths subset of "DREJC" (default "DREJ" — C opt-in)
+# paths subset of "DREJCW" (default "DREJ" — C and W opt-in)
# Equivalent env vars: CFREE_TEST_FILTER, CFREE_TEST_PATHS.
#
# Optimization levels:
@@ -50,6 +58,7 @@ LINK_TEST_DIR="$ROOT/test/link"
BUILD_DIR="$ROOT/build/test"
LIB_AR="$ROOT/build/libcfree.a"
+CFREE="${CFREE:-$ROOT/build/cfree}"
PARSE_RUNNER="$BUILD_DIR/parse-runner"
ROUNDTRIP_BIN="$BUILD_DIR/cfree-roundtrip"
LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner"
@@ -105,7 +114,8 @@ case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac
case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac
case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac
case "$PATHS" in *C*) RUN_C=1;; *) RUN_C=0;; esac
-T_D=0; T_R=0; T_E=0; T_J=0; T_C=0
+case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac
+T_D=0; T_R=0; T_E=0; T_J=0; T_C=0; T_W=0
now_ms() { python3 -c 'import time;print(int(time.time()*1000))'; }
mkdir -p "$BUILD_DIR" "$BUILD_DIR/parse"
@@ -163,6 +173,7 @@ replay_events() {
E) T_E=$(( T_E + b )) ;;
J) T_J=$(( T_J + b )) ;;
C) T_C=$(( T_C + b )) ;;
+ W) T_W=$(( T_W + b )) ;;
esac
;;
QUEUE_E)
@@ -405,7 +416,7 @@ FILTERED_CASES=()
# loop flags as "missing worker result". Restrict to opt=0 in that case.
case_opt_levels="$OPT_LEVELS"
if [ $RUN_D -eq 0 ] && [ $RUN_R -eq 0 ] && [ $RUN_E -eq 0 ] && \
- [ $RUN_J -eq 0 ] && [ $RUN_C -eq 1 ]; then
+ [ $RUN_J -eq 0 ] && { [ $RUN_C -eq 1 ] || [ $RUN_W -eq 1 ]; }; then
case_opt_levels="0"
fi
for src in "${CASES[@]}"; do
@@ -630,6 +641,66 @@ run_parse_case() {
emit_event "$event" SKIP "$name/C" "host arch != $TEST_ARCH (C target is target-locked)"
fi
fi
+
+ # ---- Path W: cc -target wasm32-none + cfree run ----------------------
+ # Compile the case straight to a .wasm via the Wasm CGTarget, then run it
+ # with `cfree run -e test_main` (the lang/wasm frontend re-lowers the
+ # module to native CG, JITs it, and calls test_main). Mirrors the toy
+ # runner's W path. Like path C it is target-agnostic, so it does not
+ # depend on the cross-target arch; but the re-lowering JITs for the host,
+ # so it only runs when the host arch matches the cross-target.
+ #
+ # The Wasm CGTarget ignores -O for opt purposes (the wasm frontend picks
+ # its own native opt level when re-lowering), so W runs at opt=0 only.
+ # Phased-rollout panics ("wasm: <feature> not yet implemented" and the
+ # "unsupported"/"supported in v1" variants) surface as SKIP, not FAIL, so
+ # the signal stays "real regressions". A `<name>.wasm.skip` sidecar opts a
+ # case out of path W with a reason.
+ run_w=$RUN_W
+ if [ $run_w -eq 1 ] && [ "$opt" != "0" ]; then
+ run_w=0
+ fi
+ if [ $run_w -eq 1 ] && [ -e "$TEST_DIR/cases/$base_name.wasm.skip" ]; then
+ emit_event "$event" SKIP "$name/W" \
+ "$(head -n1 "$TEST_DIR/cases/$base_name.wasm.skip")"
+ run_w=0
+ fi
+ if [ $run_w -eq 1 ] && [ $is_native_target -eq 0 ]; then
+ emit_event "$event" SKIP "$name/W" "host arch != $TEST_ARCH (no native JIT for re-lowering)"
+ run_w=0
+ fi
+ if [ $run_w -eq 1 ]; then
+ local wasm w_cc_err w_run_err w_rc w_missing
+ wasm="$work/$base_name.wasm"
+ w_cc_err="$work/w.cc.err"
+ w_run_err="$work/w.run.err"
+ t0=$(now_ms)
+ if ! "$CFREE" cc -O0 -target wasm32-none -c "$src" -o "$wasm" \
+ >"$work/w.cc.out" 2>"$w_cc_err"; then
+ dt=$(( $(now_ms) - t0 )); emit_event "$event" TIME W "$dt"
+ w_missing=$(grep -oE 'wasm(32 ABI| target)?: .*(not yet implemented|not (yet )?supported|unsupported [a-z_0-9]+|max [0-9]+ supported|supported in v1)' \
+ "$w_cc_err" 2>/dev/null | head -n1 || true)
+ if [ -n "$w_missing" ]; then
+ emit_event "$event" SKIP "$name/W" "$w_missing"
+ else
+ emit_event "$event" FAIL "$name/W (cc -target wasm32-none failed; see $w_cc_err)"
+ fi
+ else
+ # Validate by exit code only (like paths D/J/C). cc stderr is not a
+ # failure on its own: legitimate non-fatal diagnostics such as
+ # `#warning` print there while compilation succeeds and the program
+ # still runs. A real compile failure already took the branch above;
+ # a real run failure shows up as an exit-code mismatch.
+ "$CFREE" run -e test_main "$wasm" >"$work/w.run.out" 2>"$w_run_err"
+ w_rc=$?
+ dt=$(( $(now_ms) - t0 )); emit_event "$event" TIME W "$dt"
+ if [ "$w_rc" -eq "$expected_byte" ]; then
+ emit_event "$event" PASS "$name/W (${dt}ms)"
+ else
+ emit_event "$event" FAIL "$name/W (expected $expected_byte got $w_rc, ${dt}ms)"
+ fi
+ fi
+ fi
return 0
}
@@ -685,8 +756,8 @@ if [ ${#SKIP_NAMES[@]} -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then
fi
printf '\nResults: %s pass, %s fail, %s skip\n' "$PASS" "$FAIL" "$SKIP"
-printf 'Time: D=%dms R=%dms E=%dms (batch %dms) J=%dms C=%dms\n' \
- "$T_D" "$T_R" "$T_E" "$T_E_BATCH" "$T_J" "$T_C"
+printf 'Time: D=%dms R=%dms E=%dms (batch %dms) J=%dms C=%dms W=%dms\n' \
+ "$T_D" "$T_R" "$T_E" "$T_E_BATCH" "$T_J" "$T_C" "$T_W"
if [ $FAIL -gt 0 ]; then exit 1; fi
if [ $SKIP -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then exit 1; fi
diff --git a/test/test.mk b/test/test.mk
@@ -77,6 +77,7 @@ TEST_TARGETS = \
test-smoke-x64 \
test-toy \
test-wasm \
+ test-wasm-c \
test-wasm-front \
test-wasm-target \
test-wasm-toy \
@@ -135,6 +136,17 @@ test-cbackend: bin
test-wasm-toy: bin
@CFREE_TEST_PATHS=W CFREE_TEST_ALLOW_SKIP=1 CFREE=$(abspath $(BIN)) sh test/toy/run.sh
+# test-wasm-c: opt-in C -> Wasm -> JIT roundtrip, the C-frontend analogue of
+# test-wasm-toy. Runs the test/parse corpus under the W path (compile
+# -target wasm32-none, then `cfree run -e test_main` the .wasm, which routes
+# back through the lang/wasm frontend to native CG). The C corpus exercises
+# far more of the language than the toy corpus, so expect many SKIPs for
+# not-yet-implemented Wasm lowerings; the target makes that progress visible
+# without adding noise to the default `test` summary. Drop
+# `CFREE_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
+test-wasm-c: bin $(PARSE_RUNNER)
+ @CFREE_TEST_PATHS=W CFREE_TEST_ALLOW_SKIP=1 CFREE=$(abspath $(BIN)) bash test/parse/run.sh
+
test-pp: test-pp-ok test-pp-err
test-pp-ok: bin
diff --git a/test/toy/run.sh b/test/toy/run.sh
@@ -31,7 +31,8 @@
# ./run.sh [name_filter] [paths]
# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RLXCW".
# X is opt-in cross-arch cc+ld+exec for aa64, x64, and rv64.
-# C is opt-in C-source emit; W is opt-in Wasm roundtrip; default paths are "RL".
+# C and W run only at O0 even when included with other opt levels.
+# Default paths are "RLCW"; override with CFREE_TEST_PATHS.
# CFREE_TOY_OPT_LEVELS selects optimization levels, default "0 1 2".
set -u
@@ -42,7 +43,7 @@ BUILD_DIR="$ROOT/build/test/toy"
CFREE="${CFREE:-$ROOT/build/cfree}"
FILTER="${1:-${CFREE_TEST_FILTER:-}}"
-PATHS="${2:-${CFREE_TEST_PATHS:-RL}}"
+PATHS="${2:-${CFREE_TEST_PATHS:-RLCW}}"
case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac
case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac
case "$PATHS" in *X*) RUN_X=1;; *) RUN_X=0;; esac