This commit is contained in:
2026-02-23 18:08:13 -06:00
parent a34566a0c1
commit 4da15d2a3e
16 changed files with 408 additions and 592 deletions

View File

@@ -1,5 +1,5 @@
#include "cell.h" #include "cell.h"
#include "quickjs-internal.h" #include "pit_internal.h"
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0]))) JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0]))) JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0])))

View File

@@ -241,7 +241,7 @@ source/
**`cell_runtime.c`** is the single file that defines the native code contract. It should: **`cell_runtime.c`** is the single file that defines the native code contract. It should:
1. Include `quickjs-internal.h` for access to value representation and heap types 1. Include `pit_internal.h` for access to value representation and heap types
2. Export all `cell_rt_*` functions with C linkage (no `static`) 2. Export all `cell_rt_*` functions with C linkage (no `static`)
3. Keep each function thin — delegate to existing `JS_*` functions where possible 3. Keep each function thin — delegate to existing `JS_*` functions where possible
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved 4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved

View File

@@ -1,5 +1,5 @@
#define NOTA_IMPLEMENTATION #define NOTA_IMPLEMENTATION
#include "quickjs-internal.h" #include "pit_internal.h"
#include "cell.h" #include "cell.h"
static int nota_get_arr_len (JSContext *ctx, JSValue arr) { static int nota_get_arr_len (JSContext *ctx, JSValue arr) {

View File

@@ -1,5 +1,5 @@
#include "cell.h" #include "cell.h"
#include "cell_internal.h" #include "pit_internal.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@@ -308,12 +308,9 @@ JSC_SCALL(os_system,
setting pause_flag = 2. Bump turn_gen so stale timer events are setting pause_flag = 2. Bump turn_gen so stale timer events are
ignored, and clear the pause flag so the VM doesn't raise ignored, and clear the pause flag so the VM doesn't raise
"interrupted" on the next backward branch. */ "interrupted" on the next backward branch. */
cell_rt *crt = JS_GetContextOpaque(js); atomic_fetch_add_explicit(&js->turn_gen, 1, memory_order_relaxed);
if (crt) { JS_SetPauseFlag(js, 0);
atomic_fetch_add_explicit(&crt->turn_gen, 1, memory_order_relaxed); js->turn_start_ns = cell_ns();
JS_SetPauseFlag(js, 0);
crt->turn_start_ns = cell_ns();
}
ret = number2js(js, err); ret = number2js(js, err);
) )

View File

