This commit is contained in:
udo
2026-06-13 02:07:38 +00:00
parent eb8f303f94
commit 577aae076e
27 changed files with 2937 additions and 21 deletions
+412
View File
@@ -0,0 +1,412 @@
// Production WASM W1 core entrypoint.
//
// This file deliberately includes the real UCE runtime amalgamation with
// __UCE_WASM_CORE__ enabled. Native-only pieces are carved out in the runtime
// sources, while the workspace-owned DValue ABI and output plumbing are built
// into core.wasm.
#define __UCE_WASM_CORE__ 1
#include "../lib/uce_lib.cpp"
#include "../lib/mysql-connector.h"
#include "../lib/sqlite-connector.h"
// ---- W3 connector membrane stubs -------------------------------------------
// Generated units reference the connector class surface through uce_lib.h, so
// the core must define it. Until the real hostcall connectors land (W5 parity
// scope: the starter uses no database), every operation fails cleanly with an
// explanatory error instead of trapping.
static const char* WASM_DB_UNAVAILABLE =
"database connectors are not yet available in the wasm workspace";
bool MySQL::connect(String host, String username, String password)
{
(void)host; (void)username; (void)password;
connection = 0;
statement_info = WASM_DB_UNAVAILABLE;
return(false);
}
void MySQL::disconnect() { connection = 0; }
String MySQL::error() { return(WASM_DB_UNAVAILABLE); }
String MySQL::escape(String raw, char quote_char) { (void)quote_char; return(raw); }
String MySQL::parse_query_parameters(String query, StringMap m) { (void)m; return(query); }
DValue MySQL::query(String q) { (void)q; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
DValue MySQL::query(String q, StringMap params) { (void)q; (void)params; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
DValue MySQL::get_pending_result() { return(DValue()); }
String mysql_escape(String raw, char quote_char) { (void)quote_char; return(raw); }
bool SQLite::connect(String path) { this->path = path; connection = 0; set_error(1, WASM_DB_UNAVAILABLE); return(false); }
void SQLite::disconnect() { connection = 0; }
String SQLite::error() { return(error_code ? String(WASM_DB_UNAVAILABLE) : String("")); }
DValue SQLite::query(String q) { (void)q; set_error(1, WASM_DB_UNAVAILABLE); return(DValue()); }
DValue SQLite::query(String q, const StringMap& params) { (void)q; (void)params; set_error(1, WASM_DB_UNAVAILABLE); return(DValue()); }
void SQLite::set_error(s32 code, String info) { error_code = code; statement_info = info; }
bool SQLite::apply_default_pragmas() { return(false); }
bool SQLite::bind_params(void* statement, const StringMap& params) { (void)statement; (void)params; return(false); }
DValue SQLite::collect_rows(void* statement) { (void)statement; return(DValue()); }
SQLite* sqlite_connect(String path)
{
SQLite* db = new SQLite();
db->request_cleanup_delete = true;
db->connect(path);
return(db);
}
void sqlite_disconnect(SQLite* db) { if(db) { db->disconnect(); if(db->request_cleanup_delete) delete db; } }
String sqlite_error(SQLite* db) { return(db ? db->error() : String(WASM_DB_UNAVAILABLE)); }
DValue sqlite_query(SQLite* db, String q) { return(db ? db->query(q) : DValue()); }
DValue sqlite_query(SQLite* db, String q, const StringMap& params) { return(db ? db->query(q, params) : DValue()); }
u64 sqlite_insert_id(SQLite* db) { return(db ? db->insert_id : 0); }
u32 sqlite_affected_rows(SQLite* db) { return(db ? db->affected_rows : 0); }
void cleanup_sqlite_connections() { }
static ServerState wasm_server;
static Request wasm_request;
static String wasm_output;
static String wasm_response_meta;
// ---- vague-linkage link anchors --------------------------------------------
// Units import libc++ template instantiations they use; --export-all only
// exports what the core itself instantiated. Some libc++ internals lack the
// hide-from-ABI attribute (the Phase 0 lambda finding), so units emit them as
// imports rather than binding locally. This function exists purely to make
// the core instantiate — and therefore export — the ones the site tree needs.
// Extend it when the loader reports "unresolved import env.<libc++ symbol>".
extern "C" void uce_wasm_link_anchors()
{
StringMap string_map;
string_map["k"] = "v";
string_map.erase(String("k")); // __tree::__erase_unique<String>
std::map<String, DValue> dvalue_map;
dvalue_map["k"] = DValue();
dvalue_map.erase(String("k"));
std::vector<String> string_list = { "a", "b" };
string_list.erase(string_list.begin());
std::set<String> string_set;
string_set.insert("k");
string_set.erase(String("k"));
// libc functions units may call that the core itself never references —
// taking their address forces them into the link (and --export-all)
static void* volatile libc_anchors[] = {
(void*)&atof, (void*)&atoi, (void*)&atol, (void*)&atoll,
(void*)&strtol, (void*)&strtoul, (void*)&strtoll, (void*)&strtoull,
(void*)&strtod, (void*)&strtof,
(void*)&qsort, (void*)&bsearch,
(void*)&snprintf, (void*)&sscanf,
(void*)&memmove, (void*)&strncmp, (void*)&strncpy,
// memchr/strchr/strrchr/strstr are C++-overloaded; cast to the C shape
(void*)(const void* (*)(const void*, int, size_t))&memchr,
(void*)(const char* (*)(const char*, int))&strchr,
(void*)(const char* (*)(const char*, int))&strrchr,
(void*)(const char* (*)(const char*, const char*))&strstr,
};
(void)libc_anchors;
}
// W3 membrane: the host resolves component/render targets to funcref-table
// slots (loading units lazily) and writes the resolved unit path back so
// nested relative component resolution keeps working.
extern "C" int32_t uce_host_component_resolve(
const char* target, size_t target_len, int32_t kind,
const char* current_unit, size_t current_unit_len,
char* resolved_buf, size_t resolved_cap);
// target → table slot, reset per request (workspaces die with the request,
// but a single workspace can render the same component many times)
static std::map<String, s32> wasm_component_slots;
// These mirror small page-runtime pieces of compiler.cpp, which is carved
// out of the wasm core wholesale (it is the native toolchain: parser, clang
// driver, dlopen). Kept byte-identical where possible.
String component_normalize_path(String name)
{
name = trim(name);
if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce")
return(name);
return(name + ".uce");
}
void component_parse_target(String target, String& file_name, String& render_name)
{
target = trim(target);
render_name = "";
auto render_split_pos = target.find(":");
if(render_split_pos != String::npos)
{
render_name = trim(target.substr(render_split_pos + 1));
target = trim(target.substr(0, render_split_pos));
}
file_name = target;
}
String component_error_banner(String message)
{
return("<div class=\"banner\">" + html_escape(message) + "</div>");
}
struct RequestPropsScope
{
Request* context = 0;
DValue previous_props;
RequestPropsScope(Request* context, const DValue& props)
{
this->context = context;
if(this->context)
{
previous_props = this->context->props;
this->context->props = props;
}
}
~RequestPropsScope()
{
if(context)
context->props = previous_props;
}
};
// kind values shared with the host loader (see src/wasm/worker.h)
enum WasmResolveKind { WASM_RESOLVE_COMPONENT = 0, WASM_RESOLVE_RENDER = 1, WASM_RESOLVE_EXISTS = 2 };
static s32 wasm_resolve_target(String target, s32 kind, String* resolved_out = 0)
{
String cache_key = std::to_string(kind) + ":" + target;
auto cached = wasm_component_slots.find(cache_key);
if(cached != wasm_component_slots.end() && kind != WASM_RESOLVE_EXISTS)
return(cached->second);
char resolved[512];
String current = context ? context->resources.current_unit_file : "";
s32 slot = uce_host_component_resolve(
target.data(), target.size(), kind,
current.data(), current.size(),
resolved, sizeof(resolved));
if(resolved_out && slot)
*resolved_out = String(resolved, strnlen(resolved, sizeof(resolved)));
if(kind != WASM_RESOLVE_EXISTS)
wasm_component_slots[cache_key] = slot;
return(slot);
}
String component_resolve(String name)
{
String resolved;
if(wasm_resolve_target(trim(name), WASM_RESOLVE_EXISTS, &resolved))
return(resolved);
return("");
}
bool component_exists(String name)
{
return(component_resolve(name) != "");
}
void component_render(String name, DValue props, Request& request)
{
String resolved;
s32 slot = wasm_resolve_target(trim(name), WASM_RESOLVE_COMPONENT, &resolved);
if(!slot)
{
print(component_error_banner("component not found: " + trim(name)));
return;
}
RequestPropsScope props_scope(&request, props);
String previous_unit = request.resources.current_unit_file;
if(resolved != "")
request.resources.current_unit_file = resolved;
// a wasm function pointer is its index in the shared funcref table; the
// host returned the handler's slot, so this is a plain call_indirect
request_ref_handler handler = (request_ref_handler)(uintptr_t)slot;
handler(request);
request.resources.current_unit_file = previous_unit;
}
void component_render(String name) { DValue props; component_render(name, props, *context); }
void component_render(String name, Request& request) { DValue props; component_render(name, props, request); }
void component_render(String name, DValue props) { component_render(name, props, *context); }
String component(String name, DValue props, Request& request)
{
ob_start();
component_render(name, props, request);
return(ob_get_close());
}
String component(String name) { DValue props; return(component(name, props, *context)); }
String component(String name, Request& request) { DValue props; return(component(name, props, request)); }
String component(String name, DValue props) { return(component(name, props, *context)); }
void unit_render(String file_name, Request& request)
{
String resolved;
s32 slot = wasm_resolve_target(trim(file_name), WASM_RESOLVE_RENDER, &resolved);
if(!slot)
{
print(component_error_banner("unit not found: " + trim(file_name)));
return;
}
String previous_unit = request.resources.current_unit_file;
if(resolved != "")
request.resources.current_unit_file = resolved;
request_ref_handler handler = (request_ref_handler)(uintptr_t)slot;
handler(request);
request.resources.current_unit_file = previous_unit;
}
void unit_render(String file_name) { unit_render(file_name, *context); }
extern "C" {
void* uce_alloc(size_t len)
{
return(malloc(len));
}
void uce_free(void* ptr)
{
free(ptr);
}
u32 uce_wasm_core_abi_version()
{
return(6);
}
int uce_wasm_core_init()
{
wasm_server.config = default_config();
wasm_request.server = &wasm_server;
// the primary output stream must live ON ob_stack (native semantics):
// ob_get_close()/ob_close() pop and rebalance against the stack, so a
// stream outside it would be orphaned by the first component() capture
if(wasm_request.ob_stack.empty())
wasm_request.ob_start();
context = &wasm_request;
return(0);
}
void uce_wasm_core_reset_request()
{
if(context == 0)
uce_wasm_core_init();
wasm_request.call = DValue();
wasm_request.props = DValue();
wasm_request.params.clear();
wasm_request.get.clear();
wasm_request.post.clear();
wasm_request.header.clear();
wasm_request.set_cookies.clear();
wasm_request.response_code = "HTTP/1.1 200 OK";
wasm_request.flags = Request::Flags();
wasm_request.stats = Request::Stats();
for(auto* stream : wasm_request.ob_stack)
delete stream;
wasm_request.ob_stack.clear();
wasm_request.ob_start();
wasm_output = "";
wasm_response_meta = "";
wasm_request.cookies.clear();
wasm_request.session.clear();
wasm_request.out = "";
wasm_request.resources.current_unit_file = "";
wasm_component_slots.clear();
}
// Host pushes the UCEB1-encoded request context into a guest buffer
// (uce_alloc) and applies it here; mirrors the native param population.
int uce_wasm_apply_context(const char* buf, size_t len)
{
if(context == 0)
uce_wasm_core_init();
DValue decoded;
String error;
if(!ucb_decode(String(buf, len), decoded, &error))
{
uce_host_log(3, error.data(), error.size());
return(1);
}
wasm_request.call = decoded;
auto apply_map = [](DValue* source, StringMap& dest) {
dest.clear();
if(source)
source->each([&](const DValue& item, String key) {
dest[key] = item.to_string();
});
};
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);
DValue* entry = decoded.key("entry_unit");
if(entry)
wasm_request.resources.current_unit_file = entry->to_string();
return(0);
}
Request* uce_wasm_request()
{
if(context == 0)
uce_wasm_core_init();
return(&wasm_request);
}
// After render: response metadata (status line, headers, cookies, session)
// goes back to the host as UCEB1.
void uce_wasm_finish_response_meta()
{
DValue meta;
meta["status"] = wasm_request.response_code;
for(auto& header : wasm_request.header)
meta["headers"][header.first] = header.second;
for(auto& cookie : wasm_request.set_cookies)
{
DValue cookie_value;
cookie_value = cookie;
meta["cookies"].push(cookie_value);
}
for(auto& entry : wasm_request.session)
meta["session"][entry.first] = entry.second;
wasm_response_meta = ucb_encode(meta);
}
const char* uce_wasm_response_meta_data()
{
return(wasm_response_meta.data());
}
size_t uce_wasm_response_meta_size()
{
return(wasm_response_meta.size());
}
void uce_print_bytes(const char* data, size_t len)
{
if(context == 0)
uce_wasm_core_init();
if(context->ob && data && len)
context->ob->write(data, len);
}
void uce_wasm_finish_output()
{
// ob_stack[0] is the request's primary stream; nested captures above it
// belong to unbalanced ob_start() calls and are intentionally ignored
wasm_output = wasm_request.ob_stack.empty() ? String("") : wasm_request.ob_stack[0]->str();
}
const char* uce_wasm_output_data()
{
return(wasm_output.data());
}
size_t uce_wasm_output_size()
{
return(wasm_output.size());
}
}
+8
View File
@@ -0,0 +1,8 @@
uce_host_time
uce_host_time_precise
uce_host_env
uce_host_log
uce_host_random
uce_host_component_resolve
uce_host_file_exists
uce_host_file_read
+21
View File
@@ -0,0 +1,21 @@
atof
atoi
atol
atoll
strtol
strtoul
strtoll
strtoull
strtod
strtof
qsort
bsearch
snprintf
sscanf
memmove
memchr
strncmp
strncpy
strchr
strrchr
strstr
+308
View File
@@ -0,0 +1,308 @@
// W1 smoke driver for the production UCE core.wasm.
#include <wasm.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#define FAIL(...) do { fprintf(stderr, "FAIL: " __VA_ARGS__); fprintf(stderr, "\n"); exit(1); } while(0)
#define CHECK(cond, ...) do { if(!(cond)) FAIL(__VA_ARGS__); } while(0)
static std::vector<uint8_t> read_file(const char* path)
{
FILE* f = fopen(path, "rb");
CHECK(f, "cannot open %s", path);
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
std::vector<uint8_t> data((size_t)n);
CHECK(fread(data.data(), 1, data.size(), f) == data.size(), "short read on %s", path);
fclose(f);
return(data);
}
static std::string wasm_name(const wasm_name_t* name)
{
std::string s(name->data, name->size);
while(!s.empty() && s.back() == '\0') s.pop_back();
return(s);
}
static wasm_store_t* g_store_for_traps = nullptr;
static void set_i32(wasm_val_t* result, int32_t value)
{
result->kind = WASM_I32;
result->of.i32 = value;
}
static void set_i64(wasm_val_t* result, int64_t value)
{
result->kind = WASM_I64;
result->of.i64 = value;
}
static void set_f64(wasm_val_t* result, double value)
{
result->kind = WASM_F64;
result->of.f64 = value;
}
static wasm_trap_t* host_time(void*, const wasm_val_vec_t*, wasm_val_vec_t* results)
{
set_i64(&results->data[0], 1700000000);
return(nullptr);
}
static wasm_trap_t* host_time_precise(void*, const wasm_val_vec_t*, wasm_val_vec_t* results)
{
set_f64(&results->data[0], 1700000000.25);
return(nullptr);
}
static wasm_trap_t* host_env(void*, const wasm_val_vec_t*, wasm_val_vec_t* results)
{
set_i32(&results->data[0], 0);
return(nullptr);
}
static wasm_memory_t* g_memory = nullptr;
static wasm_trap_t* host_random(void*, const wasm_val_vec_t* args, wasm_val_vec_t* results)
{
uint32_t ptr = args->data[0].of.i32;
uint32_t len = args->data[1].of.i32;
uint8_t* mem = (uint8_t*)wasm_memory_data(g_memory);
size_t mem_size = wasm_memory_data_size(g_memory);
if((size_t)ptr + len > mem_size)
{
set_i32(&results->data[0], 0);
return(nullptr);
}
for(uint32_t i = 0; i < len; ++i)
mem[ptr + i] = (uint8_t)(0x5au ^ (i * 29u));
set_i32(&results->data[0], len);
return(nullptr);
}
static wasm_trap_t* host_log(void*, const wasm_val_vec_t*, wasm_val_vec_t*)
{
return(nullptr);
}
static wasm_trap_t* stub_callback(void* env, const wasm_val_vec_t*, wasm_val_vec_t*)
{
std::string label = (const char*)env;
std::string msg = "unexpected import called: " + label;
wasm_byte_vec_t message;
wasm_byte_vec_new(&message, msg.size(), msg.data());
wasm_trap_t* trap = wasm_trap_new(g_store_for_traps, &message);
wasm_byte_vec_delete(&message);
return(trap);
}
struct Instance
{
wasm_module_t* module = nullptr;
wasm_instance_t* instance = nullptr;
wasm_extern_vec_t exports = WASM_EMPTY_VEC;
std::map<std::string, wasm_extern_t*> by_name;
void index_exports()
{
wasm_exporttype_vec_t types = WASM_EMPTY_VEC;
wasm_module_exports(module, &types);
wasm_instance_exports(instance, &exports);
CHECK(types.size == exports.size, "export count mismatch");
for(size_t i = 0; i < types.size; ++i)
by_name[wasm_name(wasm_exporttype_name(types.data[i]))] = exports.data[i];
wasm_exporttype_vec_delete(&types);
}
wasm_func_t* func(const char* name)
{
auto it = by_name.find(name);
return(it == by_name.end() ? nullptr : wasm_extern_as_func(it->second));
}
wasm_memory_t* memory()
{
auto it = by_name.find("memory");
return(it == by_name.end() ? nullptr : wasm_extern_as_memory(it->second));
}
};
static void report_trap(wasm_trap_t* trap, const char* what)
{
if(!trap) return;
wasm_message_t msg;
wasm_trap_message(trap, &msg);
FAIL("trap during %s: %.*s", what, (int)msg.size, msg.data);
}
static int32_t call_i32(Instance& inst, const char* name, std::vector<int32_t> argv = {})
{
wasm_func_t* f = inst.func(name);
CHECK(f, "missing function %s", name);
CHECK(argv.size() <= 4, "too many args for %s", name);
wasm_val_t args_buf[4];
for(size_t i = 0; i < argv.size(); ++i) args_buf[i] = WASM_I32_VAL(argv[i]);
wasm_val_t result_buf[1] = { WASM_INIT_VAL };
wasm_val_vec_t args = { argv.size(), args_buf };
wasm_val_vec_t results = { 1, result_buf };
wasm_val_vec_t no_results = WASM_EMPTY_VEC;
wasm_trap_t* trap = wasm_func_call(f, &args, wasm_func_result_arity(f) ? &results : &no_results);
report_trap(trap, name);
return(wasm_func_result_arity(f) ? result_buf[0].of.i32 : 0);
}
static void write_bytes(wasm_memory_t* memory, uint32_t ptr, const std::string& data)
{
uint8_t* mem = (uint8_t*)wasm_memory_data(memory);
size_t mem_size = wasm_memory_data_size(memory);
CHECK((size_t)ptr + data.size() <= mem_size, "write outside memory");
memcpy(mem + ptr, data.data(), data.size());
}
static std::string read_bytes(wasm_memory_t* memory, uint32_t ptr, uint32_t len)
{
uint8_t* mem = (uint8_t*)wasm_memory_data(memory);
size_t mem_size = wasm_memory_data_size(memory);
CHECK((size_t)ptr + len <= mem_size, "read outside memory");
return(std::string((const char*)mem + ptr, len));
}
static std::string read_cstr(wasm_memory_t* memory, uint32_t ptr, uint32_t cap = 4096)
{
uint8_t* mem = (uint8_t*)wasm_memory_data(memory);
size_t mem_size = wasm_memory_data_size(memory);
CHECK(ptr < mem_size, "cstr starts outside memory");
std::string out;
for(uint32_t i = 0; i < cap && (size_t)ptr + i < mem_size; ++i)
{
if(mem[ptr + i] == 0)
return(out);
out.push_back((char)mem[ptr + i]);
}
FAIL("unterminated cstr");
}
static uint32_t read_u32(wasm_memory_t* memory, uint32_t ptr)
{
uint8_t* mem = (uint8_t*)wasm_memory_data(memory);
size_t mem_size = wasm_memory_data_size(memory);
CHECK((size_t)ptr + 4 <= mem_size, "u32 read outside memory");
return((uint32_t)mem[ptr] | ((uint32_t)mem[ptr + 1] << 8) | ((uint32_t)mem[ptr + 2] << 16) | ((uint32_t)mem[ptr + 3] << 24));
}
int main(int argc, char** argv)
{
const char* core_path = argc > 1 ? argv[1] : "/tmp/uce/wasm-w1/core.wasm";
wasm_engine_t* engine = wasm_engine_new();
CHECK(engine, "engine");
wasm_store_t* store = wasm_store_new(engine);
CHECK(store, "store");
g_store_for_traps = store;
std::vector<uint8_t> bytes = read_file(core_path);
wasm_byte_vec_t bv;
wasm_byte_vec_new(&bv, bytes.size(), (const char*)bytes.data());
Instance core;
core.module = wasm_module_new(store, &bv);
wasm_byte_vec_delete(&bv);
CHECK(core.module, "module load");
wasm_importtype_vec_t imports = WASM_EMPTY_VEC;
wasm_module_imports(core.module, &imports);
std::vector<wasm_extern_t*> import_externs(imports.size);
for(size_t i = 0; i < imports.size; ++i)
{
std::string mod = wasm_name(wasm_importtype_module(imports.data[i]));
std::string name = wasm_name(wasm_importtype_name(imports.data[i]));
const wasm_externtype_t* et = wasm_importtype_type(imports.data[i]);
CHECK(wasm_externtype_kind(et) == WASM_EXTERN_FUNC, "unexpected non-func import %s.%s", mod.c_str(), name.c_str());
const wasm_functype_t* ft = wasm_externtype_as_functype_const(et);
wasm_func_t* fn = nullptr;
if(mod == "env" && name == "uce_host_time") fn = wasm_func_new_with_env(store, ft, host_time, nullptr, nullptr);
else if(mod == "env" && name == "uce_host_time_precise") fn = wasm_func_new_with_env(store, ft, host_time_precise, nullptr, nullptr);
else if(mod == "env" && name == "uce_host_env") fn = wasm_func_new_with_env(store, ft, host_env, nullptr, nullptr);
else if(mod == "env" && name == "uce_host_random") fn = wasm_func_new_with_env(store, ft, host_random, nullptr, nullptr);
else if(mod == "env" && name == "uce_host_log") fn = wasm_func_new_with_env(store, ft, host_log, nullptr, nullptr);
else if(mod == "wasi_snapshot_preview1")
{
char* label = strdup((mod + "." + name).c_str());
fn = wasm_func_new_with_env(store, ft, stub_callback, label, nullptr);
}
else
FAIL("unexpected core import %s.%s", mod.c_str(), name.c_str());
CHECK(fn, "import function %s.%s", mod.c_str(), name.c_str());
import_externs[i] = wasm_func_as_extern(fn);
}
wasm_extern_vec_t iv = { import_externs.size(), import_externs.data() };
wasm_trap_t* trap = nullptr;
core.instance = wasm_instance_new(store, core.module, &iv, &trap);
report_trap(trap, "core instantiation");
CHECK(core.instance, "core instantiate");
core.index_exports();
CHECK(core.memory(), "core exports memory");
g_memory = core.memory();
if(core.func("_initialize")) call_i32(core, "_initialize");
CHECK(call_i32(core, "uce_wasm_core_init") == 0, "core init failed");
call_i32(core, "uce_wasm_core_reset_request");
CHECK(call_i32(core, "uce_wasm_core_abi_version") == 6, "unexpected ABI version");
wasm_memory_t* memory = core.memory();
int32_t root = call_i32(core, "uce_dv_root");
CHECK(root != 0, "uce_dv_root returned null");
std::string key = "message";
std::string value = "hello from W1 core";
int32_t key_ptr = call_i32(core, "uce_alloc", { (int32_t)key.size() });
int32_t value_ptr = call_i32(core, "uce_alloc", { (int32_t)value.size() });
write_bytes(memory, key_ptr, key);
write_bytes(memory, value_ptr, value);
int32_t child = call_i32(core, "uce_dv_get", { root, key_ptr, (int32_t)key.size() });
CHECK(child != 0, "uce_dv_get returned null");
call_i32(core, "uce_dv_set_value", { child, value_ptr, (int32_t)value.size() });
CHECK(call_i32(core, "uce_dv_find", { root, key_ptr, (int32_t)key.size() }) == child, "uce_dv_find mismatch");
CHECK(call_i32(core, "uce_dv_count", { root }) == 1, "root count mismatch");
CHECK(call_i32(core, "uce_dv_is_list", { root }) == 0, "root unexpectedly list-shaped");
int32_t value_len_ptr = call_i32(core, "uce_alloc", { 4 });
int32_t value_result_ptr = call_i32(core, "uce_dv_value", { child, value_len_ptr });
uint32_t value_result_len = read_u32(memory, value_len_ptr);
CHECK(read_bytes(memory, value_result_ptr, value_result_len) == value, "uce_dv_value mismatch");
int32_t encoded_len = call_i32(core, "uce_dv_encode", { root, 0, 0 });
CHECK(encoded_len > 5, "encoded length too small");
int32_t encoded_ptr = call_i32(core, "uce_alloc", { encoded_len });
CHECK(call_i32(core, "uce_dv_encode", { root, encoded_ptr, encoded_len }) == encoded_len, "encode length mismatch");
std::string encoded = read_bytes(memory, encoded_ptr, encoded_len);
CHECK(encoded.rfind("UCEB\x01", 0) == 0, "UCEB1 header missing");
int32_t decoded = call_i32(core, "uce_dv_decode", { encoded_ptr, encoded_len });
CHECK(decoded != 0, "uce_dv_decode failed");
CHECK(call_i32(core, "uce_dv_count", { decoded }) == 1, "decoded root count mismatch");
int32_t last_error_ptr = call_i32(core, "uce_dv_last_error");
CHECK(last_error_ptr != 0, "uce_dv_last_error returned null");
CHECK(read_cstr(memory, last_error_ptr) == "", "last error not clear after successful decode");
std::string bad = "bad";
int32_t bad_ptr = call_i32(core, "uce_alloc", { (int32_t)bad.size() });
write_bytes(memory, bad_ptr, bad);
CHECK(call_i32(core, "uce_dv_decode", { bad_ptr, (int32_t)bad.size() }) == 0, "bad UCEB1 decode unexpectedly succeeded");
CHECK(read_cstr(memory, call_i32(core, "uce_dv_last_error")) != "", "bad UCEB1 decode did not set error");
std::string out = "W1 output";
int32_t out_ptr = call_i32(core, "uce_alloc", { (int32_t)out.size() });
write_bytes(memory, out_ptr, out);
call_i32(core, "uce_print_bytes", { out_ptr, (int32_t)out.size() });
call_i32(core, "uce_wasm_finish_output");
int32_t output_len = call_i32(core, "uce_wasm_output_size");
int32_t output_ptr = call_i32(core, "uce_wasm_output_data");
CHECK(read_bytes(memory, output_ptr, output_len) == out, "output plumbing mismatch");
printf("W1 core.wasm smoke: abi=6 encoded=%d output=%d\n", encoded_len, output_len);
printf("W1 EXIT CRITERION: PASS\n");
return(0);
}
+184
View File
@@ -0,0 +1,184 @@
// W3 CLI driver — the exit gate for WASM-PROPOSAL §9.1 W3.
//
// Serves real requests through the production workspace runtime
// (src/wasm/worker.cpp): UCEB1 context in → core + lazily loaded real
// generated units → body/response-meta out. Each --repeat gets a fresh
// workspace, proving birth/drop. An epoch ticker thread enforces the CPU
// budget; the store limiter enforces memory; traps come back as collapsed
// wasm_trace summaries.
//
// Amalgamation TU (project style, like uce_lib.cpp): native types + DValue
// codec first, then the worker.
// same order as uce_lib.cpp, minus the native compiler/connector block
#include "../lib/types.cpp"
#include "../lib/dvalue.cpp"
#include "../lib/functionlib.cpp"
#include "../lib/hash.cpp"
#include "../lib/sys.cpp"
#include "../lib/uri.cpp"
#include "../lib/cli.cpp"
#include "../lib/wasm_trace.h"
#include "worker.cpp"
#include <atomic>
#include <thread>
static String arg_value(int argc, char** argv, int& i, const char* flag)
{
if(i + 1 >= argc)
{
fprintf(stderr, "missing value for %s\n", flag);
exit(2);
}
return(String(argv[++i]));
}
static String absolute_path(String path)
{
if(path.rfind("/", 0) == 0)
return(path);
char buf[4096];
if(!getcwd(buf, sizeof(buf)))
return(path);
return(String(buf) + "/" + path);
}
int main(int argc, char** argv)
{
WasmWorkerConfig cfg;
cfg.site_root = absolute_path("site");
String page;
String route;
StringMap params;
StringMap get_params;
std::vector<String> expects;
String expect_status;
int repeat = 1;
u64 epoch_period_ms = 50;
bool show_body = true;
for(int i = 1; i < argc; i++)
{
String arg = argv[i];
if(arg == "--core") cfg.core_wasm_path = arg_value(argc, argv, i, "--core");
else if(arg == "--site") cfg.site_root = absolute_path(arg_value(argc, argv, i, "--site"));
else if(arg == "--cache") cfg.cache_root = arg_value(argc, argv, i, "--cache");
else if(arg == "--page") page = arg_value(argc, argv, i, "--page");
else if(arg == "--route") route = arg_value(argc, argv, i, "--route");
else if(arg == "--repeat") repeat = atoi(arg_value(argc, argv, i, "--repeat").c_str());
else if(arg == "--expect") expects.push_back(arg_value(argc, argv, i, "--expect"));
else if(arg == "--expect-status") expect_status = arg_value(argc, argv, i, "--expect-status");
else if(arg == "--epoch-ticks") cfg.epoch_deadline_ticks = strtoull(arg_value(argc, argv, i, "--epoch-ticks").c_str(), 0, 10);
else if(arg == "--epoch-ms") epoch_period_ms = strtoull(arg_value(argc, argv, i, "--epoch-ms").c_str(), 0, 10);
else if(arg == "--mem-limit") cfg.memory_limit = strtoll(arg_value(argc, argv, i, "--mem-limit").c_str(), 0, 10);
else if(arg == "--table-headroom") cfg.table_headroom = (u32)atoi(arg_value(argc, argv, i, "--table-headroom").c_str());
else if(arg == "--quiet") show_body = false;
else if(arg == "--verbose") cfg.verbose = true;
else if(arg == "--param" || arg == "--get")
{
String pair = arg_value(argc, argv, i, arg.c_str());
auto eq = pair.find("=");
if(eq == String::npos)
{
fprintf(stderr, "%s expects K=V\n", arg.c_str());
return(2);
}
(arg == "--param" ? params : get_params)[pair.substr(0, eq)] = pair.substr(eq + 1);
}
else
{
fprintf(stderr, "unknown argument: %s\n", arg.c_str());
return(2);
}
}
if(page == "")
{
fprintf(stderr, "usage: w3_driver --page /demo/hello.uce [--site site] [--cache /tmp/uce/work]\n"
" [--core bin/wasm/core.wasm] [--param K=V ...] [--get K=V ...] [--route path]\n"
" [--repeat N] [--expect STR ...] [--expect-status STR] [--quiet] [--verbose]\n"
" [--epoch-ticks N] [--epoch-ms N] [--mem-limit BYTES]\n");
return(2);
}
String entry_unit = cfg.site_root + page;
WasmWorker worker(cfg);
String init_error = worker.init();
if(init_error != "")
{
fprintf(stderr, "FAIL: %s\n", init_error.c_str());
return(1);
}
// the epoch only advances through this ticker; period × deadline-ticks
// is the per-request CPU budget
std::atomic<bool> running(true);
std::thread ticker([&] {
while(running.load())
{
std::this_thread::sleep_for(std::chrono::milliseconds(epoch_period_ms));
worker.engine.increment_epoch();
}
});
DValue context_tree;
for(auto& entry : params)
context_tree["params"][entry.first] = entry.second;
if(context_tree.key("params") == 0 || !params.count("HTTP_HOST"))
context_tree["params"]["HTTP_HOST"] = "w3.test";
context_tree["params"]["SCRIPT_URL"] = page;
context_tree["params"]["REQUEST_METHOD"] = "GET";
for(auto& entry : get_params)
context_tree["get"][entry.first] = entry.second;
if(route != "")
{
context_tree["route"]["l_path"] = route;
context_tree["params"]["ROUTE_PATH"] = route;
}
context_tree["entry_unit"] = entry_unit;
bool all_ok = true;
WasmResponse last;
for(int request = 0; request < repeat && all_ok; request++)
{
f64 started = (f64)clock() / CLOCKS_PER_SEC;
last = wasm_worker_serve(worker, context_tree, entry_unit);
f64 elapsed_ms = ((f64)clock() / CLOCKS_PER_SEC - started) * 1000.0;
printf("==== request %d/%d (%.1f ms cpu) ====\n", request + 1, repeat, elapsed_ms);
if(!last.ok)
{
printf("ERROR:\n%s\n", last.error.c_str());
all_ok = false;
break;
}
printf("status: %s\n", last.meta["status"].to_string().c_str());
if(last.meta.key("headers"))
last.meta["headers"].each([](const DValue& value, String key) {
printf("header: %s: %s\n", key.c_str(), value.to_string().c_str());
});
if(show_body)
printf("---- body (%zu bytes) ----\n%.*s\n----\n",
last.body.size(), (int)last.body.size(), last.body.data());
else
printf("body: %zu bytes\n", last.body.size());
}
running.store(false);
ticker.join();
if(all_ok && expect_status != "" && last.meta["status"].to_string().find(expect_status) == String::npos)
{
printf("FAIL: status %s does not contain %s\n", last.meta["status"].to_string().c_str(), expect_status.c_str());
all_ok = false;
}
for(auto& expect : expects)
if(all_ok && last.body.find(expect) == String::npos)
{
printf("FAIL: body does not contain %s\n", expect.c_str());
all_ok = false;
}
printf(all_ok ? "W3 RESULT: PASS\n" : "W3 RESULT: FAIL\n");
return(all_ok ? 0 : 1);
}
+1074
View File
File diff suppressed because it is too large Load Diff