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
+127 -35
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;