jolt.passes.inline was fully written but dormant — it fetched bodies via the inline-ir host hook, which was a stub returning nil. Wire it up: run-passes stashes each inline-eligible defn (single fixed arity) as its form is optimized, and inline-ir hands the body back at call sites under --opt. The catch was the ^double/^long coercion: an inlined fn drops its param-entry and return coercion, so (work 3 4) on a ^double fn would return 25 instead of 25.0. New :coerce IR node carries the coercion inside the spliced body — the inline pass wraps a hinted param's arg and the return in :coerce, the back end lowers it (exact->inexact / jolt->fx), and jolt.passes.numeric reads its :kind. So an inlined call matches the called one and the body's fl*/fx* fast path still fires. Only under --opt (closed world); the seed mint and -e don't inline, so selfhost and the corpus are unaffected. test/chez/inline-test.ss 12/12 (make inline); full make test green, 0 new corpus divergences. Bench (hot loop, body is a ^double helper call): direct-link 500ms -> --opt (inlined) 184ms = 2.7x, by eliminating the call + coercion wrappers and letting Chez fuse the fl-ops unboxed. ~26x over the default dispatched build. |
||
|---|---|---|
| .github/workflows | ||
| bench | ||
| bin | ||
| docs | ||
| host/chez | ||
| img | ||
| jolt-core | ||
| stdlib | ||
| test | ||
| tools | ||
| vendor | ||
| .DS_Store | ||
| .gitignore | ||
| .gitmodules | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
Jolt
A Clojure implementation on Chez Scheme.
Jolt reads Clojure source, analyzes it to a host-neutral IR, emits Scheme, and
runs it on Chez. The compiler is self-hosted: it is written in Clojure
(jolt-core/) and compiles itself. It ships a Clojure-compatible standard library.
Requirements
Only Chez Scheme (the gate invokes it as
chez). The conformance gate additionally uses Clojure on the JVM as an oracle,
but running jolt does not.
Build
There is no build step. The bootstrap seed (host/chez/seed/{prelude,image}.ss)
is checked in, so a fresh clone runs immediately:
git clone --recurse-submodules https://github.com/jolt-lang/jolt.git
cd jolt
bin/joltc -e '(+ 1 2)' # => 3
After changing a compiler source — the reader (host/chez/reader.ss), the
analyzer/IR/backend (jolt-core/jolt/*.clj), or the clojure.core overlay
(jolt-core/clojure/core/*.clj) — re-mint the seed:
make remint # iterates host/chez/bootstrap.ss to a byte-fixpoint
Run
bin/joltc -e EXPR # evaluate a Clojure expression and print the result
$ bin/joltc -e '(->> (range 10) (filter even?) (map (fn [x] (* x x))) (reduce +))'
120
$ bin/joltc -e '(/ 1 2)'
1/2
Compile a binary
bin/joltc build ahead-of-time compiles a project into a single self-contained
executable — the runtime, clojure.core, the standard library, the app, and its
deps.edn dependencies are linked in, so the result needs no Chez install, no
JVM, and no source on disk to run.
bin/joltc build -m myapp.core -o myapp # compile myapp.core's -main into ./myapp
./myapp arg1 arg2 # runs anywhere; args reach -main
Modes trade dynamism for speed: the default (release) build uses the proven code
generator; --opt also runs the inference + scalar-replacement passes over the
closed-world program; --dev is unoptimized.
This needs Chez's kernel development files (libkernel.a, scheme.h) and a C
compiler. They come with a from-source Chez install; a distro chezscheme
package ships only the runtime, so build won't link a binary there.
RFC 0007 (docs/rfc/) covers the design and the three-mode model.
Architecture
A small Chez runtime (host/chez/*.ss: value model, persistent collections, seqs,
vars/namespaces, host interop) hosts a portable Clojure overlay split across two
source roots by when they load:
jolt-core/is baked into the seed — the compiler (jolt-core/jolt/: reader/analyzer/IR/backend, plusjolt.main/jolt.deps) andclojure.corein dependency-ordered tiers (jolt-core/clojure/core/NN-*.clj). Changing anything here means re-minting the seed.stdlib/loads lazily at runtime off the source roots — the rest of the standard library (clojure.string/set/walk/edn/pprint/…) plus thejolt.ffihost library. Editing these needs no re-mint.
bin/joltc loads the checked-in seed and the spine, then compiles and evaluates on
Chez (read → analyze → IR → emit → eval). host/chez/bootstrap.ss rebuilds that
seed from source on pure Chez; the build is a self-hosting fixpoint (a rebuild
reproduces the checked-in seed byte-for-byte).
Differences from Clojure
Jolt targets Clojure semantics but runs on Chez, not the JVM. Most portable
Clojure runs unchanged — persistent collections (32-way-trie vectors, HAMT
maps/sets), the numeric tower (exact integers, bignums, ratios, doubles), lazy
and infinite sequences, transducers, destructuring, multimethods with
hierarchies, protocols/records (deftype/defrecord/reify/extend-protocol),
metadata, namespaces, atoms, future/promise/agent/pmap,
clojure.core.async, runtime eval/load-string/defmacro, and the full
reader (#(), #_, #?, tagged literals, #"…") all behave as on the JVM.
= is category-aware ((= 3 3.0) ⇒ false) and == is value-equality, as in
Clojure. The genuine divergences:
- No JVM, no Java interop. No reflection, no
gen-class/proxy. Interop syntax (Class.,Class/static,.method) resolves only against a shimmed subset of thejava.*standard library; a class token is a name, not a loaded class. See docs/host-interop.md. To call C libraries directly, use thejolt.ffiforeign-function interface (how the db and http-client libraries bind SQLite/libpq and sockets/OpenSSL/zlib). - No
BigDecimal.decimal?is always false and there is noMliteral; the rest of the numeric tower matches the JVM. - No STM. No
ref/dosync/alter/commute— coordinated shared state uses atoms (per-atom mutex, JVM-style CAS). The concurrency primitives above are otherwise present and run on a shared heap. - Regex engine. Patterns compile through
irregex (vendored), not
java.util.regex; common patterns work, Java-specific features can differ. - Coverage.
clojure.coreis implemented function by function against the JVM-sourced conformance corpus — broad but not total; a namespace can load with most functions working and a few not yet implemented.
Test
make test # the full gate
make corpus # conformance corpus vs the JVM-sourced spec
make unit # host-specific unit cases
make selfhost # bootstrap fixpoint (rebuild == checked-in seed)
make smoke # bin/joltc CLI smoke
make sci # load borkdude/sci's source through joltc (compat stress)
make ffi # HTTP-server GC-safety + http-client temp paths
make transient # transient mutation + linear-time builds
make certify # JVM oracle (skips if clojure is absent)
The conformance corpus (test/chez/corpus.edn) is a host-neutral language spec
whose expected values are sourced from reference JVM Clojure. See
test/conformance/SPEC.md.