Add a PNG writer so the demos produce actual images. Two pieces:
- src/jolt/png.janet — the encoder (8-bit RGB, filter None, stored/uncompressed
DEFLATE so no compressor is needed; correct CRC32 + Adler32). It lives in Janet
because per-byte work in the overlay is far too slow (a byte-array aset loop is
~30s for 360k bytes, and CRC32 over even a tiny image would be worse). Janet's
bit ops are 32-bit signed, so the 32-bit-unsigned arithmetic is done with plain
number ops (doubles hold 2^32) plus byte-level bxor. Exposed to the overlay as
janet.png/* by importing it into eval_base's module-load-env.
- src/jolt/jolt/png.clj — the jolt.png overlay wrapper: image / put! / write. The
overlay only produces pixels; the host encodes them in one pass.
mandelbrot gets a `render` subcommand (jolt -m mandelbrot render out.png [size])
that colours count-point's escape counts; the numeric-arg bench path is untouched.
Verified end to end: macOS `sips` accepts the output (so CRC/zlib are valid).
png-test covers the encoder structure (signature/IHDR/IEND) and the overlay
round-trip.
Co-authored-by: Yogthos <yogthos@gmail.com>
The jolt-vs-hand-Janet-vs-JVM mandelbrot comparison splits the 15.4x floor
into two layers: a Janet-VM floor (~10.8x JVM, optimal while-loop Janet over
unboxed doubles — only native codegen moves it) plus a ~1.43x jolt loop-
lowering overhead on top. The overhead is entirely the loop/recur -> recursive-
closure-called-per-iteration lowering; hand-Janet written the same way matches
jolt, while a while+var/set version is 1.43x faster. So a cheap backend win
(jolt-v28u) sits above the structural native-codegen lever.
Adds the spike artifacts under bench/ and the results writeup; marks the spike
done in the handoff. No source changes.
Co-authored-by: Yogthos <yogthos@gmail.com>
The ray tracer is compute-bound and the three existing benches only cover
alloc / megamorphic-dispatch / collections. Add three axes the epic needs to
judge itself holistically:
- mono-dispatch: monomorphic protocol dispatch. Its jolt/JVM ratio (~110x) is
*worse* than megamorphic (~76x) — the JVM inline-caches a runtime-monomorphic
call site to near-free while jolt does a full registry dispatch (devirt only
fires on statically-proven receivers). Points at the call-site inline cache.
- mandelbrot: pure float compute, no alloc/dispatch. The floor at ~15x — native
arith already gets close to the JVM.
- fib: recursion, call + integer-arith overhead.
run.sh gains JVM=1, which runs each bench on JVM Clojure too and prints the
jolt/JVM ratio. collections sized up now that the map is a HAMT (jolt-684u).
README documents the axes and the current scorecard.
Co-authored-by: Yogthos <yogthos@gmail.com>
The ray tracer is float-compute-bound (devirt, alloc removal, type-proving all
measured flat on it), so it can't validate the optimization passes. Add a small
cross-language suite (AWFY + CLBG style, portable Clojure) isolating the axes it
misses:
binary-trees allocation / GC pressure (escaping short-lived records)
dispatch megamorphic protocol dispatch (~1M dispatches/s; WP can't devirt)
collections persistent map/vector churn
bench/run.sh runs them; bench/README.md maps each to the pass it exercises.
collections immediately surfaced jolt-684u: the persistent hash map is O(n) per
assoc (flat copy-on-write bucket array, not a HAMT) — n=4000 assocs take 50s.
Invisible to the ray tracer (no maps).
Co-authored-by: Yogthos <yogthos@gmail.com>