The x86 backend ran initialisers from .init_array and the LLVM one refused them by name, so (defvar frame Allocator (arena-new 262144)) — which the author kept writing — was a program on one backend and an error on the other. A rule that holds on one backend and not the other is not a rule. The checker lifts a computed initialiser into a function of its own and the global's initialiser becomes the call. That is what gives it a frame, which is the bug underneath the feature: a `let` or a `match` in an initialiser indexed a slot array of length zero and took the x86 emitter down with an uncaught Invalid_argument. Both backends call the lifted initialisers from main, after flan_rt_init and before a line of the program's own code — Odin's __$startup_runtime shape, not a constructor, so the runtime is up and the order is the compiler's to choose. x86 keeps .init_array for one thing only, and it is named: writing the constant image this backend has no folder for, which is standing in for the other backend's object image rather than for a program. The computed globals are sorted by what they read, transitively through the functions they call, so a global written above the one it reads works and a ring is refused with every name in it. A reload still re-runs nothing: a new global with a computed initialiser starts as ZII on both backends. The refusal that lived in x86.ml is now the checker's and is narrower. Nothing can escape an initialiser — the handler and restart stacks are empty and every frame it pushes it also pops — so what is refused is a signal or an invoke-restart with no handler-bind or restart-case around it, which is inert by construction. A restart-case inside one is ordinary code, which is what makes (defvar data (Vec u8) (slurp "level.edn")) an ordinary program. Three refusals go with the premise they rested on: a container global with a computed initialiser, a union member in a defvar, and a data type case in one. A defconst is untouched and keeps all three. One change here is not about any of that. sand.flan carried an unfinished line — (defvar game-data (embed (with-allocator frame ))), which parses as a declaration whose type is (embed ...) — so the checker refused the file and `dune test` was red at the tip of dev-loop before a line of this landed, verified by stashing this work and rebuilding. It is commented out rather than guessed at: the arena above it is the half that works, and what the global should read is the author's to decide.
76 lines
2.9 KiB
Plaintext
76 lines
2.9 KiB
Plaintext
;;;; Computed global initialisers — plan.org, Data model.
|
|
;;;;
|
|
;;;; A global whose value is not something a linker can write into the image.
|
|
;;;; The initialiser is lifted into a function of its own and called at startup,
|
|
;;;; from main, after the runtime is up and before a line of the program's own
|
|
;;;; code — Odin's __$startup_runtime shape rather than a constructor. Both
|
|
;;;; backends do it the same way, which is what the x86 survey is comparing.
|
|
;;;;
|
|
;;;; What each half of this file is asserting:
|
|
;;;;
|
|
;;;; - an arena allocated at startup and used from main, which is the form
|
|
;;;; the author kept writing: (defvar frame Allocator (arena-new N)).
|
|
;;;; - the order. `derived` is written above the global it reads, so the
|
|
;;;; declaration order is the wrong one and the sort is what makes it 30.
|
|
;;;; - a dependency that runs through a call rather than through the text of
|
|
;;;; the initialiser: `via-fn` names no global at all, and the function it
|
|
;;;; calls reads one.
|
|
;;;; - control flow in an initialiser. Every one of these needs a frame, and
|
|
;;;; before the lift there was none — a `let` in an initialiser indexed a
|
|
;;;; slot array of length zero and took the compiler down with it.
|
|
;;;; - a container loaded by its own initialiser, and a data type case
|
|
;;;; written into a global. Both were refused while there was nowhere for
|
|
;;;; an initialiser to run.
|
|
|
|
(defdata Shape [Nothing (Circle [r i32]) (Square [side i32])])
|
|
|
|
;; Written above `base`, and it reads it.
|
|
(defvar derived i64 (* base 10))
|
|
(defvar base i64 (+ 1 2))
|
|
|
|
(defn twice-base [] i64 (* base 2))
|
|
;; Names no global; the function it calls does.
|
|
(defvar via-fn i64 (+ (twice-base) 1))
|
|
|
|
;; The author's arena, and the Vec it is meant to hold.
|
|
(defvar frame Allocator (arena-new 262144))
|
|
|
|
;; Control flow, each shape in its own global.
|
|
(defn maybe [] (Option i32) (Some 3))
|
|
(defvar matched i32 (match (maybe) (Some x) x None 0))
|
|
(defvar branched i64 (if (> base 2) (let [k (+ base 1)] (* k 2)) 0))
|
|
(defvar counted i64 (let [t (i64 0)] (while (< t 4) (set t (+ t 1))) t))
|
|
|
|
;; A data type case: a store at startup, where a constant would have needed a
|
|
;; byte-level encoder for the payload blob.
|
|
(defvar shape Shape (Shape.Circle {.r 7}))
|
|
|
|
;; And a container whose real value only exists behind an allocator.
|
|
(defvar names (Vec i64) (vec-new i64))
|
|
|
|
(defn main [] i32
|
|
(println derived)
|
|
(println base)
|
|
(println via-fn)
|
|
(println matched)
|
|
(println branched)
|
|
(println counted)
|
|
(match shape
|
|
(Circle r) (println r)
|
|
(Square s) (println s)
|
|
Nothing (println -1))
|
|
;; The Vec was made with the default allocator at startup and is still the
|
|
;; program's when main runs.
|
|
(push names 11)
|
|
(push names 22)
|
|
(println (len (as-slice names)))
|
|
(println (at names 1))
|
|
(free names)
|
|
;; And the arena, used the way sand.flan means to use it.
|
|
(with-allocator frame
|
|
(let [v (vec-new i64)]
|
|
(push v 7)
|
|
(println (at v 0))))
|
|
(free-all frame)
|
|
0)
|