@@ -1,5 +1,5 @@
#define WOTA_IMPLEMENTATION #define WOTA_IMPLEMENTATION
#include "quickjs-internal.h" #include "pit_internal.h"
#include "cell.h" #include "cell.h"
typedef struct ObjectRef { typedef struct ObjectRef {

View File

@@ -8,8 +8,7 @@
#include "stb_ds.h" #include "stb_ds.h"
#include "cell.h" #include "cell.h"
#include "quickjs-internal.h" #include "pit_internal.h"
#include "cell_internal.h"
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode" #define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
#define ENGINE_SRC "internal/engine.cm" #define ENGINE_SRC "internal/engine.cm"
@@ -27,13 +26,13 @@
int run_c_test_suite(JSContext *ctx); int run_c_test_suite(JSContext *ctx);
static int run_test_suite(size_t heap_size); static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL; static JSContext *root_ctx = NULL;
static char *shop_path = NULL; static char *shop_path = NULL;
volatile JSContext *g_crash_ctx = NULL; volatile JSContext *g_crash_ctx = NULL;
static char *core_path = NULL; static char *core_path = NULL;
static int native_mode = 0; static int native_mode = 0;
static int warn_mode = 1; static int warn_mode = 1;
static JSRuntime *g_runtime = NULL; JSRuntime *g_runtime = NULL;
// Compute blake2b hash of data and return hex string (caller must free) // Compute blake2b hash of data and return hex string (caller must free)
static char *compute_blake2_hex(const char *data, size_t size) { static char *compute_blake2_hex(const char *data, size_t size) {
@@ -303,44 +302,28 @@ const char* cell_get_core_path(void) {
return core_path; return core_path;
} }
void actor_disrupt(cell_rt *crt) void actor_disrupt(JSContext *ctx)
{ {
crt->disrupt = 1; ctx->disrupt = 1;
JS_SetPauseFlag(crt->context, 2); JS_SetPauseFlag(ctx, 2);
if (crt->state != ACTOR_RUNNING) if (ctx->state != ACTOR_RUNNING)
actor_free(crt); actor_free(ctx);
} }
JSValue js_core_internal_os_use(JSContext *js); JSValue js_core_internal_os_use(JSContext *js);
JSValue js_core_json_use(JSContext *js); JSValue js_core_json_use(JSContext *js);
void script_startup(cell_rt *prt) void script_startup(JSContext *js)
{ {
if (!g_runtime) { if (!g_runtime) {
g_runtime = JS_NewRuntime(); g_runtime = JS_NewRuntime();
} }
JSContext *js = JS_NewContext(g_runtime);
JS_SetContextOpaque(js, prt);
JS_SetGCScanExternal(js, actor_gc_scan); JS_SetGCScanExternal(js, actor_gc_scan);
prt->context = js;
/* Set per-actor heap memory limit */ /* Set per-actor heap memory limit */
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT); JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
/* Register all GCRef fields so the Cheney GC can relocate them. */
JS_AddGCRef(js, &prt->idx_buffer_ref);
JS_AddGCRef(js, &prt->on_exception_ref);
JS_AddGCRef(js, &prt->message_handle_ref);
JS_AddGCRef(js, &prt->unneeded_ref);
JS_AddGCRef(js, &prt->actor_sym_ref);
prt->idx_buffer_ref.val = JS_NULL;
prt->on_exception_ref.val = JS_NULL;
prt->message_handle_ref.val = JS_NULL;
prt->unneeded_ref.val = JS_NULL;
prt->actor_sym_ref.val = JS_NULL;
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_core_blob_use(js)); JS_FreeValue(js, js_core_blob_use(js));
// Try engine fast-path: load engine.cm from source-hash cache // Try engine fast-path: load engine.cm from source-hash cache
@@ -381,11 +364,11 @@ void script_startup(cell_rt *prt)
JSValue boot_env = JS_Stone(js, boot_env_ref.val); JSValue boot_env = JS_Stone(js, boot_env_ref.val);
JS_DeleteGCRef(js, &boot_env_ref); JS_DeleteGCRef(js, &boot_env_ref);
crt->state = ACTOR_RUNNING; js->state = ACTOR_RUNNING;
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env); JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
free(boot_bin); free(boot_bin);
uncaught_exception(js, bv); uncaught_exception(js, bv);
crt->state = ACTOR_IDLE; js->state = ACTOR_IDLE;
// Retry engine from cache // Retry engine from cache
bin_data = try_engine_cache(&bin_size); bin_data = try_engine_cache(&bin_size);
@@ -405,20 +388,20 @@ void script_startup(cell_rt *prt)
tmp = js_core_json_use(js); tmp = js_core_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp); JS_SetPropertyStr(js, env_ref.val, "json", tmp);
crt->actor_sym_ref.val = JS_NewObject(js); js->actor_sym_ref.val = JS_NewObject(js);
JS_CellStone(js, crt->actor_sym_ref.val); JS_CellStone(js, js->actor_sym_ref.val);
JS_SetActorSym(js, JS_DupValue(js, crt->actor_sym_ref.val)); JS_SetActorSym(js, JS_DupValue(js, js->actor_sym_ref.val));
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, js->actor_sym_ref.val));
// Always set init (even if null) // Always set init (even if null)
if (crt->init_wota) { if (js->init_wota) {
JSGCRef init_ref; JSGCRef init_ref;
JS_PushGCRef(js, &init_ref); JS_PushGCRef(js, &init_ref);
init_ref.val = wota2value(js, crt->init_wota); init_ref.val = wota2value(js, js->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val); JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
JS_PopGCRef(js, &init_ref); JS_PopGCRef(js, &init_ref);
free(crt->init_wota); free(js->init_wota);
crt->init_wota = NULL; js->init_wota = NULL;
} else { } else {
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
} }
@@ -438,12 +421,12 @@ void script_startup(cell_rt *prt)
JS_DeleteGCRef(js, &env_ref); JS_DeleteGCRef(js, &env_ref);
// Run engine from binary // Run engine from binary
crt->state = ACTOR_RUNNING; js->state = ACTOR_RUNNING;
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env); JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data); free(bin_data);
uncaught_exception(js, v); uncaught_exception(js, v);
crt->state = ACTOR_IDLE; js->state = ACTOR_IDLE;
set_actor_state(crt); set_actor_state(js);
} }
static void signal_handler(int sig) static void signal_handler(int sig)
@@ -624,35 +607,23 @@ int cell_init(int argc, char **argv)
return 1; return 1;
} }
/* Create a cell_rt for the CLI context so JS-C bridge functions work */ /* Set up mutexes on the CLI context */
cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1); ctx->mutex = malloc(sizeof(pthread_mutex_t));
cli_rt->mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutexattr_t mattr; pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr); pthread_mutexattr_init(&mattr);
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(cli_rt->mutex, &mattr); pthread_mutex_init(ctx->mutex, &mattr);
cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t)); ctx->msg_mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(cli_rt->msg_mutex, &mattr); pthread_mutex_init(ctx->msg_mutex, &mattr);
pthread_mutexattr_destroy(&mattr); pthread_mutexattr_destroy(&mattr);
cli_rt->context = ctx;
JS_SetContextOpaque(ctx, cli_rt);
JS_SetGCScanExternal(ctx, actor_gc_scan); JS_SetGCScanExternal(ctx, actor_gc_scan);
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref); ctx->actor_sym_ref.val = JS_NewObject(ctx);
JS_AddGCRef(ctx, &cli_rt->on_exception_ref); JS_CellStone(ctx, ctx->actor_sym_ref.val);
JS_AddGCRef(ctx, &cli_rt->message_handle_ref); JS_SetActorSym(ctx, JS_DupValue(ctx, ctx->actor_sym_ref.val));
JS_AddGCRef(ctx, &cli_rt->unneeded_ref);
JS_AddGCRef(ctx, &cli_rt->actor_sym_ref);
cli_rt->idx_buffer_ref.val = JS_NULL;
cli_rt->on_exception_ref.val = JS_NULL;
cli_rt->message_handle_ref.val = JS_NULL;
cli_rt->unneeded_ref.val = JS_NULL;
cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
JS_CellStone(ctx, cli_rt->actor_sym_ref.val);
JS_SetActorSym(ctx, JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
root_cell = cli_rt; root_ctx = ctx;
JS_FreeValue(ctx, js_core_blob_use(ctx)); JS_FreeValue(ctx, js_core_blob_use(ctx));
@@ -699,7 +670,7 @@ int cell_init(int argc, char **argv)
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp); JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL; btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp); JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
btmp = js_core_json_use(ctx); btmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp); JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
if (native_mode) if (native_mode)
@@ -763,7 +734,7 @@ int cell_init(int argc, char **argv)
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp); JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL; tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp); JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
tmp = js_core_json_use(ctx); tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp); JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
if (native_mode || !warn_mode || eval_script) { if (native_mode || !warn_mode || eval_script) {
@@ -826,18 +797,11 @@ check_actors:
} }
/* No actors spawned — clean up CLI context */ /* No actors spawned — clean up CLI context */
JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref); pthread_mutex_destroy(ctx->mutex);
JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref); free(ctx->mutex);
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref); pthread_mutex_destroy(ctx->msg_mutex);
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref); free(ctx->msg_mutex);
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref); root_ctx = NULL;
pthread_mutex_destroy(cli_rt->mutex);
free(cli_rt->mutex);
pthread_mutex_destroy(cli_rt->msg_mutex);
free(cli_rt->msg_mutex);
free(cli_rt);
root_cell = NULL;
JS_FreeContext(ctx); JS_FreeContext(ctx);
JS_FreeRuntime(g_runtime); JS_FreeRuntime(g_runtime);
@@ -890,18 +854,7 @@ cell_hook cell_trace_gethook(void)
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook) void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook)
{ {
struct cell_rt *rt = JS_GetContextOpaque(ctx); ctx->actor_trace_hook = hook;
if (rt) rt->trace_hook = hook;
}
const char *cell_rt_name(struct cell_rt *rt)
{
return rt->name;
}
JSContext *cell_rt_context(struct cell_rt *rt)
{
return rt->context;
} }
int uncaught_exception(JSContext *js, JSValue v) int uncaught_exception(JSContext *js, JSValue v)
@@ -914,13 +867,12 @@ int uncaught_exception(JSContext *js, JSValue v)
by JS_ThrowError2 / print_backtrace. Just clear the flag. */ by JS_ThrowError2 / print_backtrace. Just clear the flag. */
if (has_exc) if (has_exc)
JS_GetException(js); JS_GetException(js);
cell_rt *crt = JS_GetContextOpaque(js); if (!JS_IsNull(js->on_exception_ref.val)) {
if (crt && !JS_IsNull(crt->on_exception_ref.val)) {
/* Disable interruption so actor_die can send messages /* Disable interruption so actor_die can send messages
without being re-interrupted. */ without being re-interrupted. */
JS_SetPauseFlag(js, 0); JS_SetPauseFlag(js, 0);
JSValue err = JS_NewString(js, "interrupted"); JSValue err = JS_NewString(js, "interrupted");
JS_Call(js, crt->on_exception_ref.val, JS_NULL, 1, &err); JS_Call(js, js->on_exception_ref.val, JS_NULL, 1, &err);
/* Clear any secondary exception from the callback. */ /* Clear any secondary exception from the callback. */
if (JS_HasException(js)) if (JS_HasException(js))
JS_GetException(js); JS_GetException(js);

View File

@@ -1,28 +1,3 @@
/*
* cell.h — Consolidated public C API for ƿit modules
*
* QuickJS Javascript Engine
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef CELL_H #ifndef CELL_H
#define CELL_H #define CELL_H
@@ -282,7 +257,7 @@ static inline JSValue MIST_TryNewImmediateASCII (const char *str, size_t len) {
return v; return v;
} }
/* Heap object type checks (non-inline — see mist_is_* in quickjs-internal.h /* Heap object type checks (non-inline — see mist_is_* in pit_internal.h
for inline versions used by the VM dispatch loop) */ for inline versions used by the VM dispatch loop) */
JS_BOOL JS_IsArray(JSValue v); JS_BOOL JS_IsArray(JSValue v);
JS_BOOL JS_IsRecord(JSValue v); JS_BOOL JS_IsRecord(JSValue v);
@@ -881,14 +856,11 @@ void js_debug_sethook (JSContext *ctx, js_hook, int type, void *user);
#define CELL_HOOK_ENTER 1 #define CELL_HOOK_ENTER 1
#define CELL_HOOK_EXIT 2 #define CELL_HOOK_EXIT 2
struct cell_rt; typedef void (*cell_hook)(JSContext *ctx, int type);
typedef void (*cell_hook)(struct cell_rt *rt, int type);
void cell_trace_sethook(cell_hook); void cell_trace_sethook(cell_hook);
cell_hook cell_trace_gethook(void); cell_hook cell_trace_gethook(void);
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook); void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook);
size_t cell_ctx_heap_used(JSContext *ctx); size_t cell_ctx_heap_used(JSContext *ctx);
const char *cell_rt_name(struct cell_rt *rt);
JSContext *cell_rt_context(struct cell_rt *rt);
void js_debug_gethook(JSContext *ctx, js_hook *hook, int *type, void **user); void js_debug_gethook(JSContext *ctx, js_hook *hook, int *type, void **user);
/* ============================================================ /* ============================================================

View File

@@ -1,118 +0,0 @@
#include <pthread.h>
#include <stdatomic.h>
/* Internal runtime accessors — not in public cell.h API */
void *JS_GetContextOpaque (JSContext *ctx);
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
void JS_SetPauseFlag(JSContext *ctx, int value);
JSValue JS_GetStack (JSContext *ctx);
/* Letter type for unified message queue */
typedef enum {
LETTER_BLOB, /* Blob message */
LETTER_CALLBACK /* JSValue callback function */
} letter_type;
typedef struct letter {
letter_type type;
union {
blob *blob_data; /* For LETTER_BLOB */
JSValue callback; /* For LETTER_CALLBACK */
};
} letter;
#define ACTOR_IDLE 0 // Actor not doing anything
#define ACTOR_READY 1 // Actor ready for a turn
#define ACTOR_RUNNING 2 // Actor taking a turn
#define ACTOR_EXHAUSTED 3 // Actor waiting for GC
#define ACTOR_RECLAIMING 4 // Actor running GC
#define ACTOR_SLOW 5 // Actor going slowly; deprioritize
#define ACTOR_REFRESHED 6 // GC finished, ready to resume
// #define ACTOR_TRACE
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000) // 10ms per turn
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000) // 60s for slow actors
#define ACTOR_SLOW_STRIKES_MAX 3 // consecutive slow turns -> kill
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024) // 16MB heap cap
typedef struct cell_rt {
JSContext *context;
/* JSValues on the GC heap — each paired with a JSGCRef so the
Cheney GC can relocate them during compaction. */
JSGCRef idx_buffer_ref;
JSGCRef on_exception_ref;
JSGCRef message_handle_ref;
JSGCRef unneeded_ref;
JSGCRef actor_sym_ref;
void *init_wota;
/* Protects JSContext usage */
pthread_mutex_t *mutex; /* for everything else */
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
char *id;
int idx_count;
/* The "mailbox" for incoming messages + a dedicated lock for it: */
letter *letters;
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
struct { uint32_t key; JSValue value; } *timers;
int state;
uint32_t ar; // timer for unneeded
double ar_secs; // time for unneeded
int disrupt;
int is_quiescent; // tracked by scheduler for quiescence detection
int main_thread_only;
int affinity;
uint64_t turn_start_ns; // cell_ns() when turn began
_Atomic uint32_t turn_gen; // incremented each turn start
int slow_strikes; // consecutive slow-completed turns
int vm_suspended; // 1 if VM is paused mid-turn
const char *name; // human friendly name
cell_hook trace_hook;
} cell_rt;
/* Set by actor_turn/CLI before entering the VM, cleared after.
Read by signal_handler to print JS stack on crash. */
extern volatile JSContext *g_crash_ctx;
cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor);
const char *send_message(const char *id, void *msg);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
void script_startup(cell_rt *rt);
int uncaught_exception(JSContext *js, JSValue v);
int actor_exists(const char *id);
void set_actor_state(cell_rt *actor);
void enqueue_actor_priority(cell_rt *actor);
void actor_clock(cell_rt *actor, JSValue fn);
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds);
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id);
void exit_handler(void);
void actor_loop();
void actor_initialize(void);
void actor_free(cell_rt *actor);
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
uint8_t *tb, uint8_t **tf, uint8_t *te);
int scheduler_actor_count(void);
void scheduler_enable_quiescence(void);
JSValue JS_ResumeRegisterVM(JSContext *ctx);
uint64_t cell_ns();
void cell_sleep(double seconds);
int randombytes(void *buf, size_t n);

