records use declared-shape layout with fast field access by default (jolt-t34)
Records (defrecord/deftype) are now shape-recs in a direct-linking unit by
default — no JOLT_SHAPE flag. A record's shape is DECLARED, so the inference
proves field reads by a lookup, not fragile shape inference, and they bare-index.
Result: ~1.4x faster than the :jolt/deftype table form on a record-heavy loop
(3.9s vs 5.5s), driven by cheaper construction + proven bare-index reads.
Two gates now:
- :shapes? — shape-recs active; records use declared-shape layout + bare
index reads. On with direct-linking (where the inference runs).
- :map-shapes? — also shape generic const-key maps. Opt-in (JOLT_SHAPE), because
shaping maps net-loses on unproven reads (measured). Records win.
- call-ret-type types a record ctor (->Name) as a struct of its declared shape,
fed from a ctx-env registry populated at deftype; field reads on the result
bare-index. (set-record-shapes!/set-map-shapes! wired through infer-unit!.)
- sidx reads the field's position from the :shape vector AS-IS (declared order
for records, str-sorted for map literals) — no re-sort — so any field order
bare-indexes correctly. The map :map case only sets :shape under :map-shapes?.
- record-shape-for interns the descriptor per (type, fields), not per type: a
record redefined with different fields now gets a fresh descriptor instead of a
stale one (fixes redef descriptor staleness; old instances stay valid).
Adds record-declared-shape-test (declared-order reads, incl. non-alphabetical
fields, through fn boundaries + protocol method bodies). Known pre-existing edge
case filed as jolt-wf4 (direct (:f (->R …)) read returns nil after a record is
redefined with different fields; let-bound read works; repros without shapes).
This commit is contained in:
parent
4e7b8f792f
commit
f5a5b25d59
7 changed files with 111 additions and 26 deletions
39
test/integration/record-declared-shape-test.janet
Normal file
39
test/integration/record-declared-shape-test.janet
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Records use declared-shape layout with fast field access (jolt-t34), by default
|
||||
# in a direct-linking unit — no JOLT_SHAPE needed. The key property: a record is
|
||||
# laid out in DECLARED field order, and field reads bare-index by that order, so
|
||||
# fields that are NOT alphabetically sorted must still read correctly. This is
|
||||
# what `sidx` reads off the :shape vector (declared order, not str-sorted).
|
||||
(use ../../src/jolt/api)
|
||||
|
||||
(var failures 0)
|
||||
(defn- check [label got want]
|
||||
(unless (= got want)
|
||||
(++ failures)
|
||||
(printf "FAIL [%s] got %q want %q" label got want)))
|
||||
|
||||
# A direct-linking ctx: records are shape-recs, reads proven/bare-indexed.
|
||||
(def dl (init {:compile? true :direct-linking? true}))
|
||||
|
||||
# --- representation: a record is a shape-rec (tuple), not a table -------------
|
||||
(check "record is a shape-rec"
|
||||
(tuple? (eval-string dl "(do (defrecord Sp [x y]) (->Sp 1 2))")) true)
|
||||
|
||||
# --- DECLARED-ORDER field access: fields are NOT alphabetically sorted; each
|
||||
# must read its own value, locally and through a fn boundary. Each case uses a
|
||||
# DISTINCT record name (redefining a record with new fields is jolt-wf4). -------
|
||||
(def cases
|
||||
[["decl-order local" "(do (defrecord Ra [b a c]) (let [r (->Ra 10 20 30)] (= [10 20 30] [(:b r) (:a r) (:c r)])))"]
|
||||
["decl-order via fn" "(do (defrecord Rb [b a c]) (defn rdb [r] [(:b r) (:a r) (:c r)]) (= [10 20 30] (rdb (->Rb 10 20 30))))"]
|
||||
["single field z-first" "(do (defrecord Rc [z m a]) (= 7 (:z (->Rc 7 8 9))))"]
|
||||
["protocol method body" "(do (defprotocol Sh (area [s])) (defrecord Box [w h] Sh (area [b] (* (:w b) (:h b)))) (= 12 (area (->Box 3 4))))"]
|
||||
["record? true" "(do (defrecord Rd [x y]) (record? (->Rd 1 2)))"]
|
||||
["record vs map not=" "(do (defrecord Re [x y]) (not (= (->Re 1 2) {:x 1 :y 2})))"]
|
||||
["assoc keeps type" "(do (defrecord Rf [x y]) (record? (assoc (->Rf 1 2) :x 9)))"]
|
||||
["pr declared order" "(do (defrecord Rg [b a c]) (= \"#user.Rg{:b 10, :a 20, :c 30}\" (pr-str (->Rg 10 20 30))))"]])
|
||||
|
||||
(each [label prog] cases
|
||||
(check label (eval-string dl prog) true))
|
||||
|
||||
(if (pos? failures)
|
||||
(do (printf "record-declared-shape: %d failure(s)" failures) (os/exit 1))
|
||||
(print "record-declared-shape: all cases passed"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue