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:
parent
c6b964c1b1
commit
e61a175f91
1 changed files with 16 additions and 3 deletions
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue