Say which joint is glued, so the next session does not trust it

Item 6 said the builtins archive has to come from wasi-sdk. It does not have
to, and what is standing in its place is emscripten's compiler-rt for a
different triple — which works, and is worth writing down as a substitution
rather than leaving as "wasm32 works".
This commit is contained in:
Joseph Ferano 2026-09-11 19:43:42 +07:00
parent b14793517b
commit 046593acf1

71
NEXT.md
View File

@ -161,6 +161,7 @@ reader ✅ → parse ✅ → load ✅ → check ✅ → emit ✅ → clang ✅
| `test/programs/restarts.flan` | **`restart-case` and `invoke-restart`: the transfer, across two frames** |
| `test/test_emacs.ml` | **the client, driven against a real daemon and a real program** |
| `test/reload_host.c` | the C host that loads and installs two rebuilds, in one process |
| `test/wasm-run.mjs` | **a WASI host in twenty lines of `node:wasi`, so the table can run a wasm32 build** |
```
$ flan run calc-me.flan "1 + 2 * (3 - 0.5) / 2"
@ -1392,19 +1393,63 @@ painful to retrofit:
Deferred until after the dev loop:
6. **wasm32.** The user installed `wasi-libc-devel` and `wasi-libc-static`; the
sysroot is `/usr/wasm32-wasi` and `wasm-ld` is present. `clang
--target=wasm32-wasi --sysroot=/usr/wasm32-wasi` gets past the headers and
then **fails to link**: it wants
`lib/clang/20/lib/wasm32-unknown-wasi/libclang_rt.builtins.a`, which no
Fedora package provides (`dnf provides '*libclang_rt.builtins*wasm*'` finds
nothing). It has to come from a wasi-sdk release, dropped into clang's
resource directory. After that: teach `build.ml` `--sysroot`, and run the
acceptance table — `sand-headless.flan` included, which is exactly why it
does not import raylib — on both targets in CI.
Note plan.org has the *web* build linking raylib via emscripten, which
brings its own sysroot: wasi-sdk is right for the headless table, not
necessarily for the eventual game build.
6. ~~**wasm32.**~~ **Done, with one glued joint.** `flan build
--target=wasm32-wasi` produces a module, and `test/programs/sand-headless.flan`
prints `2256461126764447066` under it — the same hash as native, byte for
byte, at `-O2` and at `-O0`. That is the milestone: the RNG is ours rather
than libc's precisely so that number can be compared across targets, and it
compares equal. `values.flan` and `machine.flan` run there too, which is
where a 32-bit pointer would have shown. The acceptance table runs all four,
and skips them by *probing* — it builds the smallest program and runs it —
rather than by looking for a binary on PATH.
Three things this cost that were not in the old note:
- **The entry point is not `main`.** wasi-libc's start code calls
`__main_argc_argv`; clang renames C's argc/argv `main` to that, and the
`.ll` `Emit` writes says `@main` literally. The link succeeds and the
program traps on a signature-mismatched weak stub. `Build.wasm_main_source`
is a two-line C shim that bridges it, and the `__asm__("main")` label in it
is load-bearing: spelling the callee `main` makes clang rename *that* too
and the shim becomes an infinite self-call.
- **The target has to reach the C compiles, not just the link.** `flan_rt.c`
includes `<stdio.h>`; without `--sysroot` it never gets that far.
`target_flags` is computed once and passed to both, and the whole flag
list — not just the triple — is in the object cache key, so repointing a
sysroot cannot serve a stale `.o`.
- **Fedora's sysroot is one level deeper** than wasi-sdk's:
`include/wasm32-wasi/stdio.h`, not `include/stdio.h`. Both shapes count.
**The glued joint, and the one thing this contradicts in the old note.** The
old note said the builtins archive has to come from a wasi-sdk release. It
does not have to: emscripten builds the same compiler-rt for wasm32 and
calls it `libcompiler_rt.a`, and dropping that in as
`libclang_rt.builtins.a` links and runs. It is a different triple
(`wasm32-unknown-emscripten`) built by a different clang (22 against
Fedora's 20), so it is *substituting*, and wasi-sdk is still the proper
article. `build.ml` looks for `FLAN_WASM_BUILTINS`, then
`/opt/wasi-sdk/...`, then emscripten's beside `emcc` on PATH, and refuses by
name listing every path it tried when none is there. clang's resource
directory is root-owned, so the archive is not dropped into it — a shadow
resource directory is built under the object cache, named by a digest of
clang's own resource dir plus the archive's path, size and mtime, with the
real `include` symlinked in.
**The runtime is Node.** No `wasmtime` and no `wasmer` on this machine;
`test/wasm-run.mjs` is twenty lines of `node:wasi` and the table prefers
`wasmtime` or `wasmer` if either appears. `--no-warnings`, because
`node:wasi` writes an `ExperimentalWarning` to stderr on every run and the
harness compares combined output.
**Refused by name, not half-supported:** `--dev` with a wasm target (the
reload path is `dlopen`), `Build.shared` with one (same reason), and `flan
run --target=` (a `.wasm` is not something this host execs — build it and
point a runtime at it).
Still open: raylib on wasm, which plan.org wants through emscripten and its
own sysroot. wasi-sdk is right for the headless table; it is not necessarily
right for the eventual game build.
7. **Loose ends from milestone 4**, none of them blocking: block-scoped
`defer`; package visibility, so `rl/get-color-raw` is not callable; a
package importing a package; imported unions.