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.
106 lines
5.1 KiB
Plaintext
106 lines
5.1 KiB
Plaintext
;;;; What a re-run does to a global, which is decided by the form that
|
|
;;;; defined it and not by the daemon.
|
|
;;;;
|
|
;;;; A [defonce] is Common Lisp's [defvar] under Clojure's name: its
|
|
;;;; initialiser runs only if the
|
|
;;;; variable is not already initialised, so its value survives a re-run. A
|
|
;;;; plain zeroed one always did — .bss is untouched by a second entry into
|
|
;;;; main — and a computed one did not, because the startup function [main]
|
|
;;;; calls ran again from the top and stored the initial value back over
|
|
;;;; whatever the last run had left. A [defconst] whose initialiser is a
|
|
;;;; compile-time constant is written into the image and no startup code
|
|
;;;; reaches it at all, so the question does not arise for it. Since
|
|
;;;; 2026-09-20 there is no other kind: a defconst's initialiser has to be a
|
|
;;;; compile-time constant, refused in the checker so that both backends
|
|
;;;; refuse the same program. Before that the LLVM backend refused a computed
|
|
;;;; one while x86 guarded it at startup like a defonce.
|
|
;;;;
|
|
;;;; So each line printed below is a claim about one of those cases, and the
|
|
;;;; run number is the first of them: [runs] is computed, so before the fix it
|
|
;;;; counted 1, 1, 1.
|
|
(import agent "vendor:agent")
|
|
|
|
(defconst base i64 40)
|
|
|
|
;; Computed, and the whole reproduction: the initialiser is a call, so it is
|
|
;; lifted into the startup function rather than written into the image.
|
|
(defn start [] i64 base)
|
|
|
|
(defonce counter i64 (start))
|
|
|
|
;; Zero-valued, which needs no startup at all and must keep needing none.
|
|
(defonce zeroed i64)
|
|
|
|
;; A computed dyn global: the map is built by a function, rooted before the
|
|
;; startup function runs, and mutated by every run. Its contents have to
|
|
;; survive a re-run for the same reason [counter]'s value does, and its root
|
|
;; has to survive collection either way.
|
|
(defn table [] dyn {:runs 0})
|
|
|
|
(defonce state dyn (table))
|
|
|
|
;; The same thing written the short way: the third element is not a type, so
|
|
;; this is a dyn global initialised at startup — the same declaration [state]
|
|
;; is, down to the guard flag, because the rule lowers to that form and not to
|
|
;; a second one. Its value has to survive a re-run for exactly [counter]'s
|
|
;; reason, and if the new spelling had grown a startup path of its own this is
|
|
;; the line that would count 1, 1, 1, 1.
|
|
(defonce tally 0)
|
|
|
|
;; A typed array with a computed initialiser: (array-fill ...) is an
|
|
;; expression, so it is lifted into the startup function and guarded there
|
|
;; exactly as [counter]'s call is. If it were not — if a fill re-ran on every
|
|
;; entry into main — this would count 251, 251, 251, 251 instead of climbing,
|
|
;; which is [counter]'s own failure in the one shape that only an array can
|
|
;; have.
|
|
(defonce grid [2 [3 u8]] (array-fill [2 3] 250))
|
|
|
|
;; The guard flags the fix adds are the compiler's own globals, and they used
|
|
;; to be spelled [.init-once.<name>] — a name a program can write, since [.]
|
|
;; is an ordinary symbol constituent. This one is exactly the old spelling of
|
|
;; [counter]'s flag. It compiles only because the flag is mangled with a [~]
|
|
;; now; before that the dev build died at the assembler with the symbol
|
|
;; defined twice, and the flag's Bool retyped this i64 on the way. It stays
|
|
;; unprinted on purpose — the expected output is what it was.
|
|
(defonce .init-once.counter i64 7)
|
|
|
|
;; [def], Common Lisp's defparameter: its initialiser runs on every re-run,
|
|
;; with no guard flag, so the increment each run makes is painted over before
|
|
;; main reads it back. Where [tally] climbs 1, 2, 3, 4, this prints 4, 4, 4, 4
|
|
;; — and after the daemon evaluates an edited (def c 9), the next re-run
|
|
;; prints 10, which is the whole reason the form exists: an edited
|
|
;; initialiser takes effect on C-c C-c plus re-run.
|
|
(def c 3)
|
|
|
|
;; The typed-array spelling of the same form. The re-run repaints the same
|
|
;; storage — no reallocation — so the element main increments is 7 again by
|
|
;; the time it is read: 8 on every run, never 9.
|
|
(def hues [4 u32] (array-fill [4] 7))
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-dev-rerun-fallback.sock")
|
|
(set counter (+ counter 1))
|
|
(set zeroed (+ zeroed 2))
|
|
(set .init-once.counter (+ .init-once.counter 1))
|
|
(put state :runs (+ (get state :runs) 1))
|
|
(set tally (+ tally 1))
|
|
(set (at grid 0 0) (u8 (+ (i32 (at grid 0 0)) 1)))
|
|
(set c (+ c 1))
|
|
(set (at hues 0) (u32 (+ (i32 (at hues 0)) 1)))
|
|
(print "counter ") (print counter) (println "")
|
|
(print "zeroed ") (print zeroed) (println "")
|
|
(print "runs ") (print (get state :runs)) (println "")
|
|
(print "tally ") (print tally) (println "")
|
|
(print "grid ") (print (i32 (at grid 0 0))) (println "")
|
|
;; The element the run never writes, which says the fill ran at all: 250
|
|
;; on every run, and 0 if the initialiser had been skipped outright.
|
|
(print "grid-far ") (print (i32 (at grid 1 2))) (println "")
|
|
(print "base ") (print base) (println "")
|
|
(print "c ") (print c) (println "")
|
|
(print "hue ") (print (i32 (at hues 0))) (println "")
|
|
;; Long enough for a client to be served, short enough to park well inside
|
|
;; any watchdog — dev-macro.flan's clock, for its reason.
|
|
(dotimes [i 100]
|
|
(agent/wait 5))
|
|
0)
|