Editing a defvar or a defn is a symbol the host exports. Adding one is not: there is nothing to bind to and ELF cannot grow a symbol. runtime/flan_dev.c is the two lookups that cover it - flan_dev_cell for a new function's cell, flan_dev_global for a new global's storage - both idempotent, so the second module to mention a name gets what the first one got. That is the whole point: two modules with their own copy of a new function would each call their own, and redefining it would update one of them. The compiler picks per name. A name the host has is a symbol and costs one load at a call site; a name it lacks is a registry lookup cached at install time in a module-local slot, and costs two. The common case pays nothing for the general one. The redefinition unit is now a list of top-level forms rather than one function. It has to be: v3 of the fixture adds a var and uses it from a redefined bump, and splitting that into two loads leaves a module referring to storage that does not exist yet. C-c C-c passes one name, C-c C-k passes a file's worth, one path either way. Four rules, each silent if broken. Every lookup resolves before any body is published, or a caller reaches a function whose slots are still null - asserted on the emitted flan_reload_install, since it cannot be race-tested. flan_dev_global refuses a size change, which is the layout-drift rule's first enforcement point rather than another exception to it. Nothing is ever dlclosed, because a cell holds an address inside a module's text. And the table is fixed capacity, because a module holds a cell's address for as long as it is loaded and a realloc would strand it. The test that separates this from a plausible wrong version is v4, which redefines a name v3 introduced at run time. v3's bump is already installed and is not rebuilt, so it picks v4 up only if its call goes through a cell both modules found by the same name. Had v3 cached the function's address instead, every other assertion would still pass and the transcript would read 246 instead of 432. Sizes are spelled LLVM's way, ptrtoint getelementptr null 1, rather than by a layout calculator in OCaml that would have to agree with LLVM's on every target.
25 lines
823 B
Plaintext
25 lines
823 B
Plaintext
;;;; v4 redefines only [added] — itself introduced at run time by v3, so it
|
|
;;;; lives in the registry and not in any symbol table.
|
|
;;;;
|
|
;;;; This is the case that separates a real implementation from a plausible
|
|
;;;; one. v3's [bump] is already installed and is not rebuilt here, so it picks
|
|
;;;; this up only if its call to [added] goes through a *cell* that both
|
|
;;;; modules found by name. Had v3 cached the address of the function instead
|
|
;;;; of the address of its cell, everything else would still pass and this
|
|
;;;; would silently keep running v3's [added].
|
|
(defvar counter i64)
|
|
(defvar extra i64)
|
|
|
|
(defn helper [x i64] i64 (* x 2))
|
|
|
|
(defn added [] i64
|
|
(set extra (+ extra 100))
|
|
extra)
|
|
|
|
(defn bump [] i64
|
|
(print-line "v3")
|
|
(set counter (+ counter (added)))
|
|
(helper counter))
|
|
|
|
(defn outer [] i64 (bump))
|