flan_dev_reg_dead was reaching for the containment scan, and it is on the free path: a dev build would have paid a 4096-entry sweep per free. A free hands back the base address the allocator gave out, which is what the slot is keyed on, so the question there is equality and never containment. Only free-all needs the scan, and that runs once a frame. The table is allocated when it is armed, not declared. A fixed array was a quarter of a megabyte of BSS in a shipped game for a table that build never writes; now a release build carries a null pointer and the not-taken branch. The pointer arm binds its subject to a slot before naming it three times — the slice arm's rule, and its reason: an inspect with a path reaches a leaf through a bounds check, and three of those to render one pointer is the walk paying for its own shape. dev-ptr.flan shows both halves on a stopped stack. It was read by hand; the test_dev.ml case that would drive it is another lane's file, and NEXT.md says so.
47 lines
1.6 KiB
Plaintext
47 lines
1.6 KiB
Plaintext
;;;; A stopped stack holding two pointers into Vec storage, one of which is
|
|
;;;; already dead. The allocation registry is what lets the inspector tell
|
|
;;;; them apart, and this is the program that shows it:
|
|
;;;;
|
|
;;;; ("live" "(Ptr Enemy)" "<ptr (Enemy {.hp 41 .x 2})>")
|
|
;;;; ("dead" "(Ptr Enemy)" "<ptr dead: was Enemy, freed at step 15>")
|
|
;;;;
|
|
;;;; Both lines are what `(:op "locals" :frame 1)` answers with today, and
|
|
;;;; both were read off a running session by hand. **No test drives this
|
|
;;;; program yet**: the case belongs beside the other `locals` and `inspect`
|
|
;;;; cases in test_dev.ml, which is another lane's file. NEXT.md says so
|
|
;;;; rather than letting the verification read as automated.
|
|
;;;;
|
|
;;;; Why a Vec rather than a struct on the stack: a stack address is not in
|
|
;;;; the registry by design — the shadow stack already answers for a local by
|
|
;;;; name — so a pointer to one renders <ptr>, which is neither half of what
|
|
;;;; this is demonstrating.
|
|
(import agent "vendor:agent")
|
|
|
|
(defstruct Boom [why i32])
|
|
(defstruct Enemy [hp i32 x i32])
|
|
|
|
(defn deeper [] i64
|
|
(restart-case
|
|
(do (error (Boom {.why 7})) 1)
|
|
(carry-on [] 5)))
|
|
|
|
(defn outer [] i64
|
|
(let [v (vec-new Enemy)
|
|
w (vec-new Enemy)]
|
|
(push v (Enemy {.hp 41 .x 2}))
|
|
(push w (Enemy {.hp 7 .x 9}))
|
|
(let [live (addr (at v 0))
|
|
dead (addr (at w 0))]
|
|
(free w)
|
|
(deeper))))
|
|
|
|
(defvar ticks i64)
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-ptr-fallback.sock")
|
|
(print (outer)) (println "")
|
|
(dotimes [i 4000]
|
|
(agent/wait 5)
|
|
(set ticks (+ ticks 1)))
|
|
0)
|