flan/test/programs/dev-rerun.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

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)