flan/test/programs/global-init.flan
Joseph Ferano 9ce51ba94e One slice over everything with elements, and the warning at the push
as-slice was a warning, not an operation. The input type already decides
which of the two things happens — a Vec can only be borrowed, an array or a
string can only be viewed, and no call site picks between them — so the second
name expressed no choice a reader could make. And it warned at the moment the
view is taken, which is the one moment nothing is wrong; the danger arrives
later, at the push. slice now takes a Vec at all three arities and as-slice
is gone.

(slice v lo) was free, and is the arity the Vec never had: the runtime already
reads a hi of -1 as "to the end", so the tail form passes the caller's lo and
the same -1 — no slot, no length read, no second evaluation. The merge is
entirely in the checker; the Vec path builds the flan_vec_as_slice call it
always built and neither backend has a line about any of it.

A Vec a call returned is refused at every arity, and not for the array's
reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the
storage outlives the expression — but the header is a temporary, so nothing
can ever free the block. The refusal says that and names the let.

The name's own refusal sits in ordinary_call after every table, so a program
that defines an as-slice still reaches its own. It reads for somebody who has
never heard of the old name and writes the call back out, spelling each
argument that is a name or a number.

The warning moved to where it bites: BUILT.md gains a section beside the Vec
table and the push row points at it, spec-memory.md's Borrowing says the same.
Investigated and deliberately not built — a diagnostic for a live view at the
push. (reserve v 100) then a slice, a push and a read is correct code under
the contract the spec chose, so any flag on it is a false positive by the
language's own semantics rather than by an approximation. FIX.org has the
finding and the syntactic sketch that does not work.
2026-09-21 09:51:35 +07:00

90 lines
3.5 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: (defonce 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.
(defonce derived i64 (* base 10))
(defonce base i64 (+ 1 2))
(defn twice-base [] i64 (* base 2))
;; Names no global; the function it calls does.
(defonce via-fn i64 (+ (twice-base) 1))
;; The author's arena, and the Vec it is meant to hold.
(defonce frame Allocator (arena-new 262144))
;; Control flow, each shape in its own global.
(defn maybe [] (Option i32) (Some 3))
(defonce matched i32 (match (maybe) (Some x) x None 0))
(defonce branched i64 (if (> base 2) (let [k (+ base 1)] (* k 2)) 0))
(defonce 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.
(defonce shape Shape (Shape.Circle {.r 7}))
;; And a container whose real value only exists behind an allocator.
(defonce names (Vec i64) (vec-new i64))
;; [def], the re-run form, in a plain run — where there is no re-run, so each
;; is simply its initialiser's value. What these four pin is that every
;; spelling lowers and starts identically on both backends, with the
;; initialiser lifted into [global/<n>] whatever it is (a def's always is,
;; zero and literal included) rather than written into the image.
(def d-zero [2 u32])
(def d-typed i64 (+ base 2))
(def d-const i64 5)
(def d-dyn 6)
(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 (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)
(println (i32 (at d-zero 0)))
(println d-typed)
(println d-const)
(println d-dyn)
0)