Reuse core host definitions across requests

This commit is contained in:
udo 2026-07-18 00:55:29 +00:00
parent 1ebf94b135
commit 6e4ef8ed7a
8 changed files with 212 additions and 56 deletions

View File

@ -218,7 +218,7 @@ Each request gets a fresh **workspace** — a per-request wasm instance tree wit
the membrane wired in. `wasm_worker_serve(worker, ctx, entry_unit, handler)`
(`src/wasm/worker.cpp`) is the single entry point for *every* mode:
1. Birth a request-scoped workspace with fresh per-request state. Current production workers instantiate a fresh workspace for each request; any future snapshot/CoW optimization must preserve that request-isolation contract.
1. Birth a request-scoped workspace with fresh per-request state. Current production workers instantiate a fresh Store, table, core instance, memory, globals, constructors, and request tree for each request. The worker retains only Wasmtime's store-independent host-function definitions and their fixed import order, avoiding reconstruction of 73 identical callback definitions. Every callback resolves the one active request workspace at invocation. Any future snapshot/CoW optimization must preserve that request-isolation contract.
2. Resolve `entry_unit` + `handler` to an export; components referenced at
runtime are resolved on demand via the `uce_host_component_resolve` hostcall
(`component_resolve()``__uce_<...>` slot), loading dependency modules
@ -240,6 +240,15 @@ module cache, adding a visible cold-start request. Request state remains bounded
by the fresh workspace and the normal per-request database/resource cleanup;
faulted workers still exit and are replaced by the parent.
The request context remains UCEB2 so application `context.call` semantics do
not fork into a private transport type. Validated decode trees move into the
request rather than being deep-copied twice; the historical by-value
`DValue::operator=(DValue)` symbol remains exported so warmed side-module
artifacts stay ABI-compatible. `request_perf()` subdivides birth into policy,
import materialization, core instantiation, export lookup, and initialization,
and context transfer into bytes, host encode, guest allocation/write,
guest decode/application, and free.
### Task callbacks and workspace lifetime
`task()` and `task_repeat()` are fork-backed. The `uce_host_task_spawn` hostcall

View File

@ -23,6 +23,8 @@ Successful first loads within the request are counted by `unit_load_count` and d
The request log's `wasm-ready`, `wasm`, `workspace`, `invoke`, `collect`, and `post` fields complete the timing boundary after this in-page snapshot: compiled-artifact readiness before backend entry, native backend wall time, complete workspace wall time, entry invocation, guest-output/meta collection, and native response assembly after the backend. They do not expose request data. This distinction is useful when a completed FastCGI request takes longer than the page's mid-render `request_perf()` snapshot.
Workspace birth is subdivided into `birth_policy_us`, `birth_import_us`, `birth_instantiate_us`, `birth_exports_us`, and `birth_initialize_us`. Request-context transfer reports `context_bytes`, `context_encode_us`, `context_allocate_us`, `context_write_us`, `context_guest_apply_us`, and `context_free_us`. These bounded aggregate fields expose sizes and timing only, never request values.
Wasm FastCGI workers retain up to `MYSQL_PERSISTENT_POOL_SIZE` credential-keyed MySQL connections (default `8`; set `0` to disable). UCE calls the client library's connection-reset operation before another request receives a cached connection, clearing transactions, temporary tables, session variables, and selected databases while avoiding a new authentication handshake. Same-request leases continue to share state until request cleanup.
:example

View File

@ -86,6 +86,16 @@ RENDER(Request& context)
DValue nav_dash_summary = nav_dash.filter({"title"});
check("DValue collection helpers", empty_pop.to_string() == "" && app_nav.is_list() && nav_titles["0"].to_string() == "Dashboard" && join(nav_dash_summary.keys(), ",") == "title" && nav_dash.values()["0"].to_string() == "app" && nav_dash.values()["1"].to_string() == "Dashboard", json_encode(nav_titles));
DValue copy_source; copy_source["nested"]["value"] = "original";
DValue copied = copy_source;
copied["nested"]["value"] = "copy";
DValue move_source; move_source["nested"]["value"] = "moved";
DValue moved = std::move(move_source);
DValue reference_target; DValue reference; reference.set_reference(&reference_target);
DValue reference_source; reference_source["nested"]["value"] = "target";
reference = reference_source;
check("DValue copy/move assignment preserves value and reference semantics", copy_source["nested"]["value"].to_string() == "original" && copied["nested"]["value"].to_string() == "copy" && moved["nested"]["value"].to_string() == "moved" && reference_target["nested"]["value"].to_string() == "target", json_encode(moved) + " / " + json_encode(reference_target));
DValue conv;
conv["name"] = "ada";
conv["count"] = "12";

