W7e: wasm-preferred dispatch + compile-on-demand; harden unit ABI validator

- Dispatch (linux_fastcgi.cpp): route every request through wasm. On a
  cold/stale artifact, compile the unit on demand (get_shared_unit, forced) and
  serve wasm; native compiler_invoke* remains only as a fallback when wasm
  cannot be made ready (compile failure / backend disabled). Applies to the 4
  handle_complete branches and the CLI socket path.
- backend.cpp: delete the now-vestigial native-only fallback token gate
  (wasm_backend_native_fallback_*), empty since W7d; should_handle now gates on
  config + current artifact + healthy worker only.
- check_unit_wasm.py: skip the defense-in-depth llvm-nm allocator scan when
  llvm-nm SIGSEGVs on a degenerate-but-valid module (e.g. a unit with no
  exported handlers). Fixes site/demo/empty.uce, the last unit that could not
  produce a .wasm; forbidden allocator *exports* are still rejected.

A pi-assisted review caught that the compile-freshness check keys off the .so
mtime only, so force_recompile is required to rebuild a missing/stale .wasm; a
unit already cached in-process whose .wasm later vanished still uses native
fallback (closed in W7e stage B). Native execution is otherwise bypassed for
all real traffic.

Verified: scripts/run_cli_tests.sh --include-wasm-kill -> 87 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-14 18:20:12 +00:00
co-authored by Claude Opus 4.8
parent 8587fbc5aa
commit 2debd33804
3 changed files with 46 additions and 57 deletions
+32 -4
View File
@@ -368,6 +368,12 @@ int handle_cli_complete(FastCGIRequest& request)
// The CLI socket path installs no native fault handler, so the
// wasm worker (whose traps are signal-based) can run directly.
String cli_unit = compiler_normalize_unit_path(&request, script_filename);
// W7e: compile a cold/stale unit on demand instead of executing
// native. force_recompile: the compile-needed check ignores the
// .wasm (keys off .so mtime), so a missing/stale .wasm with a
// current .so must be forced to rebuild — see wasm_ready note.
if(!wasm_backend_should_handle(request, cli_unit))
get_shared_unit(&request, cli_unit, true);
if(wasm_backend_should_handle(request, cli_unit))
{
String wasm_error = wasm_backend_serve(request, cli_unit, "cli");
@@ -477,6 +483,28 @@ int handle_complete(FastCGIRequest& request) {
};
String entry_unit = compiler_normalize_unit_path(&request, request.params["SCRIPT_FILENAME"]);
// W7e: every unit runs on wasm. When the artifact is missing or stale
// (cold worker, or source edited since the last compile),
// wasm_backend_should_handle() returns false; rather than fall back to
// native execution we compile the unit on demand — get_shared_unit()
// builds the .wasm side-module — and recheck. Native execution remains
// only as a safety net for a genuine wasm-compile failure (or the wasm
// backend being disabled), both of which leave should_handle() false.
// Only reached on the cold/stale path (should_handle false); warm
// requests short-circuit before this, so there is no steady-state
// recompile cost. force_recompile is needed because the compile-needed
// check keys off the .so mtime only (shared_unit_compile_check /
// compiled_time ignores the .wasm), so an uncached unit with a current
// .so but missing .wasm would otherwise not rebuild. NOTE: this does not
// cover a unit already cached in-process whose .wasm later vanished
// (compiler_reusable_cached_unit short-circuits before the recompile) —
// that synthetic case still uses native fallback and is closed properly
// in W7e stage B (make the .wasm part of the compile-freshness check).
auto wasm_ready = [&](const String& unit) -> bool {
if(!wasm_backend_should_handle(request, unit))
get_shared_unit(&request, unit, true);
return(wasm_backend_should_handle(request, unit));
};
if(request.params["UCE_WS"] == "1")
{
// A WS message the broker forwarded here: rebuild the connection
@@ -498,14 +526,14 @@ int handle_complete(FastCGIRequest& request) {
if(ucb_decode(state_raw, state, &e))
request.connection = state;
}
if(wasm_backend_should_handle(request, entry_unit))
if(wasm_ready(entry_unit))
serve_via_wasm(entry_unit, "websocket");
else
compiler_invoke_websocket(&request, request.params["SCRIPT_FILENAME"]);
}
else if(request.resources.is_cli)
{
if(wasm_backend_should_handle(request, entry_unit))
if(wasm_ready(entry_unit))
serve_via_wasm(entry_unit, "cli");
else
compiler_invoke_cli(&request, request.params["SCRIPT_FILENAME"]);
@@ -515,7 +543,7 @@ int handle_complete(FastCGIRequest& request) {
// W7c: this runs in a normal worker (clean engine) — the custom-server
// dispatcher forwarded the request here via FastCGI rather than render
// wasm in its own fork (Wasmtime cannot be re-created across fork).
if(wasm_backend_should_handle(request, entry_unit))
if(wasm_ready(entry_unit))
{
String fn = request.params["UCE_SERVE_HTTP_FUNCTION"];
serve_via_wasm(entry_unit, fn == "" ? String("serve_http") : "serve_http:" + fn);
@@ -523,7 +551,7 @@ int handle_complete(FastCGIRequest& request) {
else
compiler_invoke_serve_http(&request, request.params["SCRIPT_FILENAME"], request.params["UCE_SERVE_HTTP_FUNCTION"]);
}
else if(wasm_backend_should_handle(request, entry_unit))
else if(wasm_ready(entry_unit))
serve_via_wasm(entry_unit, "render");
else
compiler_invoke(&request, request.params["SCRIPT_FILENAME"]);