Keep record :type through depth-capping (jolt-3ko)

cap truncates a deep type's field VALUES to :any so the inter-procedural
fixpoint stays finite, but it rebuilt the struct via mk-struct and dropped the
record :type tag along the way. The tag is identity — independent of field
depth — so a record stored in a deep container (a Sphere in a world vector, a
material on a hit) degraded to a plain struct, and devirtualization (jolt-41m)
and record? folding silently stopped firing on it.

Preserve :type alongside :shape when capping. Verified: a protocol call on a
record read out of a vector now devirtualizes (the call node gets :devirt-type,
which needs the receiver's record type). Sound — the tag stays accurate; only
field values below the depth cap are truncated.

No measurable wall-clock change on its own (jolt's protocol dispatch is already
cheap), but it restores the record fast path / devirt / record?-folding on
records-in-containers, and unblocks downstream work that keys off record types.
This commit is contained in:
Yogthos 2026-06-15 20:18:37 -04:00
parent 04d3e192ed
commit 5cc39a8d7a
2 changed files with 68 additions and 2 deletions

View file

@ -111,8 +111,14 @@
;; 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))
{} (keys (sfields t))))
;; the record :type tag (and :shape) are independent of field-value
;; depth, so they survive truncation — a record read from a deep
;; container keeps its identity, so devirtualization (jolt-41m),
;; record? folding, and the record fast path still fire on it.
capped (if (get t :shape) (assoc capped :shape (get t :shape)) capped)
capped (if (get t :type) (assoc capped :type (get t :type)) capped)]
capped)
(vec-type? t) (mk-vec (cap (velem t) (dec d)))
(set-type? t) (mk-set (cap (selem t) (dec d)))
:else t))