flan dev's merged build is the program and the compiler in one -rdynamic executable, so it exports every flan.* body it has, and ELF gives it precedence over anything dlopened afterwards. The compiler expands a macro by dlopening a module into that same process, and the module is built by Emit.program whatever backend the session uses -- so under --x86 the caller was LLVM's and the body it landed in was the dev backend's, which is a crossed pair. It died with SIGSEGV inside flan.[clamp] during the first expansion, before the program had run a line, and Dev.start refused the combination rather than do that. Build.macro_module now asks Emit.program for hidden visibility on the module's own Flan definitions. There is nothing left for the host to interpose, and the flan.macro.* thunks stay exported because dlsym is how the compiler reaches them -- nm -D on the built module lists those three and nothing else of Flan's. The -Wl,-Bsymbolic that had been binding everything locally since 65d14f4 goes with it: the module links its own flan_rt.c, and binding that locally aimed its calls at a runtime flan_rt_init never ran on, with a null flan_exit_hook, so a trap raised inside an expansion would have exited the process instead of parking it. Nothing about the host moved, which is what keeps redefinition modules reaching its cells, its globals and flan_dev_cell. hidden defaults to false, and the 540 IR files this compiler emits for the test corpus are byte-identical to the ones before it. test_dev.ml's assertion that the merged daemon refuses --x86 becomes the session it was standing in for: dev-macro.flan calls a prelude macro at the top level, so the daemon coming up at all is the old crash not happening, and one build then carries C-x C-e, a C-c C-c whose body calls a macro again, the park and the rerun.
44 lines
1.8 KiB
Plaintext
44 lines
1.8 KiB
Plaintext
;;;; dev-loop.flan's shape, with a macro in it, and the macro is the whole
|
|
;;;; point of the file.
|
|
;;;;
|
|
;;;; [flan dev]'s merged daemon is the program and the compiler in one process,
|
|
;;;; and the compiler expands a macro by dlopening a module into *itself* --
|
|
;;;; which is now also the program's address space. The module is always built
|
|
;;;; by LLVM whatever backend the session uses, and the host is linked
|
|
;;;; -rdynamic so a redefinition module can reach its cells, so until
|
|
;;;; Build.macro_module hid the module's own Flan definitions the host's bodies
|
|
;;;; interposed them. Under --x86 that is a crossed pair and it was a SIGSEGV
|
|
;;;; during the first expansion, before the program had run a line.
|
|
;;;;
|
|
;;;; So [unless] is called twice over: once here at the top level, which is the
|
|
;;;; expansion that used to kill the daemon on its way up, and once from a body
|
|
;;;; typed in later, which is the same expansion with the program already
|
|
;;;; running beside it.
|
|
(import agent "vendor:agent")
|
|
|
|
(defvar ticks i64)
|
|
|
|
(defn parity [n i64] string
|
|
(let [out "even"]
|
|
(unless (= 0 (% n 2))
|
|
(set out "odd"))
|
|
out))
|
|
|
|
(defn step [] i64
|
|
(set ticks (+ ticks 1))
|
|
ticks)
|
|
|
|
;;; A *bounded* run of frame boundaries, where dev-loop.flan waits for a
|
|
;;; delivery and dev-repl.flan runs long enough to outlast a whole test file.
|
|
;;; This program is here to park, and it has to park whether or not the thing
|
|
;;; that was delivered to it is one [agent/wait] reports — so the clock ends
|
|
;;; it rather than the editor does. Two seconds is long enough for a client to
|
|
;;; be served twice and short enough to be well inside a watchdog.
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-dev-macro-fallback.sock")
|
|
(println (parity (step)))
|
|
(dotimes [i 400]
|
|
(agent/wait 5))
|
|
(println (parity (step)))
|
|
0)
|