diff --git a/docs/handoffs/HANDOFF-x86-macro-visibility.md b/docs/handoffs/HANDOFF-x86-macro-visibility.md index 635df6d..d8cc99f 100644 --- a/docs/handoffs/HANDOFF-x86-macro-visibility.md +++ b/docs/handoffs/HANDOFF-x86-macro-visibility.md @@ -91,6 +91,13 @@ is what it was. Item by item, with what says so: The one thing that did change is stated in §The correction above, and it changed *back*: the macro module's calls into the C runtime bind to the host's copy again, as they did before 65d14f4. +**The invariant that keeps it that way: `~hidden:true` is passed by exactly one caller, `Build.macro_module`, +and it never passes `~dev`.** `Emit.program` refuses the pair outright now, because nothing in the types stops +someone writing it and the consequence would be a merged host that links, runs, and silently installs nothing: +a dev build's cells, globals and `flan.abi.*` marker are in the dynamic symbol table *on purpose*, for a +redefinition module to bind against. The other four call sites (`Build.executable`, `Dev.merged_executable`, +`bin/main.ml`'s `emit`, and the tests) take the default. + ## Verification | | before (`9e80147`) | after | @@ -103,8 +110,13 @@ calls into the C runtime bind to the host's copy again, as they did before 65d14 **The IR comparison is how "the LLVM path did not move" was verified, and it is the check worth repeating.** Every `test/programs/*.flan` plus `sand.flan` and `calc-me.flan`, emitted twice — once release, once `--dev` — by the compiler at `9e80147` and by this one: 540 files, `diff -rq` reports nothing. `hidden` defaults to -`false` and the only other edit to a format string adds an empty interpolation, so this is the argument made -mechanical rather than a sample. +`false` and the only other edit to a format string adds an empty interpolation, so for those two paths this is +the argument made mechanical rather than a sample. + +Be clear about what that diff does *not* say: it covers `Emit.program`'s default, which is every path but the +new one. `macro_module` deletes its `.ll` unless `opts.keep`, so the `hidden:true` output is not in it. What +stands behind the new path is `nm -D` on the linked object above — three thunks and nothing else of Flan's, +which is the property being claimed, read off the artefact rather than off the text that made it. `spike/x86/survey.sh` was **not** run here — it is forty minutes and this lane changes no lowering. It will report one more `runs-forever` when it next runs: `dev-macro.flan` waits on an agent that is not there, which @@ -112,9 +124,12 @@ is what `dev-loop.flan` and `dev-repl.flan` already do. The two properties that had to hold together, in one run each: -- **LLVM merged, `sand-headless.flan`.** Its package is `sand.flan`, which calls `rl/with-drawing`. If hidden - visibility did not cover 65d14f4's bug, the daemon would have died with the checker refusing an unqualified - `begin-drawing`. It came up and ran. +- **LLVM merged, `sand-headless.flan`.** This is the one that checks 65d14f4, and the fixture is chosen for + it: `sand-headless.flan` imports `sand.flan` as a package, `sand.flan` imports `vendor:raylib` and calls + `rl/with-drawing` — a *package's* `defmacro`, reached through an import, which is exactly the shape the + reproduced bug had. A prelude macro would not have covered it, because the prelude's macros are not the ones + whose bodies were qualified after the quasiquote became a literal. If hidden visibility did not cover that + bug the daemon would have died with the checker refusing an unqualified `begin-drawing`. It came up and ran. - **`--x86` merged, same program.** Both runs print the same grid hash, `15595743031174623232`, which is the simulation having produced identical output through two backends in a process that also expanded macros. diff --git a/lib/emit.ml b/lib/emit.ml index dba850d..fdae2c3 100644 --- a/lib/emit.ml +++ b/lib/emit.ml @@ -2971,6 +2971,18 @@ let macro_thunk m (fn : Tast.fn) = let program ?(checks = true) ?(dev = false) ?(debug = false) ?(pnames = []) ?(sanitize = false) ?(macros = []) ?(hidden = false) (p : Tast.program) : string = + (* [hidden] and [dev] are opposites and the refusal is here so that they + cannot be written together by accident. A dev build's whole point is that + its cells, its globals and [flan.abi.*] are in the dynamic symbol table + for a redefinition module to bind against; hiding them would leave a host + that links, runs, and silently installs nothing. There is no such thing as + a reloadable macro module, so nothing is lost by saying so out loud. *) + if hidden && dev then + failwith + "Emit.program ~hidden ~dev: a dev build exports its cells so that a \ + redefinition module can reach them, and hiding them would break every \ + reload. [hidden] is the macro module's flag and a macro module is not a \ + dev build."; let m = new_module ~checks ~dev ~known:(fun _ -> true) ~debug ~sanitize p in (* One cell per function, initialised to the function this build compiled. Nothing has been redefined yet, so a dev build starts out behaving exactly