Merge first, measure, then choose a backend

This commit is contained in:
Joseph Ferano 2026-09-12 17:44:19 +07:00
parent b093de25ff
commit fbc4aea071

View File

@ -486,3 +486,53 @@ embedding OCaml 5.2 — and the multicore runtime means the most likely obstacle
**Order of work:** answer the embedding questions above with a spike, not a rewrite. If OCaml embeds acceptably, item
11 is buildable and no port is needed. Self-hosting stays a long-term ambition rather than a prerequisite.
## 13. The two questions are independent, and the order matters
The end of the session, and the clearest statement of what is actually being decided. Read this before items 10, 11
and 12, which were each written mid-argument.
**Two separate decisions, repeatedly conflated in conversation:**
1. **One process or two** — whether the compiler runs inside the program's process. This is about *transport*: how
compiled code and inspection data move between the compiler and the running program.
2. **How machine code is produced and installed** — the current `llc` + `ld` + `dlopen`, libLLVM's in-process JIT
(ORC), or a hand-written lowering. This is about *code generation*.
Neither implies the other. A hand-written backend removes `llc` and `ld` (about 21ms of a 35ms redefinition) and
leaves the transport. Merging the processes removes the transport and leaves code generation exactly as it is.
**Merging is the larger prize.**
**And there is a third option for question 2 that keeps getting lost: change nothing.** In one process you can still
write a `.so` and `dlopen` it. You lose nothing that works today, and you still delete the socket, the wire protocol,
the 4K result cap, the seqlock, the snapshot copying, the render-thunk-per-inspection, and `flan reload` paying process
startup every time.
**So the order is:**
1. **Spike the embedding** (item 12). OCaml 5.2's multicore runtime removes the objection that would have been fatal.
If it embeds acceptably, everything below is reachable.
2. **Merge the processes**, keeping the existing code path. Biggest win, no new dependency, deletes the most
machinery.
3. **Measure what is left.** With transport gone, a redefinition is whatever code generation costs. That may simply be
fine.
4. **Then decide the backend, on evidence.** libLLVM's JIT means linking the library `plan.org` rejected — for
reasons that were right at the time, when the JIT was worth ~13ms and nothing else. In one process it is worth the
file, the load, the unbounded accumulation *and* cheap evaluation of arbitrary real code, which the author has since
named as a first-class want. The cost is also more bounded than "breaks routinely" suggests: the bindings track
LLVM major versions, so it is a pin you own and upgrade deliberately.
A hand-written lowering buys the same speed plus no dependency and a disassembler you own, at the cost of an
instruction selector per architecture, maintained forever. The drift risk is real but testable — the `@sanitize`
alias already builds 28 programs twice and compares output and exit status, which is exactly the harness two
backends would need.
**The principle to keep:** the original decision was made on a measurement and it held up for months. Make this one the
same way — merge first, measure, then choose. Do not pre-empt step 4 at step 1.
**Also settled in passing:** a release build can include the compiler and server and simply not be connected to, the
way an SBCL executable can. The dev/release split is a build flag, not an architectural constraint.
**And one correction to keep:** OCaml's own wasm support is irrelevant to any of this. OCaml is the compiler's
implementation language; Flan programs reach wasm through LLVM. The two only meet if the *compiler* should run in a
browser, which is not a goal.