Propagate declared record hints through the whole-program fixpoint (jolt-3ko) (#134)

A ^Record param hint was applied only at the final re-emit (reinfer-def), not
during the inter-procedural fixpoint. So a hinted param with no callers stayed
:any while inference ran, and a field read off it (e.g. (:origin ^Ray r)) never
told a non-inlined callee that its arg is a Vec3 — the callee's params stayed
unproven and its field reads kept the dynamic guard.

Seed declared hints as a param-type floor in the fixpoint: phint-seed (passes/
types) resolves an arity's :phints to positional record types via the
record-shapes registry, and infer-unit! initializes each fn's fresh param slots
from them instead of nil. A fixed declared type can't poison the least-fixpoint
the way an early-iteration :any would, and a hinted param now propagates its
(and its field reads') types to its callees during inference.

Scope: this closes the hinted-propagation gap. It does NOT help the ray tracer,
which uses zero ^-hinted params (only hinted fields) — its remaining type gap is
unhinted record-param inference on recursive/non-inlined hot fns, and per the
jolt-15jq A/B it's allocation-bound regardless (jolt-8flj). Tracked on the bead.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-16 03:38:03 +00:00 committed by GitHub
parent 30a12f39ff
commit bd9e542c78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 4 deletions

View file

@ -17,7 +17,7 @@
[jolt.passes.fold :refer [const-fold]]
[jolt.passes.inline :refer [inline-node flatten-lets scalar-replace dirty set-rec-shapes!]]
[jolt.passes.types :refer [run-inference
check-form infer-body reinfer-def
check-form infer-body reinfer-def phint-seed
set-rtenv! set-vtypes! join-types
set-record-shapes! set-map-shapes! set-protocol-methods!
reset-escapes! collected-escapes

View file

@ -791,6 +791,24 @@
(get fnode :arities))))
def-node)))
(defn phint-seed
"Positional declared-hint type seeds for a fn arity. Given the param-name
vector and the arity's :phints (a seq of [name ctor-key] pairs), return a
vector parallel to params whose slot i is the resolved record TYPE of that
param's ^Record hint (via the record-shapes registry), or nil. The
whole-program fixpoint seeds these as a param-type FLOOR so a declared hint
propagates to a fn's callees DURING inference not only at the final re-emit
(reinfer-def). Without it a hinted param with no callers stays :any through the
fixpoint, so a field read off it (e.g. (:origin ^Ray r)) never tells a shared
callee its arg is a Vec3 (jolt-3ko)."
[params phints]
(let [m (reduce (fn [acc pr] (assoc acc (nth pr 0) (nth pr 1))) {} phints)]
(mapv (fn [nm]
(let [ck (get m nm)
e (and ck (get @record-shapes-box ck))]
(when e (record-type-from-entry e type-depth))))
params)))
;; Piggyback checking (jolt audit). In direct-link mode infer-top already runs
;; one inference pass for specialization; turning checking? on during it makes
;; the success checker nearly free there (no extra traversal — just the