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>
139 lines
5.6 KiB
Makefile
139 lines
5.6 KiB
Makefile
# jolt — Clojure on Chez Scheme. Single substrate, no Janet.
|
|
#
|
|
# bin/joltc runs jolt directly off the checked-in seed (host/chez/seed/); there is no
|
|
# build step. `make test` is the full gate. `make remint` rebuilds the seed after a
|
|
# source change.
|
|
|
|
.PHONY: test ci values corpus unit smoke buildsmoke selfhost sci certify ffi transient infer wp devirt fieldread numwp fieldnum protoret narrow directlink numeric inline shakesmoke remint
|
|
|
|
# Full gate (dev machine). Includes the self-host byte-fixpoint, which only holds
|
|
# on the same Chez that minted the seed.
|
|
test: selfhost ci
|
|
@echo "OK: all gates passed"
|
|
|
|
# CI gate: behavior only. The checked-in seed is a minted artifact (like a
|
|
# lockfile) — it RUNS correctly on any Chez, but `selfhost` rebuilds it and a
|
|
# different Chez version may emit byte-different (gensym/order) output, so the
|
|
# byte-fixpoint is a dev-machine check, not a CI one (jolt-8479).
|
|
ci: values corpus unit smoke buildsmoke sci ffi transient infer wp devirt fieldread numwp fieldnum protoret narrow directlink numeric inline certify
|
|
@echo "OK: CI gates passed"
|
|
|
|
# Self-host fixpoint: bootstrap.ss rebuild == checked-in seed.
|
|
selfhost:
|
|
@sh host/chez/selfcheck.sh
|
|
|
|
# Value-model unit tests (nil/truthiness/collections on Chez).
|
|
values:
|
|
@chez --script test/chez/values-test.ss
|
|
|
|
# Corpus conformance vs JVM-sourced expecteds (allowlist + floor).
|
|
corpus:
|
|
@chez --script host/chez/run-corpus.ss
|
|
|
|
# Host-specific unit cases.
|
|
unit:
|
|
@chez --script host/chez/run-unit.ss
|
|
|
|
# Real-CLI smoke over bin/joltc.
|
|
smoke:
|
|
@sh host/chez/smoke.sh
|
|
|
|
# `jolt build` produces a working standalone binary.
|
|
buildsmoke:
|
|
@sh host/chez/build-smoke.sh
|
|
|
|
# SCI conformance: load borkdude/sci's source through joltc (floor-gated).
|
|
sci:
|
|
@chez --script host/chez/run-sci.ss
|
|
|
|
# FFI: bind native functions (typed foreign-procedure), memory, and that a
|
|
# :blocking call is collect-safe (a parked thread doesn't pin the collector).
|
|
ffi:
|
|
@chez --script test/chez/ffi-binding-test.ss
|
|
|
|
# Transients: mutable backing, snapshot on persistent!, and linear-time builds.
|
|
transient:
|
|
@chez --script test/chez/transient-test.ss
|
|
|
|
# Inference / success-type checking: drive jolt.passes.types directly and assert
|
|
# diagnostic counts + collected calls/escapes (the optimization pass the other
|
|
# gates don't exercise).
|
|
infer:
|
|
@chez --script host/chez/run-infer.ss
|
|
|
|
# Whole-program param-type fixpoint: record types flowing across fn boundaries
|
|
# (a callee's param picks up its callers' ctor return types), the foundation the
|
|
# bare-index field reads + protocol devirtualization build on.
|
|
wp:
|
|
@chez --script host/chez/run-wp.ss
|
|
|
|
# Protocol-call devirtualization: a monomorphic call resolves its impl by the
|
|
# inferred record tag (find-protocol-method) instead of routing through the
|
|
# protocol var; the result must match ordinary dispatch.
|
|
devirt:
|
|
@chez --script host/chez/run-devirt.ss
|
|
|
|
# Native record field reads: a keyword lookup on a statically-known record reads
|
|
# the field by its declared slot (jrec-field-at) instead of jolt-get; the value
|
|
# must match, and a non-field key / default-arg form keeps the generic path.
|
|
fieldread:
|
|
@chez --script host/chez/run-fieldread.ss
|
|
|
|
# Hintless whole-program double inference: a fn whose every call site passes a
|
|
# flonum has its param typed :double by the closed-world fixpoint and unboxed to
|
|
# fl-ops with no ^double hint; an integer caller leaves it generic, an escaped fn
|
|
# keeps :any.
|
|
numwp:
|
|
@chez --script host/chez/run-numwp.ss
|
|
|
|
# Double record fields: a ^double-tagged field reads back as a flonum (coerced at
|
|
# construction and set!), so hintless arithmetic over those fields unboxes to fl-ops;
|
|
# an untagged field stays generic.
|
|
fieldnum:
|
|
@chez --script host/chez/run-fieldnum.ss
|
|
|
|
# Protocol-method return inference: a method whose impls all return the same record
|
|
# type has a monomorphic return, so a (method recv ..) call types as that record and
|
|
# a field read off the result bare-indexes; a disagreeing impl keeps the generic path.
|
|
protoret:
|
|
@chez --script host/chez/run-protoret.ss
|
|
|
|
# Nilable record types + flow-sensitive narrowing: a record-or-nil types as a nilable
|
|
# record (some?/nil? don't fold, so a runtime guard stays); inside (if (some? x) ..)
|
|
# the then-branch narrows x to non-nil, so its field reads bare-index and unbox.
|
|
narrow:
|
|
@chez --script host/chez/run-narrow.ss
|
|
|
|
# Direct-linking emission: a closed-world build binds top-level app defs to jv$
|
|
# Scheme bindings and routes app->app calls/refs to them, skipping var-deref +
|
|
# jolt-invoke; ^:dynamic/^:redef and nested defs opt out.
|
|
directlink:
|
|
@chez --script test/chez/directlink-test.ss
|
|
|
|
# Hint-directed fast arithmetic: ^double/^long param hints (and float literals)
|
|
# lower arithmetic to Chez fl*/fx* ops; un-hinted integer code stays generic.
|
|
numeric:
|
|
@chez --script test/chez/numeric-test.ss
|
|
|
|
# IR inlining: a small single-arity defn is spliced at call sites (under optimize),
|
|
# with ^double/^long entry/return coercions carried through via :coerce nodes.
|
|
inline:
|
|
@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.
|
|
certify:
|
|
@if command -v clojure >/dev/null 2>&1; then \
|
|
clojure -M test/conformance/certify.clj; \
|
|
else \
|
|
echo "certify: clojure not on PATH — skipped"; \
|
|
fi
|
|
|
|
# Re-mint the seed after changing a seed source (reader/analyzer/backend/core).
|
|
remint:
|
|
@sh host/chez/remint.sh
|