;;;; 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.] — 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) ;; A def initialiser that *reads another global*, which is a claim the static ;; ordering analysis cannot make: the lifted function loads [counter] when it ;; runs, and it runs again on every re-run, so this follows the value the last ;; run left rather than the value the first startup saw. [counter] is a ;; defonce that climbs 41, 42, 43, 44, and the startup store lands before main ;; increments it — so this prints 40, 41, 42, 43, one behind, and a def that ;; had captured its initialiser's first answer would print 40 four times. (def echo i64 (+ counter 0)) ;; 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 "echo ") (print echo) (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)