Tree-shake: count #'x references; multi-app soundness smoke
Two things, both from studying Stalin's closed-world DCE: 1. Soundness fix: dce-collect-refs now counts a :the-var (#'x / (var x)) reference, not just a :var. Stalin's rule is that ANY reference — value position, not only a direct call — keeps the target live; a var referenced as #'x would otherwise be wrongly dropped. (My :var collection already covered value-position refs; this closes the the-var hole.) 2. host/chez/tree-shake-smoke.sh (make shakesmoke): builds example apps default vs --tree-shake and requires identical output — the real risk is shaking a binary that pulled libraries via deps.edn. Covers markdown/malli/commonmark/hiccup (git-lib apps). All produce byte-identical output shaken vs not, and drop ~0.8-1MB (malli 10.0MB -> 9.0MB) from the compiler-drop. Slow; not in the default gate. Skips without the examples repo.
This commit is contained in:
parent
709d152a1e
commit
e0c1564ff9
3 changed files with 80 additions and 6 deletions
8
Makefile
8
Makefile
|
|
@ -4,7 +4,7 @@
|
||||||
# build step. `make test` is the full gate. `make remint` rebuilds the seed after a
|
# build step. `make test` is the full gate. `make remint` rebuilds the seed after a
|
||||||
# source change.
|
# source change.
|
||||||
|
|
||||||
.PHONY: test ci values corpus unit smoke buildsmoke selfhost sci certify ffi transient infer directlink numeric inline remint
|
.PHONY: test ci values corpus unit smoke buildsmoke selfhost sci certify ffi transient infer directlink numeric inline shakesmoke remint
|
||||||
|
|
||||||
# Full gate (dev machine). Includes the self-host byte-fixpoint, which only holds
|
# Full gate (dev machine). Includes the self-host byte-fixpoint, which only holds
|
||||||
# on the same Chez that minted the seed.
|
# on the same Chez that minted the seed.
|
||||||
|
|
@ -77,6 +77,12 @@ numeric:
|
||||||
inline:
|
inline:
|
||||||
@chez --script test/chez/inline-test.ss
|
@chez --script test/chez/inline-test.ss
|
||||||
|
|
||||||
|
# Tree-shake soundness: build example apps (incl. deps.edn git-lib apps) default vs
|
||||||
|
# --tree-shake and require identical output. Slow (two builds per app); not in the
|
||||||
|
# default gate. Skips without the examples repo / Chez kernel dev files.
|
||||||
|
shakesmoke:
|
||||||
|
@sh host/chez/tree-shake-smoke.sh
|
||||||
|
|
||||||
# JVM oracle: certify the corpus against reference Clojure. Skips if clojure absent.
|
# JVM oracle: certify the corpus against reference Clojure. Skips if clojure absent.
|
||||||
certify:
|
certify:
|
||||||
@if command -v clojure >/dev/null 2>&1; then \
|
@if command -v clojure >/dev/null 2>&1; then \
|
||||||
|
|
|
||||||
|
|
@ -93,17 +93,22 @@
|
||||||
;; only the re-emitted namespaces are shaken.
|
;; only the re-emitted namespaces are shaken.
|
||||||
(define dce-kw-op (keyword #f "op"))
|
(define dce-kw-op (keyword #f "op"))
|
||||||
(define dce-kw-var (keyword #f "var"))
|
(define dce-kw-var (keyword #f "var"))
|
||||||
|
(define dce-kw-the-var (keyword #f "the-var"))
|
||||||
(define dce-kw-def (keyword #f "def"))
|
(define dce-kw-def (keyword #f "def"))
|
||||||
(define dce-kw-ns (keyword #f "ns"))
|
(define dce-kw-ns (keyword #f "ns"))
|
||||||
(define dce-kw-name (keyword #f "name"))
|
(define dce-kw-name (keyword #f "name"))
|
||||||
(define dce-reduce-children (var-deref "jolt.ir" "reduce-ir-children"))
|
(define dce-reduce-children (var-deref "jolt.ir" "reduce-ir-children"))
|
||||||
|
|
||||||
;; "ns/name" of every :var reference anywhere in node, prepended to acc. Arg order
|
;; "ns/name" of every var reference anywhere in node, prepended to acc. Counts BOTH
|
||||||
;; (acc node) matches reduce-ir-children's fold fn, so it nests directly.
|
;; a :var (call head or value) and a :the-var (#'x / (var x)) — per Stalin's rule,
|
||||||
|
;; any reference (not just a direct call) keeps the target live, so a var passed as a
|
||||||
|
;; value or referenced as #'x is reachable. Arg order (acc node) matches
|
||||||
|
;; reduce-ir-children's fold fn, so it nests directly.
|
||||||
(define (dce-collect-refs acc node)
|
(define (dce-collect-refs acc node)
|
||||||
(if (eq? (jolt-get node dce-kw-op) dce-kw-var)
|
(let ((op (jolt-get node dce-kw-op)))
|
||||||
(cons (string-append (jolt-get node dce-kw-ns) "/" (jolt-get node dce-kw-name)) acc)
|
(if (or (eq? op dce-kw-var) (eq? op dce-kw-the-var))
|
||||||
(dce-reduce-children dce-collect-refs acc node)))
|
(cons (string-append (jolt-get node dce-kw-ns) "/" (jolt-get node dce-kw-name)) acc)
|
||||||
|
(dce-reduce-children dce-collect-refs acc node))))
|
||||||
|
|
||||||
;; The fqn of a bare top-level def (the only prunable form), else #f.
|
;; The fqn of a bare top-level def (the only prunable form), else #f.
|
||||||
(define (dce-def-fqn node)
|
(define (dce-def-fqn node)
|
||||||
|
|
|
||||||
63
host/chez/tree-shake-smoke.sh
Normal file
63
host/chez/tree-shake-smoke.sh
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Tree-shake soundness smoke: build each example app twice — default and
|
||||||
|
# --tree-shake — run both, and require identical output. A wrongly-dropped def
|
||||||
|
# (incl. a core fn once core-shaking lands) shows up as a diff or a crash. Covers a
|
||||||
|
# pure-compute app and several that pull libraries via deps.edn (the key risk).
|
||||||
|
#
|
||||||
|
# Skips (like build-smoke) when the example repo or the Chez kernel dev files /
|
||||||
|
# C compiler aren't available. Slow (two full binary builds per app); not in the
|
||||||
|
# default gate — run with `make shakesmoke`.
|
||||||
|
root="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
||||||
|
cd "$root"
|
||||||
|
examples="$root/../examples"
|
||||||
|
[ -d "$examples" ] || examples="$HOME/src/jolt-lang/examples"
|
||||||
|
if [ ! -d "$examples" ]; then echo "shake smoke: skipped (examples repo not found)"; exit 0; fi
|
||||||
|
|
||||||
|
csv="$JOLT_CHEZ_CSV"
|
||||||
|
if [ -z "$csv" ]; then
|
||||||
|
chez_bin="$(command -v chez || command -v scheme || command -v petite || true)"
|
||||||
|
if [ -n "$chez_bin" ]; then
|
||||||
|
base="$(cd "$(dirname "$chez_bin")/.." 2>/dev/null && pwd)"
|
||||||
|
for d in "$base"/lib/csv*/*/; do [ -f "${d}libkernel.a" ] && csv="${d%/}" && break; done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! command -v cc >/dev/null 2>&1 || [ -z "$csv" ] || [ ! -f "$csv/scheme.h" ]; then
|
||||||
|
echo "shake smoke: skipped (Chez kernel dev files or C compiler not available)"; exit 0
|
||||||
|
fi
|
||||||
|
export JOLT_CHEZ_CSV="$csv"
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
# app-dir | main-ns | args
|
||||||
|
run_case() {
|
||||||
|
app="$examples/$1"; ns="$2"; args="$3"
|
||||||
|
[ -d "$app" ] || { echo " - $1: skipped (not present)"; return; }
|
||||||
|
b0="$tmp/$1-plain"; b1="$tmp/$1-shake"
|
||||||
|
if ! JOLT_PWD="$app" bin/joltc build -m "$ns" -o "$b0" >/dev/null 2>&1; then
|
||||||
|
echo " - $1: FAIL (default build)"; fail=1; return; fi
|
||||||
|
if ! JOLT_PWD="$app" bin/joltc build -m "$ns" -o "$b1" --tree-shake >/dev/null 2>&1; then
|
||||||
|
echo " - $1: FAIL (--tree-shake build)"; fail=1; return; fi
|
||||||
|
o0="$(cd "$app" && "$b0" $args 2>&1)"
|
||||||
|
o1="$(cd "$app" && "$b1" $args 2>&1)"
|
||||||
|
if [ "$o0" != "$o1" ]; then
|
||||||
|
echo " - $1: FAIL (output differs default vs --tree-shake)"
|
||||||
|
echo " --- default ---"; echo "$o0" | head -5
|
||||||
|
echo " --- shake -----"; echo "$o1" | head -5
|
||||||
|
fail=1; return
|
||||||
|
fi
|
||||||
|
s0="$(wc -c < "$b0")"; s1="$(wc -c < "$b1")"
|
||||||
|
echo " - $1: ok (output identical; $((s0/1024))K -> $((s1/1024))K)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Library apps (deps.edn git deps) with deterministic stdout — the key risk is that
|
||||||
|
# tree-shaking a binary that pulled libraries drops a reachable lib (or, later, core)
|
||||||
|
# fn. A timing/benchmark app (e.g. ray-tracer) is unsuitable: its output varies.
|
||||||
|
echo "shake smoke: building each app default vs --tree-shake (output must match)"
|
||||||
|
run_case markdown-app app.core ""
|
||||||
|
run_case malli-app app.core ""
|
||||||
|
run_case commonmark-app app.core ""
|
||||||
|
run_case hiccup-app app.core ""
|
||||||
|
|
||||||
|
[ "$fail" = 0 ] && echo "shake smoke: passed" || echo "shake smoke: FAILED"
|
||||||
|
exit $fail
|
||||||
Loading…
Add table
Add a link
Reference in a new issue