diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index 16142ac..ef4bd36 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -133,6 +133,13 @@ __uce_[_] path and looks up the export's funcref slot; `exists` lets callers probe a unit 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 backend is initialized and the requested artifact/handler is currently available. If an artifact is cold or stale, dispatch compiles it on demand via diff --git a/src/wasm/backend.cpp b/src/wasm/backend.cpp index 107ba54..ab6dc4b 100644 --- a/src/wasm/backend.cpp +++ b/src/wasm/backend.cpp @@ -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-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-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-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); diff --git a/src/wasm/core.cpp b/src/wasm/core.cpp index 8277391..eb237c0 100644 --- a/src/wasm/core.cpp +++ b/src/wasm/core.cpp @@ -724,26 +724,20 @@ void unit_render(String file_name) { unit_render(file_name, *context); } extern "C" { -// Host calls this to invoke the request's entry unit through a named handler -// ("render"/"cli"/"websocket"/"serve_http:named"). It is the same resolve + -// ONCE() + dispatch path as components — the entry handler is just another -// export. The host pre-checks the export exists, so a missing slot is a no-op. -void uce_wasm_invoke_entry(const char* path, size_t path_len, const char* handler, size_t handler_len) +// The host has already loaded the request's entry unit and can place its +// exports directly in the shared table. This avoids resolving that same unit +// back through a hostcall while preserving per-request ONCE deduplication. +void uce_wasm_invoke_loaded_entry(s32 handler_slot, s32 once_slot) { - // the host always runs uce_wasm_core_init + apply_context before this - String file_name(path, path_len); - String handler_name(handler, handler_len); - String resolved; - s32 slot = wasm_resolve_target(trim(file_name), handler_name, &resolved); - if(!slot) - return; - wasm_run_once(resolved, *context); - String previous_unit = context->resources.current_unit_file; - if(resolved != "") - context->resources.current_unit_file = resolved; - WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; + String resolved = context->resources.current_unit_file; + if(once_slot && resolved != "" && context->once_units.find(resolved) == context->once_units.end()) + { + context->once_units.insert(resolved); + WasmRequestHandler once_handler = (WasmRequestHandler)(uintptr_t)once_slot; + once_handler(*context); + } + WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)handler_slot; handler_fn(*context); - context->resources.current_unit_file = previous_unit; } void* uce_alloc(size_t len) diff --git a/src/wasm/worker.cpp b/src/wasm/worker.cpp index b14773b..59557cf 100644 --- a/src/wasm/worker.cpp +++ b/src/wasm/worker.cpp @@ -193,6 +193,10 @@ struct WasmRequestProfile u64 context_guest_apply_us = 0; u64 context_free_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 workspace_complete_us = 0; u64 component_resolve_count = 0; @@ -1560,45 +1564,61 @@ public: // 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) { + 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(now - phase_started).count(); + phase_started = now; + return(elapsed); + }; entry_dir = dir_of(entry_source_path); size_t unit_index = 0; String error = load_unit(entry_source_path, "entry", unit_index); + entry_load_us = phase_us(); if(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) *handler_present = present; if(!present) return(""); - // Invoke through the core: it resolves the same handler and runs ONCE + - // dispatch (unit is pre-loaded above; resolution is cached). The core - // entry takes two guest buffers — the unit path and the handler name. - auto entry = core_func("uce_wasm_invoke_entry"); + // The entry unit is already loaded above. Place its handler and optional + // ONCE export directly in the shared table, then let the core retain ONCE + // deduplication and dispatch without resolving the same unit via hostcall. + auto entry = core_func("uce_wasm_invoke_loaded_entry"); if(!entry) - return("core does not export uce_wasm_invoke_entry"); - int32_t path_ptr = 0, handler_ptr = 0; - error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &path_ptr); - if(error != "" || path_ptr == 0) - return(error == "" ? String("guest uce_alloc failed for entry path") : error); - error = guest_write((u32)path_ptr, entry_source_path); + return("core does not export uce_wasm_invoke_loaded_entry"); + auto link_handler = [&](const String& symbol, const std::optional& func, u32& slot) { + if(!func) + { + slot = 0; + 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 == "") - { - 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); + error = link_handler(once_symbol, unit_func(unit_index, once_symbol), once_slot); if(error != "") - { - if(path_ptr) call_core("uce_free", { path_ptr }, 0); - if(handler_ptr) call_core("uce_free", { handler_ptr }, 0); return(error); - } - auto result = entry->call(ctx(), { wasmtime::Val(path_ptr), wasmtime::Val((int32_t)entry_source_path.size()), - wasmtime::Val(handler_ptr), wasmtime::Val((int32_t)handler.size()) }); - call_core("uce_free", { path_ptr }, 0); - call_core("uce_free", { handler_ptr }, 0); + entry_link_us = phase_us(); + auto result = entry->call(ctx(), { wasmtime::Val((int32_t)handler_slot), wasmtime::Val((int32_t)once_slot) }); + entry_dispatch_us = phase_us(); if(!result) return(trap_text(result.err())); return("");