Reuse core host definitions across requests
This commit is contained in:
+127
-35
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user