View File

@@ -1,29 +1,4 @@
/* #include "pit_internal.h"
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "quickjs-internal.h"
#include <assert.h> #include <assert.h>
/* ============================================================ /* ============================================================

View File

@@ -1,30 +1,6 @@
#ifndef QUICKJS_INTERNAL_H #ifndef QUICKJS_INTERNAL_H
#define QUICKJS_INTERNAL_H #define QUICKJS_INTERNAL_H
/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
@@ -36,6 +12,9 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
#ifndef TARGET_PLAYDATE
#include <pthread.h>
#endif
#if defined(__APPLE__) #if defined(__APPLE__)
#include <malloc/malloc.h> #include <malloc/malloc.h>
#elif defined(__linux__) || defined(__GLIBC__) #elif defined(__linux__) || defined(__GLIBC__)
@@ -103,8 +82,6 @@ void JS_SetPoolSize (JSRuntime *rt, size_t initial, size_t cap);
JSContext *JS_NewContext (JSRuntime *rt); JSContext *JS_NewContext (JSRuntime *rt);
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size); JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
void JS_FreeContext (JSContext *s); void JS_FreeContext (JSContext *s);
void *JS_GetContextOpaque (JSContext *ctx);
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
typedef void (*JS_GCScanFn)(JSContext *ctx, typedef void (*JS_GCScanFn)(JSContext *ctx,
uint8_t *from_base, uint8_t *from_end, uint8_t *from_base, uint8_t *from_end,
@@ -864,6 +841,38 @@ typedef struct CCallRoot {
struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */ struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */
} CCallRoot; } CCallRoot;
/* ============================================================
Actor Types (merged from cell_internal.h)
============================================================ */
/* Letter type for unified message queue */
typedef enum {
LETTER_BLOB,
LETTER_CALLBACK
} letter_type;
typedef struct letter {
letter_type type;
union {
blob *blob_data;
JSValue callback;
};
} letter;
/* Actor state machine constants */
#define ACTOR_IDLE 0
#define ACTOR_READY 1
#define ACTOR_RUNNING 2
#define ACTOR_EXHAUSTED 3
#define ACTOR_RECLAIMING 4
#define ACTOR_SLOW 5
#define ACTOR_REFRESHED 6
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000)
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000)
#define ACTOR_SLOW_STRIKES_MAX 3
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024)
struct JSContext { struct JSContext {
JSRuntime *rt; JSRuntime *rt;
@@ -919,7 +928,6 @@ struct JSContext {
/* if NULL, RegExp compilation is not supported */ /* if NULL, RegExp compilation is not supported */
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags); JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
void *user_opaque;
/* GC callback to scan external C-side roots (actor letters, timers) */ /* GC callback to scan external C-side roots (actor letters, timers) */
void (*gc_scan_external)(JSContext *ctx, void (*gc_scan_external)(JSContext *ctx,
@@ -956,6 +964,44 @@ struct JSContext {
// todo: want this, but should be a simple increment/decrement counter while frames are pushed // todo: want this, but should be a simple increment/decrement counter while frames are pushed
size_t stack_depth; size_t stack_depth;
size_t stack_limit; size_t stack_limit;
/* === Actor fields (merged from cell_rt) === */
JSGCRef idx_buffer_ref;
JSGCRef on_exception_ref;
JSGCRef message_handle_ref;
JSGCRef unneeded_ref;
JSGCRef actor_sym_ref;
void *init_wota;
#ifndef TARGET_PLAYDATE
pthread_mutex_t *mutex;
pthread_mutex_t *msg_mutex;
#endif
char *id;
int idx_count;
letter *letters;
struct { uint32_t key; JSValue value; } *timers;
int state;
uint32_t ar;
double ar_secs;
int disrupt;
int is_quiescent;
int main_thread_only;
int affinity;
uint64_t turn_start_ns;
_Atomic uint32_t turn_gen;
int slow_strikes;
int vm_suspended;
const char *name;
cell_hook actor_trace_hook;
}; };
/* ============================================================ /* ============================================================
@@ -979,6 +1025,44 @@ static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_si
} }
/* ============================================================
Actor / Scheduler Declarations (merged from cell_internal.h)
============================================================ */
/* Set by actor_turn/CLI before entering the VM, cleared after.
Read by signal_handler to print JS stack on crash. */
extern volatile JSContext *g_crash_ctx;
JSContext *create_actor(void *wota);
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar);
void actor_disrupt(JSContext *actor);
const char *send_message(const char *id, void *msg);
void actor_unneeded(JSContext *actor, JSValue fn, double seconds);
void script_startup(JSContext *ctx);
int uncaught_exception(JSContext *js, JSValue v);
int actor_exists(const char *id);
void set_actor_state(JSContext *actor);
void enqueue_actor_priority(JSContext *actor);
void actor_clock(JSContext *actor, JSValue fn);
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds);
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id);
void exit_handler(void);
void actor_loop(void);
void actor_initialize(void);
void actor_free(JSContext *actor);
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
uint8_t *tb, uint8_t **tf, uint8_t *te);
int scheduler_actor_count(void);
void scheduler_enable_quiescence(void);
JSValue JS_ResumeRegisterVM(JSContext *ctx);
uint64_t cell_ns(void);
void cell_sleep(double seconds);
int randombytes(void *buf, size_t n);
/* ============================================================ /* ============================================================
Constant Text Pool Functions Constant Text Pool Functions
============================================================ */ ============================================================ */

View File

@@ -6,7 +6,7 @@
* string comparison, bitwise ops on floats, and boolean conversion. * string comparison, bitwise ops on floats, and boolean conversion.
*/ */
#include "quickjs-internal.h" #include "pit_internal.h"
#include <math.h> #include <math.h>
#include <pthread.h> #include <pthread.h>
#include <stdlib.h> #include <stdlib.h>

View File

