Scalar-replace short-lived record allocations (jolt-15jq) (#132)

scalar-replace already folds non-escaping const-key map literals
((:k {:k a ..}) -> a, and drops a let-bound map that doesn't escape).
Extend the same fold to record constructors: a (->Rec a b c) is a
positional struct whose declared field order lives in the record-shapes
registry, so a field read on a non-escaping ctor folds to the matching
positional arg and the allocation disappears.

Direct form (:field (->Rec ..)) and the let-bound form both handled,
threaded through run-passes via a per-unit shape registry (new
jolt.host/record-shapes accessor). Soundness: ctor args must be pure
(duplicated/discarded like map vals), arg count must equal the field
count, and only declared-field reads fold — a record answers the virtual
:jolt/deftype key with its type tag and any other key with nil, neither
of which is a positional arg, so those keep the allocation. pure? now
treats a record ctor of pure args as pure, so nested records (a Ray
holding a Vec3) fold bottom-up.

Allocation-bound microbench (non-escaping record built + field-read in a
hot loop): 69.6s -> 2.4s, landing on the no-record arithmetic baseline.
The ray tracer is unchanged — its vec3 results escape (returned/stored
each op), so they genuinely allocate; that's a separate problem.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-16 01:21:36 +00:00 committed by GitHub
parent ae593b0f66
commit 048de0200d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 204 additions and 28 deletions

View file

@ -239,6 +239,13 @@
# not just one whose ->Name happens to be interned in the current ns).
(if (record-hint-ctor-key ctx name) true false))
# The record-ctor shape registry ("ns/->Name" -> {:fields (:k ..) :type tag ..}),
# or an empty table. Lets scalar-replace (jolt-15jq) recognize a (->Rec ..) call
# and map its positional args to the declared field order — the record analogue
# of the inline map-literal keys the map fold already sees in the IR.
(defn h-record-shapes [ctx]
(or (get (ctx :env) :record-shapes) @{}))
(def- exports
{"form-sym?" h-sym? "form-sym-name" h-sym-name "form-sym-ns" h-sym-ns
"ref-put!" h-ref-put!
@ -255,7 +262,7 @@
"host-intern!" h-intern!
"inline-enabled?" h-inline-enabled? "inline-ir" h-inline-ir
"record-type?" h-record-type? "form-position" h-form-position
"record-ctor-key" h-record-ctor-key})
"record-ctor-key" h-record-ctor-key "record-shapes" h-record-shapes})
(defn install! [ctx]
(def ns (ctx-find-ns ctx "jolt.host"))