Asked whether a defconst could be redefined, probed it, and got ":status ok" for a change that did nothing at all - the module was built, delivered, installed, and the program went on using the old value. That is the silent-wrongness class the house rule exists to prevent, so it is now four refusals and a fix. A defconst's value is folded into its call sites - into an array length at worst, which is decided before any type resolves - so it lives in the running program's code and not only in its storage. Refused. A defenum member is the same thing: :space is erased to an i32 literal in the caller. Refused, and compared over declarations rather than over Tast.program, which carries no enums at all for exactly that reason. A defvar's initial value is deliberately not refused. Its storage holds live state the program moved past long ago, and refusing to change the initialiser would be refusing "edit the code, keep the sand". Same Tast.global record as a defconst, opposite answers, told apart by gconst. The value comparison is structural and conservative - anything it does not recognise counts as changed. Comparing emitted text would be wrong, since Emit.const on a string allocates a name off a per-module counter and two different strings in two throwaway modules both come out as @".str.0". Third: a new global's declared initial value was being dropped. flan_dev_global callocs, so (defvar n i64 42) added at run time was silently zero. It now takes the initial value as a blob, copies it on the allocation and ignores it afterwards - the second half being where "a reload must not reset the program's state" lives. In the allocation path rather than a branch at the call site, so it cannot be got wrong at one of them. Fourth: a change with no body to publish and no storage to allocate now answers "nothing to install" instead of shipping an empty module. That is what the defconst probe actually did, and it cost the program a frame's worth of reload it did not need.
36 lines
1.5 KiB
Plaintext
36 lines
1.5 KiB
Plaintext
;;;; The reload primitive's fixture, v1 (NEXT.md, dev loop step 1 and 2).
|
|
;;;;
|
|
;;;; There is deliberately no [main]: the host is reload_host.c, which links
|
|
;;;; this program and then dlopens two rebuilt copies of [bump].
|
|
;;;;
|
|
;;;; [counter] is the state that has to survive a reload — a redefinition
|
|
;;;; module declares it [external], so the loaded object writes the host's copy
|
|
;;;; and not a new one. [outer] is the call site that has to *follow* a reload:
|
|
;;;; it is compiled once, into the host, and never rebuilt, so if a redefined
|
|
;;;; [bump] runs when the host calls [outer] then the cell is doing its job.
|
|
;;;; The string is not decoration either — a redefinition module has to carry
|
|
;;;; its own constants, and a one-function module usually has none.
|
|
(defvar counter i64)
|
|
|
|
;;; Unused, and that is the point: the session's compatibility rules only get a
|
|
;;; chance to speak about a change the *checker* accepts, and retyping a var
|
|
;;; something else reads is an ordinary type error long before it is a layout
|
|
;;; question.
|
|
(defvar spare i64)
|
|
|
|
;;; Also unused, and for the same reason: a defconst's value and an enum
|
|
;;; member are folded into every call site, so a session has to refuse changing
|
|
;;; either. Nothing here reads them, so the checker has no opinion and the
|
|
;;; session's rule is the only thing that can speak.
|
|
(defconst folded i64 7)
|
|
(defenum Colour [red 0 green 1])
|
|
|
|
(defn helper [x i64] i64 (* x 2))
|
|
|
|
(defn bump [] i64
|
|
(print-line "v1")
|
|
(set counter (+ counter 1))
|
|
(helper counter))
|
|
|
|
(defn outer [] i64 (bump))
|