This commit is contained in:
udo
2026-06-13 16:19:52 +00:00
parent 5a56d4f39e
commit c84fc86e6c
42 changed files with 307 additions and 3137 deletions
-90
View File
@@ -1,90 +0,0 @@
#define NO_GLOBAL_ARENA_ALLOCATOR
struct MemoryArena {
u8* data;
u64 size = 0;
u64 capacity = 0;
String name = "unnamed";
MemoryArena(u64 cap, String _name = "unnamed")
{
name = _name;
capacity = cap;
printf("(i) memory arena '%s' created with capacity of %llu bytes\n", name.c_str(), capacity);
data = (u8*)malloc(cap);
}
~MemoryArena()
{
free(data);
}
void clear()
{
#ifdef DEBUG_MEMORY
printf("(i) memory arena '%s' cleared after high mark of %llu bytes\n", name.c_str(), size);
#endif
size = 0;
}
void* get(u64 size_needed)
{
u64 size_aligned = 8 + (8 * ((size_needed) / 8));
u8* result = data + size;
if(size_aligned + size >= capacity)
{
printf("(!) memory arena '%s' capacity (%llu) exceeded %llu/%llu + %llu >= %llu\n",
name.c_str(), capacity, size_needed, size_aligned, size, capacity);
return(0);
}
size += size_aligned;
#ifdef DEBUG_MEMORY_DETAILED
printf("(i) memory arena '%s' [+%llu]:%p alloc %llu/%llu bytes\n", name.c_str(), size, result, size_needed, size_aligned);
#endif
return(result);
}
};
MemoryArena* current_memory_arena = 0;
void switch_to_system_alloc()
{
#ifdef GLOBAL_ARENA_ALLOCATOR
current_memory_arena = 0;
#endif
}
void switch_to_arena(MemoryArena* a)
{
#ifdef GLOBAL_ARENA_ALLOCATOR
current_memory_arena = a;
#endif
}
#ifdef GLOBAL_ARENA_ALLOCATOR
void * operator new(decltype(sizeof(0)) n) noexcept(false)
{
if(current_memory_arena)
{
return(current_memory_arena->get(n));
}
else
{
return(malloc(n));
}
}
void operator delete(void * p) throw()
{
if(current_memory_arena)
{
}
else
{
free(p);
}
}
#endif
+85 -7
View File
@@ -1,4 +1,5 @@
#ifdef __UCE_WASM_CORE__
#include <cmath>
#include "types.h"
#include "functionlib.h"
#include "sys.h"
@@ -16,6 +17,10 @@ int uce_host_file_exists(const char* path, size_t path_len, const char* current,
size_t uce_host_file_read(const char* path, size_t path_len, const char* current, size_t current_len, char* buf, size_t cap);
int uce_host_file_write(const char* path, size_t path_len, const char* current, size_t current_len, const char* content, size_t content_len, int append);
void uce_host_file_unlink(const char* path, size_t path_len, const char* current, size_t current_len);
int uce_host_task_spawn(const char* key, size_t key_len, uint64_t callback_id, double interval, uint64_t timeout, int repeat);
int uce_host_task_pid(const char* key, size_t key_len);
int uce_host_task_kill(int pid, int sig);
unsigned int uce_host_sleep_us(uint64_t usec);
}
static String wasm_current_unit_file()
@@ -82,9 +87,55 @@ f64 config_f64(String key, f64 fallback) { return(context ? config_map_f64(conte
bool config_bool(String key, bool fallback) { return(context ? config_map_bool(context->server->config, key, fallback) : fallback); }
f64 time_precise() { return(uce_host_time_precise()); }
u64 time() { return(uce_host_time()); }
String time_format_local(String format, u64 timestamp) { (void)format; return(std::to_string(timestamp ? timestamp : time())); }
String time_format_utc(String format, u64 timestamp) { (void)format; return(std::to_string(timestamp ? timestamp : time())); }
String time_format_relative(u64 timestamp, String format_very_recent, u64 medium_recency_seconds, String format_medium_recent, u64 not_recent_seconds, String format_not_recent) { (void)format_very_recent; (void)medium_recency_seconds; (void)format_medium_recent; (void)not_recent_seconds; (void)format_not_recent; return(std::to_string(timestamp)); }
// The native build shells out to `date`; the wasm core has no shell, so it
// formats with wasi-libc strftime (no TZ data → local == UTC, acceptable).
static String wasm_time_strftime(String format, u64 timestamp, bool utc)
{
if(timestamp == 0)
timestamp = time();
time_t t = (time_t)timestamp;
struct tm tmv;
if(utc)
gmtime_r(&t, &tmv);
else
localtime_r(&t, &tmv);
char buffer[512];
size_t n = strftime(buffer, sizeof(buffer), format.c_str(), &tmv);
return(String(buffer, n));
}
String time_format_local(String format, u64 timestamp) { return(wasm_time_strftime(format, timestamp, false)); }
String time_format_utc(String format, u64 timestamp)
{
if(format == "RFC1123")
format = "%a, %d %b %Y %T GMT";
return(wasm_time_strftime(format, timestamp, true));
}
static String wasm_time_expand_delta(String format, u64 timestamp, u64 now_timestamp)
{
u64 delta_seconds = now_timestamp > timestamp ? now_timestamp - timestamp : 0;
format = replace(format, "%deltaY", std::to_string(delta_seconds / (60 * 60 * 24 * 365)));
format = replace(format, "%deltam", std::to_string(delta_seconds / (60 * 60 * 24 * 30)));
format = replace(format, "%deltad", std::to_string(delta_seconds / (60 * 60 * 24)));
format = replace(format, "%deltaH", std::to_string(delta_seconds / (60 * 60)));
format = replace(format, "%deltaM", std::to_string(delta_seconds / 60));
format = replace(format, "%deltaS", std::to_string(delta_seconds));
return(format);
}
String time_format_relative(u64 timestamp, String format_very_recent, u64 medium_recency_seconds, String format_medium_recent, u64 not_recent_seconds, String format_not_recent)
{
u64 now_timestamp = time();
u64 delta_seconds = now_timestamp > timestamp ? now_timestamp - timestamp : 0;
format_very_recent = first(format_very_recent, "just now");
medium_recency_seconds = medium_recency_seconds > 0 ? medium_recency_seconds : 90;
format_medium_recent = first(format_medium_recent, "%deltaM minutes ago");
not_recent_seconds = not_recent_seconds > 0 ? not_recent_seconds : 90 * 60;
format_not_recent = first(format_not_recent, "%deltaH hours ago");
if(delta_seconds < medium_recency_seconds)
return(wasm_time_expand_delta(format_very_recent, timestamp, now_timestamp));
if(delta_seconds < not_recent_seconds)
return(wasm_time_expand_delta(format_medium_recent, timestamp, now_timestamp));
return(wasm_time_expand_delta(format_not_recent, timestamp, now_timestamp));
}
u64 time_parse(String time_String) { char* end = 0; unsigned long long v = strtoull(time_String.c_str(), &end, 10); return(end && *end == 0 ? (u64)v : 0); }
u64 socket_connect(String host, short port) { (void)host; (void)port; return(0); }
void socket_close(u64 sockfd) { (void)sockfd; }
@@ -112,11 +163,38 @@ bool memcache_delete(u64 connection, String key) { (void)connection; (void)key;
String memcache_get(u64 connection, String key, String default_value) { (void)connection; (void)key; return(default_value); }
StringMap memcache_get_multiple(u64 connection, StringList keys) { (void)connection; (void)keys; return(StringMap()); }
void on_segfault(int sig) { (void)sig; }
int task_kill(pid_t pid, int sig) { (void)pid; (void)sig; return(-1); }
static u64 wasm_next_task_callback_id = 1;
static std::map<u64, std::function<void()>> wasm_task_callbacks;
extern "C" int uce_wasm_task_run(uint64_t callback_id)
{
auto it = wasm_task_callbacks.find(callback_id);
if(it == wasm_task_callbacks.end())
return(1);
it->second();
return(0);
}
int task_kill(pid_t pid, int sig) { return(uce_host_task_kill(pid, sig)); }
String runtime_safe_key(String key, String label) { (void)label; return(key); }
pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout) { (void)key; (void)exec_after_spawn; (void)timeout; return(0); }
pid_t task_repeat(String key, f64 interval, std::function<void()> exec_after_spawn, u64 timeout) { (void)key; (void)interval; (void)exec_after_spawn; (void)timeout; return(0); }
pid_t task_pid(String key) { (void)key; return(0); }
pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
{
u64 id = wasm_next_task_callback_id++;
wasm_task_callbacks[id] = exec_after_spawn;
return((pid_t)uce_host_task_spawn(key.data(), key.size(), id, 0.0, timeout, 0));
}
pid_t task_repeat(String key, f64 interval, std::function<void()> exec_after_spawn, u64 timeout)
{
if(!(interval > 0) || !std::isfinite(interval))
return(0);
u64 id = wasm_next_task_callback_id++;
wasm_task_callbacks[id] = exec_after_spawn;
return((pid_t)uce_host_task_spawn(key.data(), key.size(), id, interval, timeout, 1));
}
pid_t task_pid(String key) { return((pid_t)uce_host_task_pid(key.data(), key.size())); }
extern "C" unsigned int sleep(unsigned int seconds) { return(uce_host_sleep_us((uint64_t)seconds * 1000000ull)); }
extern "C" int usleep(unsigned int usec) { uce_host_sleep_us(usec); return(0); }
pid_t server_start_http(String key, String socket_fn_or_port, String call_uce_filename, String call_function) { (void)key; (void)socket_fn_or_port; (void)call_uce_filename; (void)call_function; return(0); }
bool server_stop(String key) { (void)key; return(false); }
StringMap default_config()
+6 -5
View File
@@ -117,13 +117,14 @@ static bool wasm_backend_native_fallback_uncached(Request* context, const String
// - regex_* (host PCRE2 hostcall), xml_*/yaml_*/markdown_* (compiled in),
// - unit_render()/component() (host resolver).
// What remains is genuinely host-owned / native-only for now:
// - zip, sqlite, background tasks, filesystem writes, sleeps;
// - zip, sockets/custom servers, memcache, mysql;
// - unit_call + compiler/unit introspection (need the native toolchain).
// Background tasks and sleep/usleep are host-owned but now have membrane
// hostcalls, so they intentionally do not fallback here.
StringList native_only_tokens = {
"zip_", "task(", "task_repeat(",
"task_pid(", "task_kill(", "unit_call(",
"unit_compile(", "unit_info(", "units_list(", "compiler_load_shared_unit(",
"usleep(", "sleep("
"zip_", "socket_", "server_start_http(", "server_stop(",
"memcache_", "mysql_", "unit_call(",
"unit_compile(", "unit_info(", "units_list(", "compiler_load_shared_unit("
};
for(auto& token : native_only_tokens)
if(source.find(token) != String::npos)
+4
View File
@@ -3,6 +3,10 @@ uce_host_time_precise
uce_host_env
uce_host_log
uce_host_random
uce_host_task_spawn
uce_host_task_pid
uce_host_task_kill
uce_host_sleep_us
uce_host_component_resolve
uce_host_file_exists
uce_host_file_read
+139 -36
View File
@@ -27,6 +27,7 @@
#include <wasmtime.hh>
#include <chrono>
#include <cmath>
#include <cstring>
#include <ctime>
#include <fstream>
@@ -352,6 +353,27 @@ public:
}
#endif
// The guest calls a sized hostcall twice (buf=0 to learn the length, then
// to fetch). For side-effecting ops (sqlite) re-executing on the fetch is
// wrong, so the result is staged on the first call (keyed on the exact
// input bytes) and replayed on the second without re-running the op.
String staged_hostcall_input;
String staged_hostcall_result;
bool hostcall_staged(const String& input, String& out)
{
if(!staged_hostcall_input.empty() && input == staged_hostcall_input)
{
out = staged_hostcall_result;
return(true);
}
return(false);
}
void hostcall_stage(const String& input, const String& out)
{
staged_hostcall_input = input;
staged_hostcall_result = out;
}
// resolve-kind values shared with the guest core (src/wasm/core.cpp)
// must match WasmResolveKind in src/wasm/core.cpp
enum ResolveKind { RESOLVE_COMPONENT = 0, RESOLVE_RENDER = 1, RESOLVE_EXISTS = 2, RESOLVE_ONCE = 3 };
@@ -1054,6 +1076,17 @@ private:
return((int32_t)slot);
}
String run_task_callback(u64 callback_id)
{
auto runner = core_func("uce_wasm_task_run");
if(!runner)
return("core does not export uce_wasm_task_run");
auto result = runner->call(ctx(), { wasmtime::Val((int64_t)callback_id) });
if(!result)
return(trap_text(result.err()));
return("");
}
// ---- 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)
@@ -1168,52 +1201,58 @@ private:
// handle table (handle = 1-based index).
String encoded;
self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
DValue request, response, decode_err_unused;
String decode_error;
if(ucb_decode(encoded, request, &decode_error))
String out;
// run the op once across the length-query + fetch pair
if(!self->hostcall_staged(encoded, out))
{
String op = request["op"].to_string();
if(op == "connect")
DValue request, response;
String decode_error;
if(ucb_decode(encoded, request, &decode_error))
{
SQLite* db = new SQLite();
db->connect(request["path"].to_string());
u64 handle = 0;
if(db->connection)
String op = request["op"].to_string();
if(op == "connect")
{
self->sqlite_handles.push_back(db);
handle = self->sqlite_handles.size();
}
response["handle"] = (f64)handle;
response["error_code"] = (f64)db->error_code;
response["statement_info"] = db->error();
if(handle == 0)
delete db;
}
else
{
u64 handle = request["handle"].to_u64();
SQLite* db = (handle >= 1 && handle <= self->sqlite_handles.size())
? self->sqlite_handles[(size_t)handle - 1] : 0;
if(op == "query" && db)
{
StringMap params;
DValue* p = request.key("params");
if(p)
p->each([&](const DValue& value, String key) { params[key] = value.to_string(); });
response["result"] = db->query(request["query"].to_string(), params);
response["insert_id"] = (f64)db->insert_id;
response["affected"] = (f64)db->affected_rows;
SQLite* db = new SQLite();
db->connect(request["path"].to_string());
u64 handle = 0;
if(db->connection)
{
self->sqlite_handles.push_back(db);
handle = self->sqlite_handles.size();
}
response["handle"] = (f64)handle;
response["error_code"] = (f64)db->error_code;
response["statement_info"] = db->error();
if(handle == 0)
delete db;
}
else if(op == "disconnect" && db)
else
{
delete db;
self->sqlite_handles[(size_t)handle - 1] = 0;
u64 handle = request["handle"].to_u64();
SQLite* db = (handle >= 1 && handle <= self->sqlite_handles.size())
? self->sqlite_handles[(size_t)handle - 1] : 0;
if(op == "query" && db)
{
StringMap params;
DValue* p = request.key("params");
if(p)
p->each([&](const DValue& value, String key) { params[key] = value.to_string(); });
response["result"] = db->query(request["query"].to_string(), params);
response["insert_id"] = (f64)db->insert_id;
response["affected"] = (f64)db->affected_rows;
response["error_code"] = (f64)db->error_code;
response["statement_info"] = db->error();
}
else if(op == "disconnect" && db)
{
delete db;
self->sqlite_handles[(size_t)handle - 1] = 0;
}
}
}
out = ucb_encode(response);
self->hostcall_stage(encoded, out);
}
String out = ucb_encode(response);
u32 cap = (u32)args[3].i32();
int32_t buf = args[2].i32();
if(buf != 0 && cap >= out.size())
@@ -1232,6 +1271,70 @@ private:
::unlink(resolved.c_str());
return(std::monostate());
}));
if(mod == "env" && name == "uce_host_task_spawn")
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
String key;
self->hostcall_read(args[0].i32(), args[1].i32(), key);
u64 callback_id = (u64)args[2].i64();
f64 interval = args[3].f64();
u64 timeout = (u64)args[4].i64();
bool repeat = args[5].i32() != 0;
auto run_callback = [self, callback_id]() {
String error = self->run_task_callback(callback_id);
if(error != "")
fprintf(stderr, "[wasm task] callback failed: %s\n", error.c_str());
};
pid_t pid = 0;
try
{
if(!repeat || (interval > 0 && std::isfinite(interval)))
pid = repeat
? ::task_repeat(key, interval, run_callback, timeout)
: ::task(key, run_callback, timeout);
}
catch(const std::exception& e)
{
fprintf(stderr, "[wasm task] spawn failed for key '%s': %s\n", key.c_str(), e.what());
}
catch(...)
{
fprintf(stderr, "[wasm task] spawn failed for key '%s'\n", key.c_str());
}
results[0] = Val((int32_t)pid);
return(std::monostate());
}));
if(mod == "env" && name == "uce_host_task_pid")
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
String key;
self->hostcall_read(args[0].i32(), args[1].i32(), key);
results[0] = Val((int32_t)::task_pid(key));
return(std::monostate());
}));
if(mod == "env" && name == "uce_host_task_kill")
return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
results[0] = Val((int32_t)::task_kill((pid_t)args[0].i32(), args[1].i32()));
return(std::monostate());
}));
if(mod == "env" && name == "uce_host_sleep_us")
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
u64 usec = (u64)args[0].i64();
while(usec >= 1000000ull)
{
unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull));
if(remaining != 0)
{
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
results[0] = Val((int32_t)remaining);
return(std::monostate());
}
usec %= 1000000ull;
}
if(usec > 0)
::usleep((useconds_t)usec);
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
results[0] = Val((int32_t)0);
return(std::monostate());
}));
if(mod == "env" && name == "uce_host_regex")
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
// {op,pattern,subject,flags,replacement} in (UCEB1) → result out.