W6
This commit is contained in:
@@ -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
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user