feat: completeness preservation — shapes survive cap and same-shape joins (jolt-t34 R2)

The inference dropped the complete :shape whenever it rebuilt a struct type
(cap) or joined two (join-t/merge-fields), so a vec3 retrieved from a container
or a fn param typed across call sites lost its layout and every field read fell
to the slow descriptor path. Two fixes:

- cap preserves :shape: capping truncates field VALUES below the depth limit but
  never the key SET, so the layout is still complete. It also recurses into
  fields, so a shaped value nested in a container (a vec3 inside a hit-info)
  keeps its own :shape — which is what lets (:r (:normal hit-info)) bare-index.
- join-t preserves :shape when both sides are the SAME complete shape (the
  merged struct has the same keys); different shapes still drop it. This carries
  the shape through if-joins and the inter-procedural fixpoint's call-site joins.

Result: the ray tracer goes from 22s (R1, correct-but-descriptor-path) to 4.36s
— 2.7x FASTER than the 11.7s no-shape baseline, and ~3x the JVM (was 8.5x), with
byte-identical output. The compounding of cheaper tuple construction plus
bare-index reads across the whole render far exceeds the per-op estimate.

Gate green flag-off, suite 4718, default-path bench even, transparency intact.
This commit is contained in:
Yogthos 2026-06-13 18:30:22 -04:00
parent c6b964c1b1
commit e61a175f91

View file

@ -782,7 +782,14 @@
(= a b) a
(nil? a) b
(nil? b) a
(and (struct-type? a) (struct-type? b)) (mk-struct (merge-fields (sfields a) (sfields b)))
(and (struct-type? a) (struct-type? b))
(let [merged (mk-struct (merge-fields (sfields a) (sfields b)))]
;; joining two values of the SAME complete shape preserves it — the
;; merged struct has the same key set (jolt-t34 R2). Different shapes
;; (or an incomplete side) drop it, as the layout is no longer proven.
(if (and (get a :shape) (= (get a :shape) (get b :shape)))
(assoc merged :shape (get a :shape))
merged))
(and (vec-type? a) (vec-type? b)) (mk-vec (join-t (velem a) (velem b)))
(and (set-type? a) (set-type? b)) (mk-set (join-t (selem a) (selem b)))
;; differing kinds: form a scalar union when both sides reduce to scalars
@ -797,8 +804,14 @@
(defn- cap [t d]
(cond
(<= d 0) (if (or (struct-type? t) (vec-type? t) (set-type? t)) :any t)
(struct-type? t) (mk-struct (reduce (fn [m k] (assoc m k (cap (get (sfields t) k) (dec d))))
{} (keys (sfields t))))
(struct-type? t)
;; capping truncates VALUES below depth d, but the KEY SET is unchanged, so
;; a complete :shape survives — keep it so nested/container field reads can
;; still bare-index (jolt-t34 R2). cap recurses into fields, so a nested
;; shaped value (a vec3 inside a hit-info) keeps its own :shape too.
(let [capped (mk-struct (reduce (fn [m k] (assoc m k (cap (get (sfields t) k) (dec d))))
{} (keys (sfields t))))]
(if (get t :shape) (assoc capped :shape (get t :shape)) capped))
(vec-type? t) (mk-vec (cap (velem t) (dec d)))
(set-type? t) (mk-set (cap (selem t) (dec d)))
:else t))