⏺ WASM Tier 1 is productionized and verified end-to-end. #25
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "wasm-t1"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What landed
plgc build --target wasm32-wasi prog.pl -o prog.wasm is now
a real command — the hand-script from the spike is gone.
routes to link_ir (native) or link_wasm; feature-gated
WASM_RUNTIME_LIB.
wasm32-wasi triple and name the entry __main_argc_argv (the
wasi startup bridge) instead of main. The IR body is
unchanged — same musttail chains.
the wasm target's self-contained wasi-libc via rustc
--print sysroot, runs llc -mattr=+tail-call → wasm-ld, with
helpful errors naming the exact rustup command when a
piece is missing.
wasm runtime archive. Default cargo install plgc is
byte-for-byte unchanged and needs no wasm toolchain.
output extension.
wasm-smoke test.
SUMMARY.md and compiler-usage.md; ROADMAP M10 recorded; the
Future "cross-compilation" bullet narrowed to Tier 2.
Verified
(just wasm-smoke, 3/3).
plgc path).
build/clippy/tests all green without the feature.
an unknown --target both give actionable errors, no crash.
clippy clean on both feature sets.
Honest scope notes
a CI test; the runner doesn't have the wasm toolchain yet.
CI wiring is the deferred follow-up (recorded in M10).
bundled engine) isn't wired — build to .wasm is the Tier 1
deliverable; you run it with wasmtime run.
layout (/lib/rustlib/.../bin), which is the known
brittleness of the "reuse Rust llvm-tools" choice — it
errors clearly if the layout/components aren't present.
Review — WASM Tier 1 productionization
This is what "additive new target on a working compiler" should look like. The native path is unchanged byte-for-byte; the wasm path is strictly opt-in (
--features wasm); the IR body is identical between targets — only the triple, the entry symbol, and the linker driver differ. The years of discipline aroundmusttailpay off here exactly: LLVM's wasm backend lowersmusttailtoreturn_call, so the constant-stack guarantee that took PRs #20/#24 to nail down for native carries through for wasm with zero IR changes.Strong calls worth naming:
cargo install plgcis byte-for-byte unchanged.WASM_RUNTIME_LIB: Option<&[u8]>isNonewithout the feature; no native code path is altered; theTarget::Wasmarm fails with an actionable "rebuild with--features wasm" before reaching the linker. This is the right opt-in shape.-mattr=+tail-callis the load-bearing flag. The PR description's claim "If the tail-call feature is ever missing, llc fails at build time — it never silently emits a non-tail call" is what makes "constant stack on wasm" defensible. Build-time enforcement, not runtime hope.__main_argc_argvas the entry symbol. This is the correct wasi-libc bridge (_start → __main_void → __main_argc_argv). Getting this wrong produces a module that loads-but-does-nothing — a confusing failure mode you've sidestepped by naming it explicitly.llc/rust-lld→rustup component add llvm-tools-preview. Missing wasi-libc →rustup target add wasm32-wasip1. Missing wasm runtime archive →just build-runtime-wasm. Each error names the exact next step. UX discipline throughout.wasm32-wasiandwasm32-wasip1. Legacy name and Rust's current name both work. Good usability call — the WASI alias is widely-known; the wasip1 form is what Rust prints internally.wasm, notwasm32-wasi. Future-proofs against Tier 2 (V8 isolates /wasm32-unknown-unknown) adding bullets to the feature without renaming.Issues below — none are blockers.
Real concerns
1. The toolchain discovery reaches into rustup's internal layout. The PR description names this explicitly, and the error message recovers well, but it's worth one line of ROADMAP/wasm-target.md recording the strategy: "track rustup's
<sysroot>/lib/rustlib/<host>/bin/layout; if it ever moves, switch tollvm-configor the wasi-sdk distribution." The layout has been stable for years and Rust is unlikely to change it without warning, but if it ever does, the breakage is silent ("file not found" → user thinks their install is broken). A line in the doc saves a future contributor (or your future self) the diagnostic time.Two small durability follow-ups inside
wasm_toolchain:rustc -vVthen parsinghost:is the host-triple discovery.rustc --print host-tuple(documented stable since ~1.85) does the same without string parsing. Marginal but more durable.self_contained/libc.aand reports "needs thewasm32-wasip1std" — fine when the target is unconfigured, but ifself_contained/exists with a different missing file (e.g. partial install), the message is misleading. Adding aself_contained.exists()first-step check would distinguish "target not added" from "target partially broken."2. The "constant stack on wasm" claim is structural, not exercised at depth. The wasm-smoke runs three flat queries against
examples/deps.pl— they prove the wire contract round-trips, but they don't exercise the musttail-as-return_call lowering at the recursion depths where it actually matters. PR #24'scall1_recursion_runs_in_constant_c_stackruns 100k-deep under a 512KB native stack; an analogouswasmtime run --wasm max-stack-size=…invocation against a recursive Prolog program would close the structural-vs-measured gap. The just recipe could grow one more case: compilecount(0). count(N) :- N > 0, N1 is N - 1, call(count(N1))., run with a constrained engine stack, assert success at 100k. That's the test that earns the "constant stack on wasm" claim.Small observations
3.
--O3on the CLI silently becomes-O2at the llc layer for wasm. The source comment explains: "llc's -O3 buys little for our IR and -O2 is what the gate proved." Reasonable, but a user reading--debug=falseand assuming "release optimization" doesn't know they're getting-O2. One line inwasm-target.md— "wasm builds top out at LLVM-O2" — closes it.4.
--allow-undefineddefers symbol resolution to instantiation. Standard wasm-ld pattern; correct for a WASI module. The trade-off is that a missing runtime import surfaces atwasmtime runtime, not at link time. The wasm-smoke happens to catch this end-to-end because it runs the module, so the coverage is there — worth one comment inlink_wasmnoting "undefined imports survive link; the smoke test's actual run is what verifies them."5. Tail-call feature requires a recent wasm engine. wasmtime ≥ 0.40 or so, recent wasmer, recent V8 (for the eventual Tier 2 path). Engines without it will error at instantiation with a "tail-call" reference. The error class is fine (clear and actionable), but a one-line "needs a wasm engine with the tail-call proposal" in
wasm-target.mdwould head off "why doesn't this run on old wasmtime?" questions.6. Three hard-coded
Target::Nativeliterals inmain.rs(build path'scompile_files, run path'scompile_files, script-mode'scompile_files). Two of those (run and script-mode) genuinely can't be wasm. The build path is the only placetargetis computed from the CLI. The explicit literal isn't wrong, but acompile_files_native(..)helper for the two paths that can't change would document the intent and shrink the call sites. Stylistic.7. The
parse_targetfunction returnsErrforSome("native")to map to Native — but the CLI flag acceptsNone | Some("native")as native. That's consistent with "no flag = native, explicit--target native= native, anything else = error". Worth confirming the docs listnativeas an accepted value (the current error message says "supported: native, wasm32-wasi", so it does). Good — the CLI surface, the error message, and the docs all agree.What's good
musttailfrom PRs #20/#24 carries through to wasm with zero IR changes. This is the architectural payoff. The discipline of "every cross-predicate transfer is musttail" looked native-specific at the time but is exactly what makes wasm-target work. The PR description names this; it deserves naming again.cargo install plgcs for native work) is unaffected. The cost lands only on contributors / users who actively want wasm. This is how to add capability without taxing the default user.Targetenum + two switches in codegen (triple, entry name) + a parallellink_wasmfunction. No hidden conditionals scattered through unrelated files. If a Tier 2 target needs another switch (e.g. no main at all, exported functions only), the seam is in two known places.--features wasmwithout the runtime archive panics withjust build-runtime-wasmas the recovery. This is correct cargo idiom (build script failures are how cargo communicates configuration problems) and the error message is honest about cause and cure.wasm32-wasiandwasm32-wasip1). Avoids one of the most common "but I typed the right thing!" failure modes.examples/deps.pl. Catches "did the toolchain regress" on a contributor's machine without CI infrastructure investment. CI wiring as a follow-up (ROADMAP M10) is the right scope discipline.wasm-target.mdis honest about V8 isolates /wasm32-unknown-unknownbeing Tier 2. Future users searching for Cloudflare Workers find the doc and know where to look without filing an issue.plgc checkorplgc compile-to-ir(the IR-only path) doesn't pay for missing wasm tools. Only the linker step requires them. This keeps the lint/check loop fast even on a wasm-enabled plgc.Suggested order
#1's two sub-items (
rustc --print host-tupleand a one-line strategy note in the docs) are small durability improvements and worth doing before merge. #2 (a deep-recursion case inwasm-smoke) is the one substantive add — it earns the "constant stack on wasm" claim in the same way PR #24 earned it natively. #3+ are observations.