flan/test/programs/registry.flan
Joseph Ferano a4c6b996ff def re-runs its initialiser, and defvar is renamed defonce
The trio the author decided on 2026-09-20 is now all built: def is CL's
defparameter — its initialiser runs on every daemon re-run, unguarded, so
an edited initialiser repaints the same storage on C-c C-c plus re-run —
defonce (Clojure's name for CL's defvar, per the author) initialises once
behind the .init~once. flag, and defconst stays the image.

One parse arm reads both forms; the difference is Ast.reinit, carried to
Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and
Check.check_global lifts every def initialiser — zero and literal
included — into global/<n>, so the host's startup reaches it through the
function cell and a re-evaluated def swaps it (Session's def_inits;
Emit.redefinition declares the cell for a non-sibling target). The old
defvar spelling is refused with the rename and both compiling spellings,
and every program, test, doc and editor list is swept — except sand.flan,
the author's live WIP, whose seven defvar lines are flagged in FIX.org
and keep its three dependent tests red on this branch.
2026-09-21 07:12:04 +07:00

67 lines
3.1 KiB
Plaintext

;;;; The allocation registry — NEXT.md, "a dev-build 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 (len 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)