* Make the benchmark harness build optimized binaries on Chez bench/run.sh was Janet-era: it invoked a 'jolt' binary and set JOLT_DIRECT_LINK/JOLT_WHOLE_PROGRAM, none of which exist on Chez, where 'joltc run -m' runs fully unoptimized (direct-link and inline default off). So the suite was measuring jolt's unoptimized path. run.sh now compiles each benchmark to an optimized AOT binary (joltc build --direct-link --opt) and times it against JVM Clojure on the same portable source, auto-detecting the Chez kernel dev files like build-smoke.sh. Adds bench/deps.edn so joltc resolves the namespaces, NO_JVM to skip the reference. mandelbrot.clj dropped its jolt.png require so the JVM reference can run it; the picture demo moved to mandelbrot_png.clj (jolt-only). README scorecard refreshed with current Chez numbers and the two-regime read (compute ~8-10x substrate floor; dispatch/alloc ~120-330x architectural gaps the passes don't touch). Stale 'jolt -m' header lines point at bench/run.sh. * Emit direct self-calls for named-fn self-recursion A self-recursive call to a named fn compiled to (jolt-invoke fib ...) instead of a direct (fib ...): emit-invoke handled a :local callee only when it was NOT a known proc, so a :local that IS in *known-procs* (the letrec-bound self-name) fell through to the :else jolt-invoke branch. Now a :local known proc emits a direct Scheme call — no jolt-invoke, no per-call arg-list consing; case-lambda handles arity. fib 30: 63.3ms -> 4.7ms (faster than JVM Clojure's 7.1ms; was 9x slower). The win is on every self-recursive non-loop fn, including the compiler's own. No semantic change — selfhost holds, make test green, shakesmoke/buildsmoke byte-identical. Re-mint (backend is seed). Corpus rows pin self-recursion across fixed/multi/ variadic arities. * Intern no-ns keywords without per-call allocation (keyword #f name) built a fresh combined-key string (string-append) on every call just to do the intern-table lookup — ~80 bytes of garbage per (:kw x), map literal, keyword arg, etc. A no-ns keyword now interns in a table keyed by the name string directly, so a lookup of an already-interned keyword is one hashtable-ref with no allocation. The ns table keeps the combined key; both share the keyword-t khash (equal-hash of the combined key) so hash values are unchanged. Small time win on its own (the field-read dispatch dominates hot record code — see jolt-unx4) but removes per-call keyword allocation everywhere. Runtime .ss, no re-mint; identity/=/hash unchanged, make test green. * Fast record field reads: single eq? scan, skip the get-arm walk (:field rec) / (get rec :field) lowers to (jolt-get rec kw), which walked the get-arm list to reach the jrec arm, then did jrec-has? + jrec-lookup — TWO linear scans, each comparing keys through the generic jolt=2 equality dispatcher. Field keys are interned keywords, so: - jrec-key=? compares a keyword query by eq? (jolt=2 only for non-keyword keys), - jrec-ref does ONE scan (vs has?+lookup) and runs a deftype's ILookup valAt only when the field is genuinely absent (present-nil still returns nil, not default), - jolt-get-dispatch checks jrec? first, skipping the get-arm walk for the hottest get target. jrec-lookup/jrec-has? (used by =, contains?, etc.) get the fast compare too. binary-trees 135x->18.9x, dispatch 121x->26.4x, mono-dispatch 327x->108x vs JVM. Runtime .ss (collections.ss + records.ss), no re-mint; make test + shakesmoke + buildsmoke green, record get/assoc/keys/=/count semantics unchanged. --------- Co-authored-by: Yogthos <yogthos@gmail.com> |
||
|---|---|---|
| .. | ||
| build-app | ||
| bench-chez.ss | ||
| corpus.edn | ||
| directlink-test.ss | ||
| ffi-binding-test.ss | ||
| inline-test.ss | ||
| numeric-test.ss | ||
| README.md | ||
| transient-test.ss | ||
| unit.edn | ||
| values-test.ss | ||
Chez test harness
The correctness gate for jolt. Pure Chez (+ Clojure for the JVM oracle).
Correctness is judged against the JVM-sourced conformance spec; the spec itself
lives in test/conformance/ (see its SPEC.md). Run the whole gate with make test from the repo root.
The spec corpus
corpus.edn is the contract: ~2920 rows {:suite :label :expected :actual}, with
:expected sourced from reference JVM Clojure by test/conformance/regen-corpus.clj.
It is frozen (the canonical source) — add or change cases here, then re-source the
answers with regen-corpus.clj and re-certify with test/conformance/certify.clj.
The gate runners (host/chez/)
-
run-corpus.ss— runs every corpus case through the spine (read → analyze → IR → emit → eval, all on Chez), comparing each result by value-equality against the JVM:expected. Aknown-failallowlist covers cases jolt can't match because Chez has no JVM host (Java classes, arrays,BigDecimal, opaque host-object printers, …); the gate fails only on a NEW divergence or if the pass count drops below the floor.chez --script host/chez/run-corpus.ss JOLT_CORPUS_LIMIT=200 … # every-Nth stride, fast iteration JOLT_CHEZ_ZJ_FLOOR=N … # override the floor (default 2678) -
run-unit.ss— host-specific unit cases (test/chez/unit.edn) that aren't in the JVM-portable corpus: dot-forms, java statics, io, reader, walk, vars/namespaces, refs. Each:expris evaluated in-process and its printed value compared to a baked:expected(:throwsasserts a raise). -
selfcheck.sh— self-host fixpoint:bootstrap.ssrebuild byte-equals the checked-in seed (host/chez/seed/). -
smoke.sh— realbin/joltc -eCLI smoke.
Other Chez tests
values-test.ss— the value model (nil/truthiness/collections).make values.bench-chez.ss— compute bench through the pipeline (opt-in; not in the gate).
All runners assume chez on PATH.