perf: structural type inference (RFC 0005) — nested access typed, hint-free

Replace the ad-hoc inference lattice (a flat :struct-map tag plus {:vec ELEM})
with one recursive structural type: {:struct {field -> T}}, {:vec T}, {:set T},
scalar tags, and :any. A keyword lookup now returns its field's type, so nested
access like (:r (:direction ray)) is typed end to end and drops its guard. join
is field-wise and element-wise with a depth cap of 4 so the inter-procedural
fixpoint still terminates.

The back end honors a struct hint on any subject node, not just locals, so an
inferred field type on a nested lookup specializes. The orchestrator's fixpoint
joins through the portable join-types so compound types no longer collapse to
:any.

Ray tracer goes 12.8s to 11.0s with no hints, matching the explicit ^:struct
version (10.9s). Render checksum unchanged (1915337), full gate green,
conformance x3 modes pass.

jolt-5uj
This commit is contained in:
Yogthos 2026-06-13 10:44:40 -04:00
parent e7473f38cf
commit 9bc7b27245
4 changed files with 127 additions and 36 deletions

View file

@ -29,11 +29,12 @@
(def report (backend/infer-unit! ctx "p1"))
# --- the fixpoint computed the right param types -----------------------------
# rd's param v flows from mk's struct-map result (mk inlines to a struct literal
# in drv) and stays struct across the recursive self-call -> :struct-map
(assert (= :struct-map (in (get report "p1/rd") 0)) (string "rd param v: " (in (get report "p1/rd") 0)))
# rd's param v flows from mk's struct result (mk inlines to a struct literal in
# drv) and stays struct across the recursive self-call -> a {:struct ...} type
(defn struct-type? [t] (truthy? (get t :struct)))
(assert (struct-type? (in (get report "p1/rd") 0)) (string "rd param v: " (in (get report "p1/rd") 0)))
# esc escaped (passed to mapv) -> param stays unknown (:any / nil), NOT struct
(assert (not= :struct-map (in (get report "p1/esc") 0)) "escaping fn param not inferred struct")
(assert (not (struct-type? (in (get report "p1/esc") 0))) "escaping fn param not inferred struct")
# --- the seeded re-inference drops the guard for a struct param --------------
# (on a FRESH analysis, since infer-unit! re-stashes the already-specialized body)
@ -42,7 +43,7 @@
(def rd-def (backend/analyze-form ctx (reader/parse-string "(defn rdx [v n] (if (< n 1) (:r v) (rdx v (dec n))))")))
(defn guards-seeded [ptmap]
(length (string/find-all ":jolt/type" (string/format "%p" (backend/emit-ir ctx ((types/var-get reinfer) rd-def ptmap))))))
(assert (= 0 (guards-seeded @{"v" :struct-map})) "struct param -> bare lookup")
(assert (= 0 (guards-seeded @{"v" {:struct {}}})) "struct param -> bare lookup")
(assert (= 1 (guards-seeded @{})) "no param type -> guard kept")
# --- correctness: recompiled unit still computes the same --------------------

View file

@ -21,13 +21,13 @@
# a reduce closure's element param gets the vector's element type
(def red "(defn f [coll] (reduce (fn [acc h] (+ acc (:r h))) 0 coll))")
(assert (= 0 (guards red @{"coll" {:vec :struct-map}})) "reduce element typed -> bare lookup in closure")
(assert (= 0 (guards red @{"coll" {:vec {:struct {}}}})) "reduce element typed -> bare lookup in closure")
(assert (= 1 (guards red @{"coll" {:vec :any}})) "reduce over vector of unknown -> guard kept")
(assert (= 1 (guards red @{})) "untyped coll -> guard kept")
# mapv over a vector-of-structs types the closure element too
(def mp "(defn g [coll] (mapv (fn [h] (:r h)) coll))")
(assert (= 0 (guards mp @{"coll" {:vec :struct-map}})) "mapv element typed -> bare lookup")
(assert (= 0 (guards mp @{"coll" {:vec {:struct {}}}})) "mapv element typed -> bare lookup")
(assert (= 1 (guards mp @{"coll" {:vec :any}})) "mapv over unknown element -> guard")
# element type is DERIVED, not just seeded: a vector literal of structs, reduced