jolt/host/chez/seed
Dmitri Sotnikov f920ff6ea2
Nilable record types + flow-sensitive nil narrowing (#235)
A record-or-nil (a protocol method whose impls return a record in one branch and
nil in another, or an `if` over a ctor and nil) now types as a NILABLE record
instead of widening to :any. A nilable record still bare-indexes its field reads
(jrec-field-at falls back to jolt-get on nil), but some?/nil? do NOT fold on it, so
a runtime guard is preserved — and inside (if (some? x) ..) / (if x ..) the then-
branch narrows x to the non-nil record, so its reads bare-index AND unbox there.

This is what lets the bounced ray type without a hint: scatter returns
ScatterResult-or-nil (Metal absorbs some rays), and the consumer reads
(:ray scattered) only under (if (some? scattered) ..). The narrowing proves
scattered non-nil there.

lattice: :nil type; :nil ∨ struct -> nilable struct, ∨ anything else -> :any;
nilability is contagious through a struct join, which also now preserves :type when
both sides agree (needed so a record ∨ its nilable self stays that record).
truthy-type?/field-type/pred-on treat a nilable struct as maybe-nil. types: nil
literal -> :nil; an `if` whose test is (some? x)/(nil? x)/x narrows the nilable
local x in the proven branch.

Ray tracer with NO hints: 38.4s -> 23.9s (~1.6x) — hit-sphere now types fully
(0 jolt-get, 57 jrec-field-at, 38 fl-ops), identical to the hand-hinted build.

run-narrow.ss gate, incl. the load-bearing check that the nil case still takes the
else branch (the guard is not folded away). make test / shakesmoke green, selfhost
holds, 0 new divergences.

Co-authored-by: Yogthos <yogthos@gmail.com>
2026-06-26 17:16:16 +00:00
..
image.ss Nilable record types + flow-sensitive nil narrowing (#235) 2026-06-26 17:16:16 +00:00
prelude.ss See through or/and in truthy-elision (#224) 2026-06-26 06:11:35 +00:00
README.md Docs: Chez-only, drop the Janet-era references and obsolete migration notes 2026-06-22 09:05:35 -04:00

Chez bootstrap seed

These two files are the bootstrap compiler for jolt — the seed that makes the build self-hosting:

  • prelude.ss — the clojure.core prelude (all tiers + clojure.string/walk/ template/edn/set/pprint) as Scheme def-var! forms.
  • image.ss — the compiler image (jolt.ir + jolt.analyzer + jolt.backend-scheme) as Scheme def-var! forms.

Both are generated, not hand-written. They are checked in because a fresh checkout must be able to build jolt-on-Chez using only Chez: host/chez/bootstrap.ss loads this seed, then rebuilds the prelude + image from the .clj/.ss sources via the on-Chez compiler (read → analyze → emit, all on Chez). The seed is a joint byte-fixpoint: rebuilding from an up-to-date seed reproduces it exactly. make selfhost (host/chez/selfcheck.sh) runs host/chez/bootstrap.ss and diffs the rebuilt artifacts against the checked-in seed.

Re-minting

When the seed sources change (the core tiers, the compiler namespaces, the host contract, the reader, emit-image.ss), the seed drifts and make selfhost fails. Re-mint it by running host/chez/bootstrap.ss and writing the freshly rebuilt prelude/image back to host/chez/seed/prelude.ss / host/chez/seed/image.ss, then commit the refreshed files.