FIX.org, NEXT.md, DISCUSS.org, docs/DISCUSS.md and the session handoff at the root are one TODO.org now: 293 entries under seven subsystem headings, each carrying an org keyword that says where it stands. A DONE entry is a few lines saying what was decided and what that rules out; the reasoning that would not compress — the embedding spike and the four reports the hand-written x86 backend was built from — moved into docs/BUILT.md instead, and its entries point there in one line. Every entry was checked against the tree before it got a keyword, and the prose was wrong in both directions. Things the deleted files called open were built: the first-evaluation stall, main being redefinable, macro parameter lists, the type-limit constants, the array constructors, the byte fills, inc/dec, the discard's fontification, the Emacs buffers, rt_die's _exit, the backtrace surface, and the acceptance failure that could print and still exit zero. Things they called done were not: the backend reports' no-plan buckets had gone stale in the other direction, the value-dependent defvar was superseded rather than built, and macro-expansion source locations are on an unmerged lane, so that entry is NEXT and names the branch. Every comment that cited one of the five by name now cites a heading that exists, in TODO.org or in docs/BUILT.md. The session reports under docs/handoffs/ keep naming the files they worked on, because rewriting them would falsify what those sessions did; each carries a note saying where the content went.
67 lines
3.1 KiB
Plaintext
67 lines
3.1 KiB
Plaintext
;;;; The allocation registry — TODO.org, "The allocation registry".
|
|
;;;;
|
|
;;;; A Flan struct is exactly its C layout with no header and no tag word, so
|
|
;;;; nothing at run time can say what is at an address. The registry sidesteps
|
|
;;;; that: the allocator's *caller* knew the type, and a dev build writes it
|
|
;;;; down. What is asserted here is the consequence a program can see without
|
|
;;;; an inspector — whether an address is still live — and the two ways
|
|
;;;; storage dies underneath one.
|
|
;;;;
|
|
;;;; This program is deliberately readable in a release build too, and prints
|
|
;;;; a different and equally correct answer there: nothing is recorded, so
|
|
;;;; every question about an address comes back 0. The two expectations sit
|
|
;;;; side by side in the acceptance table, which is the honest way to assert
|
|
;;;; "a release build carries none of it".
|
|
|
|
(declare-c reg-on [] i32 "flan_dev_reg_enabled")
|
|
(declare-c reg-live [p (Ptr i32)] i32 "flan_dev_reg_live")
|
|
(declare-c reg-count [live i32] i64 "flan_dev_reg_count")
|
|
|
|
(defonce frame Allocator)
|
|
|
|
(defn main [] i32
|
|
;; Armed by a constructor in a dev build and never in a release one.
|
|
(println (reg-on))
|
|
|
|
;; 1. The heap tier. A pointer into a Vec's storage is live while the Vec is,
|
|
;; and the free that releases it is seen — which is the whole of "use
|
|
;; after free that names what died", minus the naming, which needs the
|
|
;; inspector to read it back.
|
|
(let [v (vec-new i32)]
|
|
(push v 7)
|
|
(push v 8)
|
|
(let [p (addr (at v 1))]
|
|
(println (reg-live p)) ; dev: 1
|
|
(free v)
|
|
(println (reg-live p)))) ; 0 either way
|
|
|
|
;; 2. The arena tier, and the hole test_valgrind.ml measures. free-all is
|
|
;; retain-capacity: the pages stay mapped and the bytes stay readable, so
|
|
;; memcheck is never told anything died and a later read of stale bytes
|
|
;; goes unnoticed. This does not tell memcheck. It tells the registry, so
|
|
;; that the same read is at least *answerable*.
|
|
(set frame (arena-new 4096))
|
|
(let [w (vec-new i32 frame)]
|
|
(push w 3)
|
|
(let [q (addr (at w 0))]
|
|
(println (reg-live q)) ; dev: 1
|
|
(free-all frame)
|
|
(println (reg-live q)))) ; 0 either way
|
|
|
|
;; 3. (bytes s) allocates — the copy is a block the registry sees, exactly
|
|
;; as a Vec's is, where the old zero-cost reinterpret was invisible to
|
|
;; every memory diagnostic because there was nothing to record. Nothing
|
|
;; else is live by here — sections 1 and 2 both released — so the live
|
|
;; count *is* the copy's block, and free-all takes it back to zero.
|
|
(let [b (bytes "copy" frame)]
|
|
(println (length b)) ; 4 — the string's length
|
|
(println (reg-count 1)) ; dev: 1 — the copy's block
|
|
(free-all frame)
|
|
(println (reg-count 1))) ; 0 either way
|
|
|
|
;; Nothing is live by now except whatever the arena's own destroy leaves, so
|
|
;; the count is a statement about the table rather than about one address.
|
|
(arena-destroy frame)
|
|
(println (reg-count 1)) ; 0 either way
|
|
0)
|