@@ -1,13 +1,11 @@
#include "cell.h" #include "cell.h"
#include "cell_internal.h" #include "pit_internal.h"
#include "quickjs-internal.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
JSC_CCALL(os_createactor, JSC_CCALL(os_createactor,
cell_rt *rt = JS_GetContextOpaque(js); if (js->disrupt)
if (rt->disrupt)
return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting."); return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting.");
void *startup = value2wota(js, argv[0], JS_NULL, NULL); void *startup = value2wota(js, argv[0], JS_NULL, NULL);
@@ -57,14 +55,12 @@ JSC_CCALL(os_mailbox_push,
) )
JSC_CCALL(os_register_actor, JSC_CCALL(os_register_actor,
cell_rt *rt = JS_GetContextOpaque(js);
const char *id = JS_ToCString(js, argv[0]); const char *id = JS_ToCString(js, argv[0]);
double ar; double ar;
JS_ToFloat64(js, &ar, argv[3]); JS_ToFloat64(js, &ar, argv[3]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar); const char *err = register_actor(id, js, JS_ToBool(js, argv[2]), ar);
if (err) return JS_RaiseDisrupt(js, "Could not register actor: %s", err); if (err) return JS_RaiseDisrupt(js, "Could not register actor: %s", err);
rt->message_handle_ref.val = argv[1]; js->message_handle_ref.val = argv[1];
rt->context = js;
JS_FreeCString(js, id); JS_FreeCString(js, id);
) )
@@ -77,56 +73,49 @@ JSC_CCALL(os_mailbox_exist,
) )
JSC_CCALL(os_unneeded, JSC_CCALL(os_unneeded,
cell_rt *actor = JS_GetContextOpaque(js);
double secs; double secs;
JS_ToFloat64(js, &secs, argv[1]); JS_ToFloat64(js, &secs, argv[1]);
actor_unneeded(actor, argv[0], secs); actor_unneeded(js, argv[0], secs);
) )
JSC_CCALL(actor_disrupt, JSC_CCALL(actor_disrupt,
cell_rt *crt = JS_GetContextOpaque(js); actor_disrupt(js);
actor_disrupt(crt);
) )
JSC_SCALL(actor_setname, JSC_SCALL(actor_setname,
cell_rt *rt = JS_GetContextOpaque(js); js->name = strdup(str);
rt->name = strdup(str);
) )
JSC_CCALL(actor_on_exception, JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js); js->on_exception_ref.val = argv[0];
rt->on_exception_ref.val = argv[0];
) )
JSC_CCALL(actor_clock, JSC_CCALL(actor_clock,
if (!JS_IsFunction(argv[0])) if (!JS_IsFunction(argv[0]))
return JS_RaiseDisrupt(js, "Argument must be a function."); return JS_RaiseDisrupt(js, "Argument must be a function.");
cell_rt *actor = JS_GetContextOpaque(js); actor_clock(js, argv[0]);
actor_clock(actor, argv[0]);
) )
JSC_CCALL(actor_delay, JSC_CCALL(actor_delay,
if (!JS_IsFunction(argv[0])) if (!JS_IsFunction(argv[0]))
return JS_RaiseDisrupt(js, "Argument must be a function."); return JS_RaiseDisrupt(js, "Argument must be a function.");
cell_rt *actor = JS_GetContextOpaque(js);
double seconds; double seconds;
JS_ToFloat64(js, &seconds, argv[1]); JS_ToFloat64(js, &seconds, argv[1]);
if (seconds <= 0) { if (seconds <= 0) {
actor_clock(actor, argv[0]); actor_clock(js, argv[0]);
return JS_NULL; return JS_NULL;
} }
uint32_t id = actor_delay(actor, argv[0], seconds); uint32_t id = actor_delay(js, argv[0], seconds);
return JS_NewUint32(js, id); return JS_NewUint32(js, id);
) )
JSC_CCALL(actor_removetimer, JSC_CCALL(actor_removetimer,
cell_rt *actor = JS_GetContextOpaque(js);
uint32_t timer_id; uint32_t timer_id;
JS_ToUint32(js, &timer_id, argv[0]); JS_ToUint32(js, &timer_id, argv[0]);
(void)actor_remove_timer(actor, timer_id); (void)actor_remove_timer(js, timer_id);
) )
/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack]) /* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])

View File

@@ -1,30 +1,5 @@
/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#define BLOB_IMPLEMENTATION #define BLOB_IMPLEMENTATION
#include "quickjs-internal.h" #include "pit_internal.h"
#include <unistd.h> #include <unistd.h>
// #define DUMP_BUDDY // #define DUMP_BUDDY
@@ -87,7 +62,7 @@ static inline JS_BOOL JS_IsInteger (JSValue v) {
JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
/* === Public API wrappers (non-inline, for quickjs.h declarations) === /* === Public API wrappers (non-inline, for quickjs.h declarations) ===
These delegate to the static inline versions in quickjs-internal.h. */ These delegate to the static inline versions in pit_internal.h. */
JS_BOOL JS_IsStone(JSValue v) { return mist_is_stone(v); } JS_BOOL JS_IsStone(JSValue v) { return mist_is_stone(v); }
JS_BOOL JS_IsArray(JSValue v) { return mist_is_array(v); } JS_BOOL JS_IsArray(JSValue v) { return mist_is_array(v); }
@@ -1943,10 +1918,8 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
/* Check memory limit — kill actor if heap exceeds cap */ /* Check memory limit — kill actor if heap exceeds cap */
if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) { if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) {
#ifdef ACTOR_TRACE #ifdef ACTOR_TRACE
void *crt = ctx->user_opaque; fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
if (crt) ctx->current_block_size, ctx->heap_memory_limit);
fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
ctx->current_block_size, ctx->heap_memory_limit);
#endif #endif
return -1; return -1;
} }
@@ -2080,6 +2053,18 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
ctx->log_callback_js = JS_NULL; ctx->log_callback_js = JS_NULL;
ctx->log_callback = NULL; ctx->log_callback = NULL;
/* Register actor GCRef fields so the Cheney GC can relocate them. */
JS_AddGCRef(ctx, &ctx->idx_buffer_ref);
JS_AddGCRef(ctx, &ctx->on_exception_ref);
JS_AddGCRef(ctx, &ctx->message_handle_ref);
JS_AddGCRef(ctx, &ctx->unneeded_ref);
JS_AddGCRef(ctx, &ctx->actor_sym_ref);
ctx->idx_buffer_ref.val = JS_NULL;
ctx->on_exception_ref.val = JS_NULL;
ctx->message_handle_ref.val = JS_NULL;
ctx->unneeded_ref.val = JS_NULL;
ctx->actor_sym_ref.val = JS_NULL;
/* Initialize constant text pool (avoids overflow pages for common case) */ /* Initialize constant text pool (avoids overflow pages for common case) */
{ {
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */ size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
@@ -2156,11 +2141,7 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) {
return ctx; return ctx;
} }
void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; }
void JS_SetContextOpaque (JSContext *ctx, void *opaque) {
ctx->user_opaque = opaque;
}
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) { void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) {
ctx->gc_scan_external = fn; ctx->gc_scan_external = fn;
@@ -2190,6 +2171,11 @@ void JS_FreeContext (JSContext *ctx) {
cell_rt_free_native_state(ctx); cell_rt_free_native_state(ctx);
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref); JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
JS_DeleteGCRef(ctx, &ctx->idx_buffer_ref);
JS_DeleteGCRef(ctx, &ctx->on_exception_ref);
JS_DeleteGCRef(ctx, &ctx->message_handle_ref);
JS_DeleteGCRef(ctx, &ctx->unneeded_ref);
JS_DeleteGCRef(ctx, &ctx->actor_sym_ref);
for (i = 0; i < ctx->class_count; i++) { for (i = 0; i < ctx->class_count; i++) {
} }

View File

@@ -9,15 +9,14 @@
#include "stb_ds.h" #include "stb_ds.h"
#include "cell.h" #include "cell.h"
#include "quickjs-internal.h" #include "pit_internal.h"
#include "cell_internal.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#endif #endif
typedef struct actor_node { typedef struct actor_node {
cell_rt *actor; JSContext *actor;
struct actor_node *next; struct actor_node *next;
} actor_node; } actor_node;
@@ -30,13 +29,13 @@ typedef enum {
typedef struct { typedef struct {
uint64_t execute_at_ns; uint64_t execute_at_ns;
cell_rt *actor; JSContext *actor;
uint32_t timer_id; uint32_t timer_id;
timer_type type; timer_type type;
uint32_t turn_gen; /* generation at registration time */ uint32_t turn_gen; /* generation at registration time */
} timer_node; } timer_node;
static timer_node *timer_heap = NULL; static timer_node *timer_heap = NULL;
// Priority queue indices // Priority queue indices
#define PQ_READY 0 #define PQ_READY 0
@@ -92,7 +91,7 @@ static int has_any_work(actor_node *heads[]) {
} }
static pthread_mutex_t *actors_mutex; static pthread_mutex_t *actors_mutex;
static struct { char *key; cell_rt *value; } *actors = NULL; static struct { char *key; JSContext *value; } *actors = NULL;
#define lockless_shdel(NAME, KEY) pthread_mutex_lock(NAME##_mutex); shdel(NAME, KEY); pthread_mutex_unlock(NAME##_mutex); #define lockless_shdel(NAME, KEY) pthread_mutex_lock(NAME##_mutex); shdel(NAME, KEY); pthread_mutex_unlock(NAME##_mutex);
#define lockless_shlen(NAME) ({ \ #define lockless_shlen(NAME) ({ \
@@ -109,7 +108,7 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
}) })
#define lockless_shget(NAME, KEY) ({ \ #define lockless_shget(NAME, KEY) ({ \
pthread_mutex_lock(NAME##_mutex); \ pthread_mutex_lock(NAME##_mutex); \
cell_rt *_actor = shget(NAME, KEY); \ JSContext *_actor = shget(NAME, KEY); \
pthread_mutex_unlock(NAME##_mutex); \ pthread_mutex_unlock(NAME##_mutex); \
_actor; \ _actor; \
}) })
@@ -122,21 +121,21 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
}) })
// Forward declarations // Forward declarations
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval); uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval);
void actor_turn(cell_rt *actor); void actor_turn(JSContext *actor);
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) { void heap_push(uint64_t when, JSContext *actor, uint32_t timer_id, timer_type type) {
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 }; timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 };
if (type == TIMER_PAUSE || type == TIMER_KILL) if (type == TIMER_PAUSE || type == TIMER_KILL)
node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed); node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed);
arrput(timer_heap, node); arrput(timer_heap, node);
// Bubble up // Bubble up
int i = arrlen(timer_heap) - 1; int i = arrlen(timer_heap) - 1;
while (i > 0) { while (i > 0) {
int parent = (i - 1) / 2; int parent = (i - 1) / 2;
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break; if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
timer_node tmp = timer_heap[i]; timer_node tmp = timer_heap[i];
timer_heap[i] = timer_heap[parent]; timer_heap[i] = timer_heap[parent];
timer_heap[parent] = tmp; timer_heap[parent] = tmp;
@@ -147,10 +146,10 @@ void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type
// Helper: Heap Pop // Helper: Heap Pop
int heap_pop(timer_node *out) { int heap_pop(timer_node *out) {
if (arrlen(timer_heap) == 0) return 0; if (arrlen(timer_heap) == 0) return 0;
*out = timer_heap[0]; *out = timer_heap[0];
timer_node last = arrpop(timer_heap); timer_node last = arrpop(timer_heap);
if (arrlen(timer_heap) > 0) { if (arrlen(timer_heap) > 0) {
timer_heap[0] = last; timer_heap[0] = last;
// Bubble down // Bubble down
@@ -160,14 +159,14 @@ int heap_pop(timer_node *out) {
int left = 2 * i + 1; int left = 2 * i + 1;
int right = 2 * i + 2; int right = 2 * i + 2;
int smallest = i; int smallest = i;
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns) if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
smallest = left; smallest = left;
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns) if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
smallest = right; smallest = right;
if (smallest == i) break; if (smallest == i) break;
timer_node tmp = timer_heap[i]; timer_node tmp = timer_heap[i];
timer_heap[i] = timer_heap[smallest]; timer_heap[i] = timer_heap[smallest];
timer_heap[smallest] = tmp; timer_heap[smallest] = tmp;
@@ -180,7 +179,7 @@ int heap_pop(timer_node *out) {
void *timer_thread_func(void *arg) { void *timer_thread_func(void *arg) {
while (1) { while (1) {
pthread_mutex_lock(&engine.lock); pthread_mutex_lock(&engine.lock);
if (engine.shutting_down) { if (engine.shutting_down) {
pthread_mutex_unlock(&engine.lock); pthread_mutex_unlock(&engine.lock);
return NULL; return NULL;
@@ -203,10 +202,10 @@ void *timer_thread_func(void *arg) {
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed); uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
if (cur == t.turn_gen) { if (cur == t.turn_gen) {
if (t.type == TIMER_PAUSE) { if (t.type == TIMER_PAUSE) {
JS_SetPauseFlag(t.actor->context, 1); JS_SetPauseFlag(t.actor, 1);
} else { } else {
t.actor->disrupt = 1; t.actor->disrupt = 1;
JS_SetPauseFlag(t.actor->context, 2); JS_SetPauseFlag(t.actor, 2);
} }
} }
} else { } else {
@@ -216,26 +215,26 @@ void *timer_thread_func(void *arg) {
JSValue cb = t.actor->timers[idx].value; JSValue cb = t.actor->timers[idx].value;
hmdel(t.actor->timers, t.timer_id); hmdel(t.actor->timers, t.timer_id);
actor_clock(t.actor, cb); actor_clock(t.actor, cb);
JS_FreeValue(t.actor->context, cb); JS_FreeValue(t.actor, cb);
} }
pthread_mutex_unlock(t.actor->msg_mutex); pthread_mutex_unlock(t.actor->msg_mutex);
} }
continue; continue;
} else { } else {
// --- WAIT FOR DEADLINE --- // --- WAIT FOR DEADLINE ---
struct timespec ts; struct timespec ts;
uint64_t wait_ns = timer_heap[0].execute_at_ns - now; uint64_t wait_ns = timer_heap[0].execute_at_ns - now;
// Convert relative wait time to absolute CLOCK_REALTIME // Convert relative wait time to absolute CLOCK_REALTIME
struct timespec now_real; struct timespec now_real;
clock_gettime(CLOCK_REALTIME, &now_real); clock_gettime(CLOCK_REALTIME, &now_real);
uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL + uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL +
(uint64_t)now_real.tv_nsec + wait_ns; (uint64_t)now_real.tv_nsec + wait_ns;
ts.tv_sec = deadline_real_ns / 1000000000ULL; ts.tv_sec = deadline_real_ns / 1000000000ULL;
ts.tv_nsec = deadline_real_ns % 1000000000ULL; ts.tv_nsec = deadline_real_ns % 1000000000ULL;
pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts); pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts);
} }
} }
@@ -244,7 +243,7 @@ void *timer_thread_func(void *arg) {
return NULL; return NULL;
} }
void enqueue_actor_priority(cell_rt *actor) { void enqueue_actor_priority(JSContext *actor) {
actor_node *n = malloc(sizeof(actor_node)); actor_node *n = malloc(sizeof(actor_node));
n->actor = actor; n->actor = actor;
n->next = NULL; n->next = NULL;
@@ -301,10 +300,10 @@ void actor_initialize(void) {
engine.mq_head[i] = NULL; engine.mq_head[i] = NULL;
engine.mq_tail[i] = NULL; engine.mq_tail[i] = NULL;
} }
actors_mutex = malloc(sizeof(pthread_mutex_t)); actors_mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(actors_mutex, NULL); pthread_mutex_init(actors_mutex, NULL);
// Start Timer Thread // Start Timer Thread
pthread_create(&engine.timer_thread, NULL, timer_thread_func, NULL); pthread_create(&engine.timer_thread, NULL, timer_thread_func, NULL);
@@ -323,7 +322,7 @@ void actor_initialize(void) {
} }
} }
void actor_free(cell_rt *actor) void actor_free(JSContext *actor)
{ {
if (actor->is_quiescent) { if (actor->is_quiescent) {
actor->is_quiescent = 0; actor->is_quiescent = 0;
@@ -364,42 +363,36 @@ void actor_free(cell_rt *actor)
pthread_mutex_lock(actor->msg_mutex); pthread_mutex_lock(actor->msg_mutex);
pthread_mutex_lock(actor->mutex); pthread_mutex_lock(actor->mutex);
JSContext *js = actor->context;
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_DeleteGCRef(js, &actor->actor_sym_ref);
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value); JS_FreeValue(actor, actor->timers[i].value);
} }
hmfree(actor->timers); hmfree(actor->timers);
/* Free all letters in the queue */ /* Free all letters in the queue */
for (int i = 0; i < arrlen(actor->letters); i++) { for (int i = 0; i < arrlen(actor->letters); i++) {
if (actor->letters[i].type == LETTER_BLOB) { if (actor->letters[i].type == LETTER_BLOB) {
blob_destroy(actor->letters[i].blob_data); blob_destroy(actor->letters[i].blob_data);
} else if (actor->letters[i].type == LETTER_CALLBACK) { } else if (actor->letters[i].type == LETTER_CALLBACK) {
JS_FreeValue(js, actor->letters[i].callback); JS_FreeValue(actor, actor->letters[i].callback);
} }
} }
arrfree(actor->letters); arrfree(actor->letters);
JS_FreeContext(js);
free(actor->id); free(actor->id);
pthread_mutex_unlock(actor->mutex); pthread_mutex_t *m = actor->mutex;
pthread_mutex_destroy(actor->mutex); pthread_mutex_t *mm = actor->msg_mutex;
free(actor->mutex);
pthread_mutex_unlock(actor->msg_mutex); JS_FreeContext(actor);
pthread_mutex_destroy(actor->msg_mutex);
free(actor->msg_mutex); pthread_mutex_unlock(m);
pthread_mutex_destroy(m);
free(actor); free(m);
pthread_mutex_unlock(mm);
pthread_mutex_destroy(mm);
free(mm);
int actor_count = lockless_shlen(actors); int actor_count = lockless_shlen(actors);
if (actor_count == 0) { if (actor_count == 0) {
@@ -467,7 +460,7 @@ int actor_exists(const char *id)
return idx != -1; return idx != -1;
} }
void set_actor_state(cell_rt *actor) void set_actor_state(JSContext *actor)
{ {
if (actor->disrupt) { if (actor->disrupt) {
#ifdef SCHEDULER_DEBUG #ifdef SCHEDULER_DEBUG
@@ -548,7 +541,7 @@ void set_actor_state(cell_rt *actor)
pthread_mutex_unlock(actor->msg_mutex); pthread_mutex_unlock(actor->msg_mutex);
} }
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval) uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval)
{ {
pthread_mutex_lock(actor->mutex); pthread_mutex_lock(actor->mutex);
// Check if this timer is still valid (match actor->ar) // Check if this timer is still valid (match actor->ar)
@@ -556,22 +549,22 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
return 0; return 0;
} }
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded_ref.val)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor, ret);
} }
int should_free = (actor->state == ACTOR_IDLE); int should_free = (actor->state == ACTOR_IDLE);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
if (should_free) actor_free(actor); if (should_free) actor_free(actor);
return 0; return 0;
} }
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(JSContext *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
@@ -582,14 +575,14 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
actor->unneeded_ref.val = fn; actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
if (actor->ar) if (actor->ar)
actor->ar = 0; actor->ar = 0;
set_actor_state(actor); set_actor_state(actor);
} }
cell_rt *get_actor(char *id) JSContext *get_actor(char *id)
{ {
int idx = lockless_shgeti(actors, id); int idx = lockless_shgeti(actors, id);
if (idx == -1) { if (idx == -1) {
@@ -622,17 +615,14 @@ void actor_loop()
} }
} }
cell_rt *create_actor(void *wota) extern JSRuntime *g_runtime;
JSContext *create_actor(void *wota)
{ {
cell_rt *actor = calloc(sizeof(*actor), 1); JSContext *actor = JS_NewContext(g_runtime);
#ifdef HAVE_MIMALLOC if (!actor) return NULL;
actor->heap = mi_heap_new();
#endif
actor->init_wota = wota; actor->init_wota = wota;
/* GCRef fields are registered after JSContext creation in script_startup.
For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
which is not a pointer so GC-safe). The actual JS_NULL assignment and
JS_AddGCRef happen in script_startup. */
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
@@ -642,19 +632,22 @@ cell_rt *create_actor(void *wota)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(actor->mutex, &attr); pthread_mutex_init(actor->mutex, &attr);
actor->msg_mutex = malloc(sizeof(pthread_mutex_t)); actor->msg_mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(actor->msg_mutex, &attr); // msg_mutex can be recursive too to be safe pthread_mutex_init(actor->msg_mutex, &attr);
pthread_mutexattr_destroy(&attr); pthread_mutexattr_destroy(&attr);
JS_SetGCScanExternal(actor, actor_gc_scan);
JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT);
/* Lock actor->mutex while initializing JS runtime. */ /* Lock actor->mutex while initializing JS runtime. */
pthread_mutex_lock(actor->mutex); pthread_mutex_lock(actor->mutex);
script_startup(actor); script_startup(actor);
set_actor_state(actor); set_actor_state(actor);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
return actor; return actor;
} }
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar) const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar)
{ {
actor->main_thread_only = mainthread; actor->main_thread_only = mainthread;
actor->id = strdup(id); actor->id = strdup(id);
@@ -676,29 +669,29 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
const char *send_message(const char *id, void *msg) const char *send_message(const char *id, void *msg)
{ {
cell_rt *target = get_actor(id); JSContext *target = get_actor(id);
if (!target) { if (!target) {
blob_destroy((blob *)msg); blob_destroy((blob *)msg);
return "Could not get actor from id."; return "Could not get actor from id.";
} }
letter l; letter l;
l.type = LETTER_BLOB; l.type = LETTER_BLOB;
l.blob_data = (blob *)msg; l.blob_data = (blob *)msg;
pthread_mutex_lock(target->msg_mutex); pthread_mutex_lock(target->msg_mutex);
arrput(target->letters, l); arrput(target->letters, l);
pthread_mutex_unlock(target->msg_mutex); pthread_mutex_unlock(target->msg_mutex);
if (target->ar) if (target->ar)
target->ar = 0; target->ar = 0;
set_actor_state(target); set_actor_state(target);
return NULL; return NULL;
} }
void actor_turn(cell_rt *actor) void actor_turn(JSContext *actor)
{ {
pthread_mutex_lock(actor->mutex); pthread_mutex_lock(actor->mutex);
#ifdef ACTOR_TRACE #ifdef ACTOR_TRACE
@@ -710,21 +703,21 @@ void actor_turn(cell_rt *actor)
actor->name ? actor->name : actor->id, prev_state); actor->name ? actor->name : actor->id, prev_state);
#endif #endif
if (!actor->trace_hook) { if (!actor->actor_trace_hook) {
cell_hook gh = cell_trace_gethook(); cell_hook gh = cell_trace_gethook();
if (gh) actor->trace_hook = gh; if (gh) actor->actor_trace_hook = gh;
} }
if (actor->trace_hook) if (actor->actor_trace_hook)
actor->trace_hook(actor, CELL_HOOK_ENTER); actor->actor_trace_hook(actor, CELL_HOOK_ENTER);
JSValue result; JSValue result;
if (actor->vm_suspended) { if (actor->vm_suspended) {
/* RESUME path: continue suspended turn under kill timer only */ /* RESUME path: continue suspended turn under kill timer only */
g_crash_ctx = actor->context; g_crash_ctx = actor;
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed); atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
JS_SetPauseFlag(actor->context, 0); JS_SetPauseFlag(actor, 0);
actor->turn_start_ns = cell_ns(); actor->turn_start_ns = cell_ns();
/* Register kill timer only for resume */ /* Register kill timer only for resume */
@@ -734,7 +727,7 @@ void actor_turn(cell_rt *actor)
pthread_cond_signal(&engine.timer_cond); pthread_cond_signal(&engine.timer_cond);
pthread_mutex_unlock(&engine.lock); pthread_mutex_unlock(&engine.lock);
result = JS_ResumeRegisterVM(actor->context); result = JS_ResumeRegisterVM(actor);
actor->vm_suspended = 0; actor->vm_suspended = 0;
if (JS_IsSuspended(result)) { if (JS_IsSuspended(result)) {
@@ -743,7 +736,7 @@ void actor_turn(cell_rt *actor)
goto ENDTURN; goto ENDTURN;
} }
if (JS_IsException(result)) { if (JS_IsException(result)) {
if (!uncaught_exception(actor->context, result)) if (!uncaught_exception(actor, result))
actor->disrupt = 1; actor->disrupt = 1;
} }
actor->slow_strikes++; actor->slow_strikes++;
@@ -778,7 +771,7 @@ void actor_turn(cell_rt *actor)
pthread_mutex_unlock(actor->msg_mutex); pthread_mutex_unlock(actor->msg_mutex);
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed); atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
JS_SetPauseFlag(actor->context, 0); JS_SetPauseFlag(actor, 0);
actor->turn_start_ns = cell_ns(); actor->turn_start_ns = cell_ns();
/* Register both pause and kill timers */ /* Register both pause and kill timers */
@@ -790,35 +783,35 @@ void actor_turn(cell_rt *actor)
pthread_cond_signal(&engine.timer_cond); pthread_cond_signal(&engine.timer_cond);
pthread_mutex_unlock(&engine.lock); pthread_mutex_unlock(&engine.lock);
g_crash_ctx = actor->context; g_crash_ctx = actor;
if (l.type == LETTER_BLOB) { if (l.type == LETTER_BLOB) {
size_t size = blob_length(l.blob_data) / 8; size_t size = blob_length(l.blob_data) / 8;
JSValue arg = js_new_blob_stoned_copy(actor->context, JSValue arg = js_new_blob_stoned_copy(actor,
(void *)blob_data(l.blob_data), size); (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle_ref.val, result = JS_Call(actor, actor->message_handle_ref.val,
JS_NULL, 1, &arg); JS_NULL, 1, &arg);
if (JS_IsSuspended(result)) { if (JS_IsSuspended(result)) {
actor->vm_suspended = 1; actor->vm_suspended = 1;
actor->state = ACTOR_SLOW; actor->state = ACTOR_SLOW;
JS_FreeValue(actor->context, arg); JS_FreeValue(actor, arg);
goto ENDTURN_SLOW; goto ENDTURN_SLOW;
} }
if (!uncaught_exception(actor->context, result)) if (!uncaught_exception(actor, result))
actor->disrupt = 1; actor->disrupt = 1;
JS_FreeValue(actor->context, arg); JS_FreeValue(actor, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL); result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
if (JS_IsSuspended(result)) { if (JS_IsSuspended(result)) {
actor->vm_suspended = 1; actor->vm_suspended = 1;
actor->state = ACTOR_SLOW; actor->state = ACTOR_SLOW;
JS_FreeValue(actor->context, l.callback); JS_FreeValue(actor, l.callback);
goto ENDTURN_SLOW; goto ENDTURN_SLOW;
} }
if (!uncaught_exception(actor->context, result)) if (!uncaught_exception(actor, result))
actor->disrupt = 1; actor->disrupt = 1;
JS_FreeValue(actor->context, l.callback); JS_FreeValue(actor, l.callback);
} }
if (actor->disrupt) goto ENDTURN; if (actor->disrupt) goto ENDTURN;
@@ -830,8 +823,8 @@ ENDTURN:
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed); atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
actor->state = ACTOR_IDLE; actor->state = ACTOR_IDLE;
if (actor->trace_hook) if (actor->actor_trace_hook)
actor->trace_hook(actor, CELL_HOOK_EXIT); actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
if (actor->disrupt) { if (actor->disrupt) {
#ifdef SCHEDULER_DEBUG #ifdef SCHEDULER_DEBUG
@@ -857,8 +850,8 @@ ENDTURN_SLOW:
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n", fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
actor->name ? actor->name : actor->id); actor->name ? actor->name : actor->id);
#endif #endif
if (actor->trace_hook) if (actor->actor_trace_hook)
actor->trace_hook(actor, CELL_HOOK_EXIT); actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
enqueue_actor_priority(actor); enqueue_actor_priority(actor);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
} }
@@ -869,51 +862,50 @@ void actor_gc_scan(JSContext *ctx,
uint8_t *from_base, uint8_t *from_end, uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) uint8_t *to_base, uint8_t **to_free, uint8_t *to_end)
{ {
cell_rt *actor = JS_GetContextOpaque(ctx);
if (!actor) return;
/* Lock msg_mutex to synchronize with the timer thread, which reads /* Lock msg_mutex to synchronize with the timer thread, which reads
timers and writes letters under the same lock. */ timers and writes letters under the same lock. */
pthread_mutex_lock(actor->msg_mutex); if (ctx->msg_mutex)
pthread_mutex_lock(ctx->msg_mutex);
for (int i = 0; i < arrlen(actor->letters); i++) { for (int i = 0; i < arrlen(ctx->letters); i++) {
if (actor->letters[i].type == LETTER_CALLBACK) { if (ctx->letters[i].type == LETTER_CALLBACK) {
actor->letters[i].callback = gc_copy_value(ctx, ctx->letters[i].callback = gc_copy_value(ctx,
actor->letters[i].callback, ctx->letters[i].callback,
from_base, from_end, to_base, to_free, to_end); from_base, from_end, to_base, to_free, to_end);
} }
} }
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(ctx->timers); i++) {
actor->timers[i].value = gc_copy_value(ctx, ctx->timers[i].value = gc_copy_value(ctx,
actor->timers[i].value, ctx->timers[i].value,
from_base, from_end, to_base, to_free, to_end); from_base, from_end, to_base, to_free, to_end);
} }
pthread_mutex_unlock(actor->msg_mutex); if (ctx->msg_mutex)
pthread_mutex_unlock(ctx->msg_mutex);
} }
void actor_clock(cell_rt *actor, JSValue fn) void actor_clock(JSContext *actor, JSValue fn)
{ {
letter l; letter l;
l.type = LETTER_CALLBACK; l.type = LETTER_CALLBACK;
l.callback = JS_DupValue(actor->context, fn); l.callback = JS_DupValue(actor, fn);
pthread_mutex_lock(actor->msg_mutex); pthread_mutex_lock(actor->msg_mutex);
arrput(actor->letters, l); arrput(actor->letters, l);
pthread_mutex_unlock(actor->msg_mutex); pthread_mutex_unlock(actor->msg_mutex);
set_actor_state(actor); set_actor_state(actor);
} }
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds) uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
{ {
pthread_mutex_lock(actor->msg_mutex); pthread_mutex_lock(actor->msg_mutex);
static uint32_t global_timer_id = 1; static uint32_t global_timer_id = 1;
uint32_t id = global_timer_id++; uint32_t id = global_timer_id++;
JSValue cb = JS_DupValue(actor->context, fn); JSValue cb = JS_DupValue(actor, fn);
hmput(actor->timers, id, cb); hmput(actor->timers, id, cb);
uint64_t now = cell_ns(); uint64_t now = cell_ns();
uint64_t execute_at = now + (uint64_t)(seconds * 1e9); uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
@@ -928,7 +920,7 @@ uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
return id; return id;
} }
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id) JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
{ {
JSValue cb = JS_NULL; JSValue cb = JS_NULL;
pthread_mutex_lock(actor->msg_mutex); pthread_mutex_lock(actor->msg_mutex);

View File

@@ -1,19 +1,19 @@
#include "stb_ds.h" #include "stb_ds.h"
#include "cell.h" #include "cell.h"
#include "cell_internal.h" #include "pit_internal.h"
// --- Data Structures --- // --- Data Structures ---
// Simple linked list for the ready queue // Simple linked list for the ready queue
typedef struct actor_node { typedef struct actor_node {
cell_rt *actor; JSContext *actor;
struct actor_node *next; struct actor_node *next;
} actor_node; } actor_node;
// Timer node for the min-heap // Timer node for the min-heap
typedef struct { typedef struct {
uint64_t execute_at_ns; uint64_t execute_at_ns;
cell_rt *actor; JSContext *actor;
uint32_t timer_id; uint32_t timer_id;
int is_native; // 1 for native remove timer, 0 for JS timer int is_native; // 1 for native remove timer, 0 for JS timer
} timer_node; } timer_node;
@@ -24,27 +24,27 @@ static actor_node *ready_head = NULL;
static actor_node *ready_tail = NULL; static actor_node *ready_tail = NULL;
static timer_node *timer_heap = NULL; // stb_ds array static timer_node *timer_heap = NULL; // stb_ds array
static struct { char *key; cell_rt *value; } *actors = NULL; // stb_ds hashmap static struct { char *key; JSContext *value; } *actors = NULL; // stb_ds hashmap
static int shutting_down = 0; static int shutting_down = 0;
// --- Forward Declarations --- // --- Forward Declarations ---
void actor_turn(cell_rt *actor); void actor_turn(JSContext *actor);
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval); uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval);
// --- Heap Helpers --- // --- Heap Helpers ---
static void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, int is_native) { static void heap_push(uint64_t when, JSContext *actor, uint32_t timer_id, int is_native) {
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .is_native = is_native }; timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .is_native = is_native };
arrput(timer_heap, node); arrput(timer_heap, node);
// Bubble up // Bubble up
int i = arrlen(timer_heap) - 1; int i = arrlen(timer_heap) - 1;
while (i > 0) { while (i > 0) {
int parent = (i - 1) / 2; int parent = (i - 1) / 2;
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break; if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
timer_node tmp = timer_heap[i]; timer_node tmp = timer_heap[i];
timer_heap[i] = timer_heap[parent]; timer_heap[i] = timer_heap[parent];
timer_heap[parent] = tmp; timer_heap[parent] = tmp;
@@ -54,10 +54,10 @@ static void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, int is_n
static int heap_pop(timer_node *out) { static int heap_pop(timer_node *out) {
if (arrlen(timer_heap) == 0) return 0; if (arrlen(timer_heap) == 0) return 0;
*out = timer_heap[0]; *out = timer_heap[0];
timer_node last = arrpop(timer_heap); timer_node last = arrpop(timer_heap);
if (arrlen(timer_heap) > 0) { if (arrlen(timer_heap) > 0) {
timer_heap[0] = last; timer_heap[0] = last;
// Bubble down // Bubble down
@@ -67,14 +67,14 @@ static int heap_pop(timer_node *out) {
int left = 2 * i + 1; int left = 2 * i + 1;
int right = 2 * i + 2; int right = 2 * i + 2;
int smallest = i; int smallest = i;
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns) if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
smallest = left; smallest = left;
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns) if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
smallest = right; smallest = right;
if (smallest == i) break; if (smallest == i) break;
timer_node tmp = timer_heap[i]; timer_node tmp = timer_heap[i];
timer_heap[i] = timer_heap[smallest]; timer_heap[i] = timer_heap[smallest];
timer_heap[smallest] = tmp; timer_heap[smallest] = tmp;
@@ -90,10 +90,10 @@ void actor_initialize(void)
{ {
} }
void actor_free(cell_rt *actor) void actor_free(JSContext *actor)
{ {
shdel(actors, actor->id); shdel(actors, actor->id);
// Remove from ready queue if present (O(N)) // Remove from ready queue if present (O(N))
if (ready_head) { if (ready_head) {
if (ready_head->actor == actor) { if (ready_head->actor == actor) {
@@ -115,43 +115,33 @@ void actor_free(cell_rt *actor)
} }
} }
} }
JSContext *js = actor->context;
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_DeleteGCRef(js, &actor->actor_sym_ref);
/* Free timer callbacks stored in actor */
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value); JS_FreeValue(actor, actor->timers[i].value);
} }
hmfree(actor->timers); hmfree(actor->timers);
/* Free all letters in the queue */ /* Free all letters in the queue */
for (int i = 0; i < arrlen(actor->letters); i++) { for (int i = 0; i < arrlen(actor->letters); i++) {
if (actor->letters[i].type == LETTER_BLOB) { if (actor->letters[i].type == LETTER_BLOB) {
blob_destroy(actor->letters[i].blob_data); blob_destroy(actor->letters[i].blob_data);
} else if (actor->letters[i].type == LETTER_CALLBACK) { } else if (actor->letters[i].type == LETTER_CALLBACK) {
JS_FreeValue(js, actor->letters[i].callback); JS_FreeValue(actor, actor->letters[i].callback);
} }
} }
arrfree(actor->letters); arrfree(actor->letters);
JS_FreeContext(js);
free(actor->id); free(actor->id);
free(actor); JS_FreeContext(actor);
int actor_count = shlen(actors); int actor_count = shlen(actors);
if (actor_count == 0) exit(0); if (actor_count == 0) exit(0);
} }
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(JSContext *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
@@ -162,15 +152,10 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
actor->unneeded_ref.val = fn; actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
// If there was an existing unneeded timer, it will be handled/ignored in set_actor_state logic
// or we can explicitly invalidate it if we tracked the ID.
// For now, set_actor_state will schedule a new one if idle.
if (actor->ar) { if (actor->ar) {
// In single threaded, we can't easily remove from heap O(N), actor->ar = 0;
// but we can just let it fire and check ID match.
actor->ar = 0;
} }
set_actor_state(actor); set_actor_state(actor);
} }
@@ -195,15 +180,15 @@ int actor_exists(const char *id)
return idx != -1; return idx != -1;
} }
void set_actor_state(cell_rt *actor) void set_actor_state(JSContext *actor)
{ {
if (actor->disrupt) { if (actor->disrupt) {
actor_free(actor); actor_free(actor);
return; return;
} }
// No mutex needed in single threaded // No mutex needed in single threaded
switch(actor->state) { switch(actor->state) {
case ACTOR_RUNNING: case ACTOR_RUNNING:
case ACTOR_READY: case ACTOR_READY:
@@ -212,59 +197,59 @@ void set_actor_state(cell_rt *actor)
actor->ar = 0; actor->ar = 0;
} }
break; break;
case ACTOR_IDLE: case ACTOR_IDLE:
if (arrlen(actor->letters)) { if (arrlen(actor->letters)) {
actor->state = ACTOR_READY; actor->state = ACTOR_READY;
// Add to ready queue // Add to ready queue
actor_node *n = malloc(sizeof(actor_node)); actor_node *n = malloc(sizeof(actor_node));
n->actor = actor; n->actor = actor;
n->next = NULL; n->next = NULL;
if (ready_tail) { if (ready_tail) {
ready_tail->next = n; ready_tail->next = n;
} else { } else {
ready_head = n; ready_head = n;
} }
ready_tail = n; ready_tail = n;
} else if (!arrlen(actor->letters) && !hmlen(actor->timers)) { } else if (!arrlen(actor->letters) && !hmlen(actor->timers)) {
// Schedule remove timer // Schedule remove timer
static uint32_t global_timer_id = 1; static uint32_t global_timer_id = 1;
uint32_t id = global_timer_id++; uint32_t id = global_timer_id++;
actor->ar = id; actor->ar = id;
uint64_t now = cell_ns(); uint64_t now = cell_ns();
uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9); uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9);
heap_push(execute_at, actor, id, 1); // 1 = native remove heap_push(execute_at, actor, id, 1); // 1 = native remove
} }
break; break;
} }
} }
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval) uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval)
{ {
// Check if this timer is still valid (match actor->ar) // Check if this timer is still valid (match actor->ar)
if (actor->ar != id && id != 0) { if (actor->ar != id && id != 0) {
return 0; return 0;
} }
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded_ref.val)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor, ret);
} }
int should_free = (actor->state == ACTOR_IDLE); int should_free = (actor->state == ACTOR_IDLE);
if (should_free) actor_free(actor); if (should_free) actor_free(actor);
return 0; return 0;
} }
cell_rt *get_actor(char *id) JSContext *get_actor(char *id)
{ {
int idx = shgeti(actors, id); int idx = shgeti(actors, id);
if (idx == -1) { if (idx == -1) {
@@ -282,19 +267,19 @@ void actor_loop()
actor_node *node = ready_head; actor_node *node = ready_head;
ready_head = node->next; ready_head = node->next;
if (!ready_head) ready_tail = NULL; if (!ready_head) ready_tail = NULL;
actor_turn(node->actor); actor_turn(node->actor);
free(node); free(node);
continue; // Loop again to check for more work or timers continue; // Loop again to check for more work or timers
} }
// 2. Check Timers // 2. Check Timers
uint64_t now = cell_ns(); uint64_t now = cell_ns();
if (arrlen(timer_heap) > 0) { if (arrlen(timer_heap) > 0) {
if (timer_heap[0].execute_at_ns <= now) { if (timer_heap[0].execute_at_ns <= now) {
timer_node t; timer_node t;
heap_pop(&t); heap_pop(&t);
if (t.is_native) { if (t.is_native) {
actor_remove_cb(t.actor, t.timer_id, 0); actor_remove_cb(t.actor, t.timer_id, 0);
} else { } else {
@@ -304,7 +289,7 @@ void actor_loop()
JSValue cb = t.actor->timers[idx].value; JSValue cb = t.actor->timers[idx].value;
hmdel(t.actor->timers, t.timer_id); hmdel(t.actor->timers, t.timer_id);
actor_clock(t.actor, cb); actor_clock(t.actor, cb);
JS_FreeValue(t.actor->context, cb); JS_FreeValue(t.actor, cb);
} }
} }
continue; // Loop again continue; // Loop again
@@ -320,30 +305,32 @@ void actor_loop()
} }
} }
cell_rt *create_actor(void *wota) extern JSRuntime *g_runtime;
JSContext *create_actor(void *wota)
{ {
cell_rt *actor = calloc(sizeof(*actor), 1); JSContext *actor = JS_NewContext(g_runtime);
if (!actor) return NULL;
actor->init_wota = wota; actor->init_wota = wota;
/* GCRef fields are registered after JSContext creation in script_startup. */
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
// No mutexes needed JS_SetGCScanExternal(actor, actor_gc_scan);
actor->mutex = NULL; JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT);
actor->msg_mutex = NULL;
script_startup(actor); script_startup(actor);
set_actor_state(actor); set_actor_state(actor);
return actor; return actor;
} }
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar) const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar)
{ {
actor->main_thread_only = mainthread; actor->main_thread_only = mainthread;
actor->id = strdup(id); actor->id = strdup(id);
actor->ar_secs = ar; actor->ar_secs = ar;
if (shgeti(actors, id) != -1) { if (shgeti(actors, id) != -1) {
free(actor->id); free(actor->id);
return "Actor with given ID already exists."; return "Actor with given ID already exists.";
@@ -355,82 +342,82 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
const char *send_message(const char *id, void *msg) const char *send_message(const char *id, void *msg)
{ {
cell_rt *target = get_actor(id); JSContext *target = get_actor(id);
if (!target) { if (!target) {
blob_destroy((blob *)msg); blob_destroy((blob *)msg);
return "Could not get actor from id."; return "Could not get actor from id.";
} }
letter l; letter l;
l.type = LETTER_BLOB; l.type = LETTER_BLOB;
l.blob_data = (blob *)msg; l.blob_data = (blob *)msg;
arrput(target->letters, l); arrput(target->letters, l);
if (target->ar) { if (target->ar) {
// Invalidate unneeded timer // Invalidate unneeded timer
target->ar = 0; target->ar = 0;
} }
set_actor_state(target); set_actor_state(target);
return NULL; return NULL;
} }
void actor_turn(cell_rt *actor) void actor_turn(JSContext *actor)
{ {
actor->state = ACTOR_RUNNING; actor->state = ACTOR_RUNNING;
JSValue result; JSValue result;
TAKETURN: TAKETURN:
if (!arrlen(actor->letters)) { if (!arrlen(actor->letters)) {
goto ENDTURN; goto ENDTURN;
} }
letter l = actor->letters[0]; letter l = actor->letters[0];
arrdel(actor->letters, 0); arrdel(actor->letters, 0);
if (l.type == LETTER_BLOB) { if (l.type == LETTER_BLOB) {
// Create a JS blob from the C blob // Create a JS blob from the C blob
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor, (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg); result = JS_Call(actor, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); uncaught_exception(actor, result);
JS_FreeValue(actor->context, arg); JS_FreeValue(actor, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL); result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
uncaught_exception(actor->context, result); uncaught_exception(actor, result);
JS_FreeValue(actor->context, l.callback); JS_FreeValue(actor, l.callback);
} }
if (actor->disrupt) goto ENDTURN; if (actor->disrupt) goto ENDTURN;
ENDTURN: ENDTURN:
actor->state = ACTOR_IDLE; actor->state = ACTOR_IDLE;
set_actor_state(actor); set_actor_state(actor);
} }
void actor_clock(cell_rt *actor, JSValue fn) void actor_clock(JSContext *actor, JSValue fn)
{ {
letter l; letter l;
l.type = LETTER_CALLBACK; l.type = LETTER_CALLBACK;
l.callback = JS_DupValue(actor->context, fn); l.callback = JS_DupValue(actor, fn);
arrput(actor->letters, l); arrput(actor->letters, l);
set_actor_state(actor); set_actor_state(actor);
} }
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds) uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
{ {
static uint32_t global_timer_id = 1; static uint32_t global_timer_id = 1;
uint32_t id = global_timer_id++; uint32_t id = global_timer_id++;
JSValue cb = JS_DupValue(actor->context, fn); JSValue cb = JS_DupValue(actor, fn);
hmput(actor->timers, id, cb); hmput(actor->timers, id, cb);
uint64_t now = cell_ns(); uint64_t now = cell_ns();
uint64_t execute_at = now + (uint64_t)(seconds * 1e9); uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
@@ -439,7 +426,7 @@ uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
return id; return id;
} }
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id) JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
{ {
JSValue cb = JS_NULL; JSValue cb = JS_NULL;
int id = hmgeti(actor->timers, timer_id); int id = hmgeti(actor->timers, timer_id);

View File

@@ -5,7 +5,7 @@
*/ */
#include "cell.h" #include "cell.h"
#include "quickjs-internal.h" #include "pit_internal.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>