View File

@ -162,7 +162,9 @@ RENDER(Request& context)
bool unit_module_profile_valid = serialized_reads_bounded && unit_operation_profile_valid && perf["unit_module_cache_hit_count"].to_u64() + module_misses == perf["unit_load_count"].to_u64() && serialized_hits + perf["unit_module_compile_count"].to_u64() == module_misses && perf["unit_module_lookup_us"].type != 'S' && perf["unit_module_read_us"].type != 'S' && perf["unit_module_read_bytes"].type != 'S' && perf["unit_module_parse_us"].type != 'S' && perf["unit_module_compile_us"].type != 'S' && perf["unit_module_classify_us"].type != 'S';
bool transport_profile_valid = perf["transport_params_us"].type != 'S' && perf["transport_input_us"].type != 'S' && perf["handler_queue_us"].type != 'S' && perf["accept_us"].to_f64() + 2 >= perf["transport_params_us"].to_f64() + perf["transport_input_us"].to_f64() + perf["handler_queue_us"].to_f64();
u64 phase_cpu_us = perf["workspace_setup_cpu_us"].to_u64() + perf["workspace_birth_cpu_us"].to_u64() + perf["context_apply_cpu_us"].to_u64();
bool workspace_cpu_profile_valid = perf["workspace_wall_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() <= perf["workspace_wall_us"].to_u64() + 2 && (perf["workspace_cpu_us"].to_u64() >= perf["workspace_wall_us"].to_u64() ? perf["workspace_wait_us"].to_u64() == 0 : perf["workspace_wait_us"].to_u64() + perf["workspace_cpu_us"].to_u64() == perf["workspace_wall_us"].to_u64()) && perf["workspace_setup_cpu_us"].to_u64() <= perf["workspace_setup_us"].to_u64() + 2 && perf["workspace_birth_cpu_us"].to_u64() <= perf["workspace_birth_us"].to_u64() + 2 && perf["context_apply_cpu_us"].to_u64() <= perf["context_apply_us"].to_u64() + 2 && phase_cpu_us + perf["execution_cpu_us"].to_u64() == perf["workspace_cpu_us"].to_u64();
u64 birth_profile_us = perf["birth_policy_us"].to_u64() + perf["birth_import_us"].to_u64() + perf["birth_instantiate_us"].to_u64() + perf["birth_exports_us"].to_u64() + perf["birth_initialize_us"].to_u64();
u64 context_profile_us = perf["context_encode_us"].to_u64() + perf["context_allocate_us"].to_u64() + perf["context_write_us"].to_u64() + perf["context_guest_apply_us"].to_u64() + perf["context_free_us"].to_u64();
bool workspace_cpu_profile_valid = perf["workspace_wall_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() <= perf["workspace_wall_us"].to_u64() + 2 && (perf["workspace_cpu_us"].to_u64() >= perf["workspace_wall_us"].to_u64() ? perf["workspace_wait_us"].to_u64() == 0 : perf["workspace_wait_us"].to_u64() + perf["workspace_cpu_us"].to_u64() == perf["workspace_wall_us"].to_u64()) && perf["workspace_setup_cpu_us"].to_u64() <= perf["workspace_setup_us"].to_u64() + 2 && perf["workspace_birth_cpu_us"].to_u64() <= perf["workspace_birth_us"].to_u64() + 2 && perf["context_apply_cpu_us"].to_u64() <= perf["context_apply_us"].to_u64() + 2 && birth_profile_us <= perf["workspace_birth_us"].to_u64() && perf["context_bytes"].to_u64() > 0 && context_profile_us <= perf["context_apply_us"].to_u64() && phase_cpu_us + perf["execution_cpu_us"].to_u64() == perf["workspace_cpu_us"].to_u64();
u64 operation_hostcalls = 0;
u64 operation_hostcall_us = 0;
u64 operation_hostcall_cpu_us = 0;

View File

@ -652,7 +652,7 @@ void DValue::set_bool(bool b)
_list_mode = false;
}
void DValue::set(DValue source)
void DValue::set(const DValue& source)
{
DValue* target = reference_target();
if(target)
@ -691,6 +691,42 @@ void DValue::set(DValue source)
}
}
void DValue::set(DValue&& source)
{
DValue* target = reference_target();
if(target)
{
target->set(std::move(source));
return;
}
set_type(source.type);
switch(type)
{
case('S'):
_String = std::move(source._String);
_list_mode = false;
break;
case('F'):
_float = source._float;
_list_mode = false;
break;
case('B'):
_bool = source._bool;
_list_mode = false;
break;
case('M'):
_map = std::move(source._map);
_array_index = source._array_index;
_list_mode = source._list_mode;
break;
case('P'):
case('R'):
_ptr = source._ptr;
_list_mode = false;
break;
}
}
void DValue::set(StringMap source)
{
DValue* target = reference_target();
@ -782,7 +818,7 @@ DValue& DValue::operator [] (String s) {
void DValue::operator = (String v) { set(v); }
void DValue::operator = (f64 v) { set(v); }
void DValue::operator = (void* v) { set(v); }
void DValue::operator = (DValue v) { set(v); }
void DValue::operator = (DValue v) { set(std::move(v)); }
void DValue::operator = (StringMap v) { set(v); }
void DValue::push(const DValue& child)
@ -1076,7 +1112,7 @@ bool ucb_decode_node(const String& src, size_t& offset, DValue& out, String& err
DValue child;
if(!ucb_decode_node(src, offset, child, error, depth + 1))
return(false);
out[key] = child;
out[key] = std::move(child);
}
return(true);
}
@ -1126,7 +1162,7 @@ bool ucb_decode(const String& encoded, DValue& out, String* error_out)
DValue decoded;
if(ucb_decode_node(encoded, offset, decoded, error) && offset == encoded.size())
{
out = decoded;
out = std::move(decoded);
if(error_out)
*error_out = "";
return(true);

View File

@ -8,6 +8,9 @@ String json_escape(String s, char quote_char = '"');
// JSON-decoded values, and metadata trees can be consumed without repetitive
// manual parsing at each call site.
struct DValue {
DValue() = default;
DValue(const DValue&) = default;
DValue(DValue&&) = default;
char type = 'S';
@ -49,7 +52,8 @@ struct DValue {
void set(void* p);
void set(f64 f);
void set_bool(bool b);
void set(DValue source);
void set(const DValue& source);
void set(DValue&& source);
void set(StringMap source);
void set_array();
void set_reference(DValue* target);

View File

@ -818,7 +818,8 @@ int uce_wasm_apply_context(const char* buf, size_t len)
uce_host_log(3, error.data(), error.size());
return(1);
}
wasm_request.call = decoded;
wasm_request.call = std::move(decoded);
DValue& applied = wasm_request.call;
auto apply_map = [](DValue* source, StringMap& dest) {
dest.clear();
if(source)
@ -826,23 +827,23 @@ int uce_wasm_apply_context(const char* buf, size_t len)
dest[key] = item.to_string();
});
};
if(decoded.key("server_config"))
apply_map(decoded.key("server_config"), wasm_server.config);
apply_map(decoded.key("params"), wasm_request.params);
apply_map(decoded.key("get"), wasm_request.get);
apply_map(decoded.key("post"), wasm_request.post);
apply_map(decoded.key("cookies"), wasm_request.cookies);
apply_map(decoded.key("session"), wasm_request.session);
if(DValue* session_id = decoded.key("session_id"))
if(applied.key("server_config"))
apply_map(applied.key("server_config"), wasm_server.config);
apply_map(applied.key("params"), wasm_request.params);
apply_map(applied.key("get"), wasm_request.get);
apply_map(applied.key("post"), wasm_request.post);
apply_map(applied.key("cookies"), wasm_request.cookies);
apply_map(applied.key("session"), wasm_request.session);
if(DValue* session_id = applied.key("session_id"))
wasm_request.session_id = session_id->to_string();
if(DValue* session_name = decoded.key("session_name"))
if(DValue* session_name = applied.key("session_name"))
wasm_request.session_name = session_name->to_string();
if(DValue* session_loaded_hash = decoded.key("session_loaded_hash"))
if(DValue* session_loaded_hash = applied.key("session_loaded_hash"))
wasm_request.session_loaded_hash = session_loaded_hash->to_string();
DValue* entry = decoded.key("entry_unit");
DValue* entry = applied.key("entry_unit");
if(entry)
wasm_request.resources.current_unit_file = entry->to_string();
DValue* raw_in = decoded.key("in");
DValue* raw_in = applied.key("in");
wasm_request.in = raw_in ? raw_in->to_string() : "";
// websocket event context: ws_send()/ws_close() capture into the dispatch
// list (the workspace owns no connections), which collect() carries back to
@ -851,7 +852,7 @@ int uce_wasm_apply_context(const char* buf, size_t len)
wasm_request.resources.websocket_connection_state_before = DValue();
wasm_request.resources.websocket_dispatch_commands = DValue();
wasm_request.resources.websocket_dispatch_capture = false;
DValue* ws = decoded.key("ws");
DValue* ws = applied.key("ws");
if(ws)
{
wasm_request.resources.websocket_connection_id = (*ws)["connection_id"].to_string();

View File

@ -172,8 +172,19 @@ struct WasmRequestProfile
u64 workspace_setup_cpu_us = 0;
u64 workspace_birth_us = 0;
u64 workspace_birth_cpu_us = 0;
u64 birth_policy_us = 0;
u64 birth_import_us = 0;
u64 birth_instantiate_us = 0;
u64 birth_exports_us = 0;
u64 birth_initialize_us = 0;
u64 context_apply_us = 0;
u64 context_apply_cpu_us = 0;
u64 context_bytes = 0;
u64 context_encode_us = 0;
u64 context_allocate_us = 0;
u64 context_write_us = 0;
u64 context_guest_apply_us = 0;
u64 context_free_us = 0;
u64 entry_invoke_us = 0;
u64 output_collect_us = 0;
u64 workspace_complete_us = 0;
@ -862,6 +873,8 @@ static bool wasm_read_metadata_file(const String& path, std::vector<u8>& metadat
// ---- worker (per process): engine + compiled module caches ----------------
class WasmWorkspace;
class WasmWorker
{
public:
@ -951,6 +964,21 @@ public:
wasmtime::Engine engine;
std::optional<wasmtime::Module> core_module;
std::optional<wasmtime::Linker> core_linker;
struct CoreImport
{
String module;
String name;
bool table = false;
};
std::vector<CoreImport> core_imports;
u32 core_table_min = 0;
bool core_table_imported = false;
u64 core_host_import_count = 0;
// A render worker dispatches one workspace at a time. Persistent host Func
// definitions resolve their request-owned state through this pointer; forked
// task children inherit the live workspace copy before the parent drops it.
WasmWorkspace* active_workspace = 0;
WasmDylinkInfo core_dylink;
// unit artifact path in the W2 cache: cache_root + <abs source dir> + /<file>.wasm
@ -1257,6 +1285,7 @@ public:
explicit WasmWorkspace(WasmWorker& w) : worker(w), store(w.engine)
{
worker.active_workspace = this;
}
void set_perf_snapshot(u64 worker_pid, u64 parent_pid, u64 request_count,
@ -1287,6 +1316,8 @@ public:
#endif
~WasmWorkspace()
{
if(worker.active_workspace == this)
worker.active_workspace = 0;
for(auto& h : file_handles)
{
if(h.fd >= 0)
@ -1341,50 +1372,82 @@ public:
String birth()
{
auto phase_start = std::chrono::steady_clock::now();
auto phase_us = [&]() -> u64 {
auto now = std::chrono::steady_clock::now();
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_start).count();
phase_start = now;
return(elapsed);
};
auto cx = ctx();
store.limiter(worker.cfg.memory_limit, -1, -1, -1, -1);
cx.set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
birth_policy_us = phase_us();
auto& module = *worker.core_module;
std::vector<wasmtime::Extern> imports;
for(auto import_type : module.imports())
if(!worker.core_linker)
{
String mod(import_type.module());
String name(import_type.name());
auto extern_type = wasmtime::ExternType::from_import(import_type);
if(auto* table_ref = std::get_if<wasmtime::TableType::Ref>(&extern_type))
worker.core_linker.emplace(worker.engine);
for(auto import_type : module.imports())
{
String mod(import_type.module());
String name(import_type.name());
auto extern_type = wasmtime::ExternType::from_import(import_type);
if(auto* table_ref = std::get_if<wasmtime::TableType::Ref>(&extern_type))
{
if(name.rfind("__indirect_function_table", 0) != 0)
return("core imports unexpected table " + mod + "." + name);
worker.core_table_min = table_ref->min();
worker.core_table_imported = true;
worker.core_imports.push_back({ mod, name, true });
continue;
}
auto* func_ref = std::get_if<wasmtime::FuncType::Ref>(&extern_type);
if(!func_ref)
return("core has unexpected non-func import " + mod + "." + name);
wasmtime::FuncType func_type(*func_ref);
String error = make_host_import(*worker.core_linker, mod, name, func_type);
if(error != "")
return(error);
worker.core_imports.push_back({ mod, name, false });
}
}
if(!worker.core_table_imported)
return("core does not import __indirect_function_table — rebuild core with --import-table");
std::vector<wasmtime::Extern> imports;
u32 total = worker.core_table_min + worker.cfg.table_headroom;
wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total);
auto table_created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional<wasmtime::Func>()));
if(!table_created)
return("table create failed: " + String(table_created.err().message()));
table.emplace(table_created.ok());
table_next_free = worker.core_table_min;
for(auto& import : worker.core_imports)
{
if(import.table)
{
if(name.rfind("__indirect_function_table", 0) != 0)
return("core imports unexpected table " + mod + "." + name);
u32 core_min = table_ref->min();
u32 total = core_min + worker.cfg.table_headroom;
wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total);
auto created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional<wasmtime::Func>()));
if(!created)
return("table create failed: " + String(created.err().message()));
table.emplace(created.ok());
table_next_free = core_min;
imports.push_back(*table);
continue;
}
auto* func_ref = std::get_if<wasmtime::FuncType::Ref>(&extern_type);
if(!func_ref)
return("core has unexpected non-func import " + mod + "." + name);
wasmtime::FuncType func_type(*func_ref);
imports.push_back(make_host_import(cx, mod, name, func_type));
auto host_import = worker.core_linker->get(cx, import.module, import.name);
if(!host_import)
return("core host import unavailable: " + import.module + "." + import.name);
imports.push_back(*host_import);
}
if(!table)
return("core does not import __indirect_function_table — rebuild core with --import-table");
hostcall_operation_slots = worker.hostcall_operation_names.size();
birth_import_us = phase_us();
auto created = wasmtime::Instance::create(cx, module, imports);
if(!created)
return("core instantiation failed: " + trap_text(created.err()));
core.emplace(created.ok());
birth_instantiate_us = phase_us();
auto exported_memory = core->get(cx, "memory");
if(!exported_memory || !std::get_if<wasmtime::Memory>(&*exported_memory))
return("core does not export memory");
memory.emplace(std::get<wasmtime::Memory>(*exported_memory));
birth_exports_us = phase_us();
String error = call_core("_initialize", {}, 0);
if(error != "")
@ -1403,6 +1466,7 @@ public:
error = call_core("uce_wasm_request", {}, &request_ptr);
if(error != "")
return(error);
birth_initialize_us = phase_us();
if(request_ptr == 0)
return("core returned null Request*");
return("");
@ -1410,21 +1474,34 @@ public:
String apply_context(const DValue& context_tree)
{
auto phase_start = std::chrono::steady_clock::now();
auto phase_us = [&]() -> u64 {
auto now = std::chrono::steady_clock::now();
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_start).count();
phase_start = now;
return(elapsed);
};
String encoded = ucb_encode(context_tree);
context_bytes = encoded.size();
context_encode_us = phase_us();
int32_t guest_ptr = 0;
String error = call_core("uce_alloc", { (int32_t)encoded.size() }, &guest_ptr);
context_allocate_us = phase_us();
if(error != "")
return(error);
if(guest_ptr == 0)
return("guest uce_alloc failed for context buffer");
error = guest_write((u32)guest_ptr, encoded);
context_write_us = phase_us();
if(error != "")
return(error);
int32_t rc = 0;
error = call_core("uce_wasm_apply_context", { guest_ptr, (int32_t)encoded.size() }, &rc);
context_guest_apply_us = phase_us();
if(error != "")
return(error);
call_core("uce_free", { guest_ptr }, 0);
context_free_us = phase_us();
if(rc != 0)
return("uce_wasm_apply_context returned " + std::to_string(rc));
return("");
@ -1532,7 +1609,6 @@ private:
std::vector<LoadedUnit> units;
std::map<String, size_t> units_by_source;
std::map<String, u32> handler_slots; // source + ":" + symbol → table slot
std::vector<wasmtime::Func> host_funcs; // keep host imports alive
bool stale_component_mutation_blocked = false;
String stale_component_mutation_status;
bool component_source_generation_checked = false;
@ -2315,18 +2391,22 @@ private:
// ---- host imports for the core -----------------------------------------
wasmtime::Extern make_host_import(wasmtime::Store::Context cx, const String& mod, const String& name, const wasmtime::FuncType& func_type)
String make_host_import(wasmtime::Linker& linker, const String& mod, const String& name, const wasmtime::FuncType& func_type)
{
using namespace wasmtime;
WasmWorkspace* self = this;
struct ActiveWorkspace
{
WasmWorker* worker;
WasmWorkspace* operator->() const { return(worker->active_workspace); }
};
ActiveWorkspace self = { &worker };
auto add = [&](auto&& callback) -> Extern {
u64 profile_index = self->hostcall_operation_slots;
auto add = [&](auto&& callback) -> String {
u64 profile_index = worker.core_host_import_count++;
if(profile_index < WasmRequestProfile::HOSTCALL_OPERATION_MAX)
{
self->hostcall_operation_slots++;
if(self->worker.hostcall_operation_names.size() <= profile_index)
self->worker.hostcall_operation_names.push_back(name);
if(worker.hostcall_operation_names.size() <= profile_index)
worker.hostcall_operation_names.push_back(name);
}
bool profile_enabled = name != "uce_host_request_perf";
bool profile_mysql = name == "uce_host_mysql";
@ -2368,15 +2448,16 @@ private:
}
return(result);
};
Func func(cx, func_type, profiled);
host_funcs.push_back(func);
return(host_funcs.back());
auto defined = linker.func_new(mod, name, func_type, profiled);
if(!defined)
return("core host import failed: " + mod + "." + name + ": " + String(defined.err().message()));
return("");
};
// Hostcall blocklist (UCE_HOSTCALL_BLOCKLIST): a sysadmin-disabled hostcall
// resolves to a trap stub instead of its real implementation, so a unit
// invoking it fails at runtime into the configurable error page. The
// decision is made once per import at workspace birth — no per-call cost,
// decision is made once per worker when imports are defined — no per-call cost,
// and zero cost when nothing is blocked. A small core set stays exempt so
// the runtime itself cannot be bricked.
if(mod == "env" && !worker.cfg.hostcall_blocklist.empty() && name.rfind("uce_host_", 0) == 0)
@ -2437,8 +2518,19 @@ private:
response["workspace_setup_cpu_us"] = (f64)self->workspace_setup_cpu_us;
response["workspace_birth_us"] = (f64)self->workspace_birth_us;
response["workspace_birth_cpu_us"] = (f64)self->workspace_birth_cpu_us;
response["birth_policy_us"] = (f64)self->birth_policy_us;
response["birth_import_us"] = (f64)self->birth_import_us;
response["birth_instantiate_us"] = (f64)self->birth_instantiate_us;
response["birth_exports_us"] = (f64)self->birth_exports_us;
response["birth_initialize_us"] = (f64)self->birth_initialize_us;
response["context_apply_us"] = (f64)self->context_apply_us;
response["context_apply_cpu_us"] = (f64)self->context_apply_cpu_us;
response["context_bytes"] = (f64)self->context_bytes;
response["context_encode_us"] = (f64)self->context_encode_us;
response["context_allocate_us"] = (f64)self->context_allocate_us;
response["context_write_us"] = (f64)self->context_write_us;
response["context_guest_apply_us"] = (f64)self->context_guest_apply_us;
response["context_free_us"] = (f64)self->context_free_us;
u64 phase_cpu_us = self->workspace_setup_cpu_us + self->workspace_birth_cpu_us + self->context_apply_cpu_us;
response["execution_cpu_us"] = (f64)(workspace_cpu_us > phase_cpu_us ? workspace_cpu_us - phase_cpu_us : 0);
response["hostcall_count"] = (f64)self->hostcall_count;