W7e: delete the native unit pipeline (.so compile + dlopen execution)
Units now run exclusively on wasm; the native generated-C++ -> clang -> .so -> dlopen path and its request-time fallback are removed. - Dispatch (linux_fastcgi.cpp): the 4 handle_complete branches + the CLI-socket path route every request through wasm (wasm_ready compiles cold/stale on demand); a wasm-unavailable unit now yields a clean error page (fail_wasm_unavailable / render_request_failure) instead of native execution. compiler_invoke / _cli / _websocket / _serve_http deleted. - compiler.cpp (-1274): removed the native .so compile (COMPILE_SCRIPT), load_shared_unit, dlopen/dlsym/dlclose, compiler_load_shared_unit, and the SharedUnit .so function-pointer fields (on_setup/on_render/on_component/ on_websocket/on_cli/on_once/on_init) in types.h/types.cpp. compile_shared_unit now builds only the .wasm side-module; the .uce preprocessor/parser front-end is kept (it emits the C++ the wasm compile consumes). - unit_call()/component()/once/init now resolve across units through the wasm host component resolver (uce_host_component_resolve) instead of native dlsym; configured runtime error pages render through the wasm backend. - Dropped the WASM_BACKEND_ENABLED feature flag and dead COMPILE_SCRIPT / COMPILE_WASM_UNITS config; unit ABI freshness tied to UCE_UNIT_ABI_VERSION; guard against serving a stale .wasm for a deleted source; retired the obsolete W5 native-vs-wasm toggle script. Docs updated. Implemented via the pi agent (with 3 delegated sub-reviews); independently re-verified on the build host: run_cli_tests --include-wasm-kill => 87 passed, 0 failed, 0 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+13
-23
@@ -2,12 +2,8 @@
|
||||
//
|
||||
// Included into the native server TU (src/linux_fastcgi.cpp) after uce_lib.cpp,
|
||||
// so it shares String/DValue/config and the UCEB1 codec. Provides a
|
||||
// config-selectable page-render backend: when WASM_BACKEND_ENABLED and a wasm
|
||||
// artifact exists for the entry unit, the request is served through a
|
||||
// per-request wasm workspace instead of the native dlopen path.
|
||||
//
|
||||
// The seam is narrow on purpose — only the page render branch in
|
||||
// handle_complete() changes. CLI, serve_http, and websocket stay native.
|
||||
// Always-on wasm backend: every unit request is served through a per-request
|
||||
// wasm workspace. The legacy native dlopen execution path has been removed.
|
||||
|
||||
#include "../lib/wasm_trace.h"
|
||||
// The native server TU has the real connectors (sqlite/mysql) compiled in, so
|
||||
@@ -31,9 +27,7 @@ static bool g_wasm_init_attempted = false;
|
||||
|
||||
bool wasm_backend_configured(Request* context)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return(false);
|
||||
return(config_bool("WASM_BACKEND_ENABLED", false));
|
||||
return(context && context->server);
|
||||
}
|
||||
|
||||
// Lazily bring up the per-process worker on first use inside a forked child
|
||||
@@ -90,22 +84,19 @@ static bool wasm_artifact_exists(Request* context, const String& entry_unit)
|
||||
struct stat wasm_st;
|
||||
if(stat(wasm_path.c_str(), &wasm_st) != 0 || !S_ISREG(wasm_st.st_mode))
|
||||
return(false);
|
||||
// The wasm path bypasses the native JIT recompile, so a source edit would
|
||||
// otherwise be served from a stale .wasm. Require the artifact to be newer
|
||||
// than the source; if it is stale, fall back to native — which JIT-rebuilds
|
||||
// both .so and .wasm — so the next request gets a fresh artifact.
|
||||
// Require the artifact to be newer than the source. If it is stale,
|
||||
// dispatch compiles the unit on demand and then rechecks this predicate.
|
||||
struct stat src_st;
|
||||
if(stat(entry_unit.c_str(), &src_st) == 0 && wasm_st.st_mtime < src_st.st_mtime)
|
||||
if(stat(entry_unit.c_str(), &src_st) != 0 || !S_ISREG(src_st.st_mode))
|
||||
return(false);
|
||||
if(wasm_st.st_mtime < src_st.st_mtime)
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
// True if this request should be served by the wasm backend. Every unit now
|
||||
// runs on wasm (W7d retired the last native-only surfaces), so this only gates
|
||||
// on config, the presence of a current wasm artifact, and a healthy worker —
|
||||
// the remaining native fallback exists solely to cover a cold/stale artifact
|
||||
// (a unit not yet compiled, or whose source is newer than its .wasm). W7e
|
||||
// removes that last fallback once cold/stale triggers an on-demand wasm compile.
|
||||
// True if this request can be served by the wasm backend now. Every unit runs
|
||||
// on wasm; cold/stale artifacts return false so dispatch can compile on demand
|
||||
// and then recheck.
|
||||
bool wasm_backend_should_handle(Request& request, const String& entry_unit)
|
||||
{
|
||||
if(!wasm_backend_configured(&request))
|
||||
@@ -181,9 +172,8 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri
|
||||
fcgi_forward_request(broker_socket, dispatch_params, ucb_encode(batch), 5);
|
||||
}
|
||||
|
||||
// A cli/serve_http unit that does not export the requested handler is a 404,
|
||||
// matching native compiler_invoke_cli. For page render a missing handler is
|
||||
// simply an empty body (native parity).
|
||||
// A cli/serve_http unit that does not export the requested handler is a 404.
|
||||
// For page render a missing handler is simply an empty body.
|
||||
if(!response.handler_present && handler != "render")
|
||||
{
|
||||
request.set_status(404, handler == "cli" ? "CLI Entry Point Not Found" : "Handler Not Found");
|
||||
|
||||
+59
-29
@@ -139,32 +139,6 @@ bool unit_compile(String path)
|
||||
}
|
||||
|
||||
static DValue wasm_unit_call_result;
|
||||
DValue* unit_call(String file_name, String function_name, DValue* call_param)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "call";
|
||||
request["file"] = file_name;
|
||||
request["function"] = function_name;
|
||||
if(call_param)
|
||||
request["param"] = *call_param;
|
||||
DValue response = wasm_units_call(request);
|
||||
String output = response["output"].to_string();
|
||||
if(output != "")
|
||||
print(output);
|
||||
DValue* result = response.key("result");
|
||||
if(result)
|
||||
{
|
||||
wasm_unit_call_result = *result;
|
||||
return(&wasm_unit_call_result);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional)
|
||||
{
|
||||
(void)context; (void)file_name; (void)current_path; (void)opt_so_optional;
|
||||
return(0);
|
||||
}
|
||||
|
||||
static DValue wasm_mysql_call(DValue request)
|
||||
{
|
||||
@@ -484,9 +458,9 @@ extern "C" int32_t uce_host_component_resolve(
|
||||
// but a single workspace can render the same component many times)
|
||||
static std::map<String, s32> wasm_component_slots;
|
||||
|
||||
// These mirror small page-runtime pieces of compiler.cpp, which is carved
|
||||
// out of the wasm core wholesale (it is the native toolchain: parser, clang
|
||||
// driver, dlopen). Kept byte-identical where possible.
|
||||
// These mirror small page-runtime pieces that cannot include compiler.cpp in
|
||||
// the wasm core (compiler.cpp owns parser/clang/cache bookkeeping for the host
|
||||
// build). Kept byte-identical where possible.
|
||||
String component_normalize_path(String name)
|
||||
{
|
||||
name = trim(name);
|
||||
@@ -595,6 +569,62 @@ static void wasm_run_once(const String& resolved, Request& request)
|
||||
request.resources.current_unit_file = previous_unit;
|
||||
}
|
||||
|
||||
DValue* unit_call(String file_name, String function_name, DValue* call_param)
|
||||
{
|
||||
String macro = to_upper(trim(function_name));
|
||||
String handler = "";
|
||||
if(macro == "RENDER" || macro.rfind("RENDER:", 0) == 0)
|
||||
handler = "render" + (macro.length() > 7 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String(""));
|
||||
else if(macro == "COMPONENT" || macro.rfind("COMPONENT:", 0) == 0)
|
||||
handler = "component" + (macro.length() > 10 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String(""));
|
||||
else if(macro == "ONCE")
|
||||
handler = "once";
|
||||
else if(macro == "INIT")
|
||||
handler = "init";
|
||||
|
||||
if(handler != "")
|
||||
{
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved);
|
||||
if(!slot)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
return(0);
|
||||
}
|
||||
RequestPropsScope props_scope(context, (call_param ? *call_param : DValue()));
|
||||
if((handler == "render" || handler.rfind("render:", 0) == 0 || handler == "component" || handler.rfind("component:", 0) == 0) && resolved != "")
|
||||
wasm_run_once(resolved, *context);
|
||||
String previous_unit = context->resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
context->resources.current_unit_file = resolved;
|
||||
request_ref_handler handler_fn = (request_ref_handler)(uintptr_t)slot;
|
||||
handler_fn(*context);
|
||||
context->resources.current_unit_file = previous_unit;
|
||||
return(0);
|
||||
}
|
||||
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(file_name, "export:" + function_name, &resolved);
|
||||
if(!slot)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
return(0);
|
||||
}
|
||||
|
||||
String previous_unit = context->resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
context->resources.current_unit_file = resolved;
|
||||
dv_call_handler handler_fn = (dv_call_handler)(uintptr_t)slot;
|
||||
DValue* result = handler_fn(call_param);
|
||||
context->resources.current_unit_file = previous_unit;
|
||||
if(result)
|
||||
{
|
||||
wasm_unit_call_result = *result;
|
||||
return(&wasm_unit_call_result);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
void component_render(String name, DValue props, Request& request)
|
||||
{
|
||||
String file_name, render_name;
|
||||
|
||||
+8
-1
@@ -1046,6 +1046,9 @@ private:
|
||||
// __uce_<base>[_<suffix>] for a handler spec like "component:CARD" / "render".
|
||||
static String handler_export_symbol(const String& handler)
|
||||
{
|
||||
String export_prefix = "export:";
|
||||
if(handler.rfind(export_prefix, 0) == 0)
|
||||
return(handler.substr(export_prefix.size()));
|
||||
auto colon = handler.find(":");
|
||||
String symbol = "__uce_" + (colon == String::npos ? handler : handler.substr(0, colon));
|
||||
if(colon != String::npos)
|
||||
@@ -1086,6 +1089,9 @@ private:
|
||||
return(1);
|
||||
}
|
||||
|
||||
if(!file_exists_host(worker.unit_wasm_path(resolved)) || compiler_unit_needs_recompile(context, resolved, 0))
|
||||
get_shared_unit(context, resolved, true);
|
||||
|
||||
size_t unit_index = 0;
|
||||
String error = load_unit(resolved, unit_index);
|
||||
if(error != "")
|
||||
@@ -1791,7 +1797,7 @@ private:
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_component_resolve")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String target, handler, current, resolved;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), target);
|
||||
self->hostcall_read(args[2].i32(), args[3].i32(), handler);
|
||||
@@ -1806,6 +1812,7 @@ private:
|
||||
self->hostcall_write(args[6].i32(), resolved);
|
||||
}
|
||||
results[0] = Val(slot);
|
||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||
return(std::monostate());
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user