Avoid resolving loaded request entries twice

This commit is contained in:
udo
2026-07-18 03:08:01 +00:00
parent 339031b6c3
commit a043fb8fc7
4 changed files with 69 additions and 44 deletions
+7
View File
@@ -133,6 +133,13 @@ __uce_<base>[_<sanitize(suffix)>]
path and looks up the export's funcref slot; `exists` lets callers probe a unit path and looks up the export's funcref slot; `exists` lets callers probe a unit
without instantiating it. without instantiating it.
The request entry unit is already loaded by the host, so its selected handler
and optional `ONCE` export are placed directly into the workspace table and
passed to `uce_wasm_invoke_loaded_entry()`. Dynamic component and unit calls
continue through `wasm_resolve_target()`, where relative-path resolution is
required. Opt-in verbose response headers split entry invocation into load,
presence lookup, table linking, and core dispatch time.
`wasm_backend_should_handle(request, entry_unit)` checks whether the wasm `wasm_backend_should_handle(request, entry_unit)` checks whether the wasm
backend is initialized and the requested artifact/handler is currently backend is initialized and the requested artifact/handler is currently
available. If an artifact is cold or stale, dispatch compiles it on demand via available. If an artifact is cold or stale, dispatch compiles it on demand via
+4
View File
@@ -243,6 +243,10 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri
request.header["X-UCE-Wasm-Workspace-Birth-Us"] = std::to_string(response.workspace_birth_us); request.header["X-UCE-Wasm-Workspace-Birth-Us"] = std::to_string(response.workspace_birth_us);
request.header["X-UCE-Wasm-Workspace-Complete-Us"] = std::to_string(response.workspace_complete_us); request.header["X-UCE-Wasm-Workspace-Complete-Us"] = std::to_string(response.workspace_complete_us);
request.header["X-UCE-Wasm-Entry-Invoke-Us"] = std::to_string(response.entry_invoke_us); request.header["X-UCE-Wasm-Entry-Invoke-Us"] = std::to_string(response.entry_invoke_us);
request.header["X-UCE-Wasm-Entry-Load-Us"] = std::to_string(response.entry_load_us);
request.header["X-UCE-Wasm-Entry-Presence-Us"] = std::to_string(response.entry_presence_us);
request.header["X-UCE-Wasm-Entry-Link-Us"] = std::to_string(response.entry_link_us);
request.header["X-UCE-Wasm-Entry-Dispatch-Us"] = std::to_string(response.entry_dispatch_us);
request.header["X-UCE-Wasm-Output-Collect-Us"] = std::to_string(response.output_collect_us); request.header["X-UCE-Wasm-Output-Collect-Us"] = std::to_string(response.output_collect_us);
request.header["X-UCE-Wasm-Component-Resolve-Count"] = std::to_string(response.component_resolve_count); request.header["X-UCE-Wasm-Component-Resolve-Count"] = std::to_string(response.component_resolve_count);
request.header["X-UCE-Wasm-Component-Resolve-Total-Us"] = std::to_string(response.component_resolve_total_us); request.header["X-UCE-Wasm-Component-Resolve-Total-Us"] = std::to_string(response.component_resolve_total_us);
+12 -18
View File
@@ -724,26 +724,20 @@ void unit_render(String file_name) { unit_render(file_name, *context); }
extern "C" { extern "C" {
// Host calls this to invoke the request's entry unit through a named handler // The host has already loaded the request's entry unit and can place its
// ("render"/"cli"/"websocket"/"serve_http:named"). It is the same resolve + // exports directly in the shared table. This avoids resolving that same unit
// ONCE() + dispatch path as components — the entry handler is just another // back through a hostcall while preserving per-request ONCE deduplication.
// export. The host pre-checks the export exists, so a missing slot is a no-op. void uce_wasm_invoke_loaded_entry(s32 handler_slot, s32 once_slot)
void uce_wasm_invoke_entry(const char* path, size_t path_len, const char* handler, size_t handler_len)
{ {
// the host always runs uce_wasm_core_init + apply_context before this String resolved = context->resources.current_unit_file;
String file_name(path, path_len); if(once_slot && resolved != "" && context->once_units.find(resolved) == context->once_units.end())
String handler_name(handler, handler_len); {
String resolved; context->once_units.insert(resolved);
s32 slot = wasm_resolve_target(trim(file_name), handler_name, &resolved); WasmRequestHandler once_handler = (WasmRequestHandler)(uintptr_t)once_slot;
if(!slot) once_handler(*context);
return; }
wasm_run_once(resolved, *context); WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)handler_slot;
String previous_unit = context->resources.current_unit_file;
if(resolved != "")
context->resources.current_unit_file = resolved;
WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot;
handler_fn(*context); handler_fn(*context);
context->resources.current_unit_file = previous_unit;
} }
void* uce_alloc(size_t len) void* uce_alloc(size_t len)
+46 -26
View File
@@ -193,6 +193,10 @@ struct WasmRequestProfile
u64 context_guest_apply_us = 0; u64 context_guest_apply_us = 0;
u64 context_free_us = 0; u64 context_free_us = 0;
u64 entry_invoke_us = 0; u64 entry_invoke_us = 0;
u64 entry_load_us = 0;
u64 entry_presence_us = 0;
u64 entry_link_us = 0;
u64 entry_dispatch_us = 0;
u64 output_collect_us = 0; u64 output_collect_us = 0;
u64 workspace_complete_us = 0; u64 workspace_complete_us = 0;
u64 component_resolve_count = 0; u64 component_resolve_count = 0;
@@ -1560,45 +1564,61 @@ public:
// empty body, a missing cli/serve handler to a 404). // empty body, a missing cli/serve handler to a 404).
String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0) String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0)
{ {
auto phase_started = std::chrono::steady_clock::now();
auto phase_us = [&]() {
auto now = std::chrono::steady_clock::now();
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_started).count();
phase_started = now;
return(elapsed);
};
entry_dir = dir_of(entry_source_path); entry_dir = dir_of(entry_source_path);
size_t unit_index = 0; size_t unit_index = 0;
String error = load_unit(entry_source_path, "entry", unit_index); String error = load_unit(entry_source_path, "entry", unit_index);
entry_load_us = phase_us();
if(error != "") if(error != "")
return(error); return(error);
bool present = (bool)unit_func(unit_index, handler_export_symbol(handler)); String handler_symbol = handler_export_symbol(handler);
auto handler_fn = unit_func(unit_index, handler_symbol);
bool present = (bool)handler_fn;
entry_presence_us = phase_us();
if(handler_present) if(handler_present)
*handler_present = present; *handler_present = present;
if(!present) if(!present)
return(""); return("");
// Invoke through the core: it resolves the same handler and runs ONCE + // The entry unit is already loaded above. Place its handler and optional
// dispatch (unit is pre-loaded above; resolution is cached). The core // ONCE export directly in the shared table, then let the core retain ONCE
// entry takes two guest buffers — the unit path and the handler name. // deduplication and dispatch without resolving the same unit via hostcall.
auto entry = core_func("uce_wasm_invoke_entry"); auto entry = core_func("uce_wasm_invoke_loaded_entry");
if(!entry) if(!entry)
return("core does not export uce_wasm_invoke_entry"); return("core does not export uce_wasm_invoke_loaded_entry");
int32_t path_ptr = 0, handler_ptr = 0; auto link_handler = [&](const String& symbol, const std::optional<wasmtime::Func>& func, u32& slot) {
error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &path_ptr); if(!func)
if(error != "" || path_ptr == 0) {
return(error == "" ? String("guest uce_alloc failed for entry path") : error); slot = 0;
error = guest_write((u32)path_ptr, entry_source_path); return(String(""));
}
String slot_key = entry_source_path + ":" + symbol;
auto cached = handler_slots.find(slot_key);
if(cached != handler_slots.end())
{
slot = cached->second;
return(String(""));
}
String link_error = place_funcref(*func, slot);
if(link_error == "")
handler_slots[slot_key] = slot;
return(link_error);
};
u32 handler_slot = 0, once_slot = 0;
error = link_handler(handler_symbol, handler_fn, handler_slot);
String once_symbol = handler_export_symbol("once");
if(error == "") if(error == "")
{ error = link_handler(once_symbol, unit_func(unit_index, once_symbol), once_slot);
error = call_core("uce_alloc", { (int32_t)handler.size() }, &handler_ptr);
if(error == "" && handler_ptr == 0)
error = "guest uce_alloc failed for handler";
}
if(error == "")
error = guest_write((u32)handler_ptr, handler);
if(error != "") if(error != "")
{
if(path_ptr) call_core("uce_free", { path_ptr }, 0);
if(handler_ptr) call_core("uce_free", { handler_ptr }, 0);
return(error); return(error);
} entry_link_us = phase_us();
auto result = entry->call(ctx(), { wasmtime::Val(path_ptr), wasmtime::Val((int32_t)entry_source_path.size()), auto result = entry->call(ctx(), { wasmtime::Val((int32_t)handler_slot), wasmtime::Val((int32_t)once_slot) });
wasmtime::Val(handler_ptr), wasmtime::Val((int32_t)handler.size()) }); entry_dispatch_us = phase_us();
call_core("uce_free", { path_ptr }, 0);
call_core("uce_free", { handler_ptr }, 0);
if(!result) if(!result)
return(trap_text(result.err())); return(trap_text(result.err()));
return(""); return("");