diff --git a/debug/js.c b/debug/js.c index 3acec8e5..61dcbdff 100644 --- a/debug/js.c +++ b/debug/js.c @@ -1,5 +1,5 @@ #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_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0]))) diff --git a/docs/spec/c-runtime.md b/docs/spec/c-runtime.md index a35c0926..99e9fd72 100644 --- a/docs/spec/c-runtime.md +++ b/docs/spec/c-runtime.md @@ -241,7 +241,7 @@ source/ **`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`) 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 diff --git a/internal/nota.c b/internal/nota.c index fe4999f2..63f326ec 100644 --- a/internal/nota.c +++ b/internal/nota.c @@ -1,5 +1,5 @@ #define NOTA_IMPLEMENTATION -#include "quickjs-internal.h" +#include "pit_internal.h" #include "cell.h" static int nota_get_arr_len (JSContext *ctx, JSValue arr) { diff --git a/internal/os.c b/internal/os.c index f2fc659b..6b99b1fd 100644 --- a/internal/os.c +++ b/internal/os.c @@ -1,5 +1,5 @@ #include "cell.h" -#include "cell_internal.h" +#include "pit_internal.h" #include #include @@ -308,12 +308,9 @@ JSC_SCALL(os_system, setting pause_flag = 2. Bump turn_gen so stale timer events are ignored, and clear the pause flag so the VM doesn't raise "interrupted" on the next backward branch. */ - cell_rt *crt = JS_GetContextOpaque(js); - if (crt) { - atomic_fetch_add_explicit(&crt->turn_gen, 1, memory_order_relaxed); - JS_SetPauseFlag(js, 0); - crt->turn_start_ns = cell_ns(); - } + atomic_fetch_add_explicit(&js->turn_gen, 1, memory_order_relaxed); + JS_SetPauseFlag(js, 0); + js->turn_start_ns = cell_ns(); ret = number2js(js, err); ) diff --git a/internal/wota.c b/internal/wota.c index ce32c800..15f3c903 100644 --- a/internal/wota.c +++ b/internal/wota.c @@ -1,5 +1,5 @@ #define WOTA_IMPLEMENTATION -#include "quickjs-internal.h" +#include "pit_internal.h" #include "cell.h" typedef struct ObjectRef { diff --git a/source/cell.c b/source/cell.c index 9c84cc85..b921b6b8 100644 --- a/source/cell.c +++ b/source/cell.c @@ -8,8 +8,7 @@ #include "stb_ds.h" #include "cell.h" -#include "quickjs-internal.h" -#include "cell_internal.h" +#include "pit_internal.h" #define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode" #define ENGINE_SRC "internal/engine.cm" @@ -27,13 +26,13 @@ int run_c_test_suite(JSContext *ctx); static int run_test_suite(size_t heap_size); -cell_rt *root_cell = NULL; +static JSContext *root_ctx = NULL; static char *shop_path = NULL; volatile JSContext *g_crash_ctx = NULL; static char *core_path = NULL; static int native_mode = 0; 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) 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; } -void actor_disrupt(cell_rt *crt) +void actor_disrupt(JSContext *ctx) { - crt->disrupt = 1; - JS_SetPauseFlag(crt->context, 2); - if (crt->state != ACTOR_RUNNING) - actor_free(crt); + ctx->disrupt = 1; + JS_SetPauseFlag(ctx, 2); + if (ctx->state != ACTOR_RUNNING) + actor_free(ctx); } JSValue js_core_internal_os_use(JSContext *js); JSValue js_core_json_use(JSContext *js); -void script_startup(cell_rt *prt) +void script_startup(JSContext *js) { if (!g_runtime) { g_runtime = JS_NewRuntime(); } - JSContext *js = JS_NewContext(g_runtime); - JS_SetContextOpaque(js, prt); JS_SetGCScanExternal(js, actor_gc_scan); - prt->context = js; /* Set per-actor heap 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)); // 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); 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); free(boot_bin); uncaught_exception(js, bv); - crt->state = ACTOR_IDLE; + js->state = ACTOR_IDLE; // Retry engine from cache bin_data = try_engine_cache(&bin_size); @@ -405,20 +388,20 @@ void script_startup(cell_rt *prt) tmp = js_core_json_use(js); JS_SetPropertyStr(js, env_ref.val, "json", tmp); - crt->actor_sym_ref.val = JS_NewObject(js); - JS_CellStone(js, crt->actor_sym_ref.val); - JS_SetActorSym(js, JS_DupValue(js, crt->actor_sym_ref.val)); - JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); + js->actor_sym_ref.val = JS_NewObject(js); + JS_CellStone(js, js->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, js->actor_sym_ref.val)); // Always set init (even if null) - if (crt->init_wota) { + if (js->init_wota) { JSGCRef 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_PopGCRef(js, &init_ref); - free(crt->init_wota); - crt->init_wota = NULL; + free(js->init_wota); + js->init_wota = NULL; } else { JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL); } @@ -438,12 +421,12 @@ void script_startup(cell_rt *prt) JS_DeleteGCRef(js, &env_ref); // 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); free(bin_data); uncaught_exception(js, v); - crt->state = ACTOR_IDLE; - set_actor_state(crt); + js->state = ACTOR_IDLE; + set_actor_state(js); } static void signal_handler(int sig) @@ -624,35 +607,23 @@ int cell_init(int argc, char **argv) return 1; } - /* Create a cell_rt for the CLI context so JS-C bridge functions work */ - cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1); - cli_rt->mutex = malloc(sizeof(pthread_mutex_t)); + /* Set up mutexes on the CLI context */ + ctx->mutex = malloc(sizeof(pthread_mutex_t)); pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(cli_rt->mutex, &mattr); - cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t)); - pthread_mutex_init(cli_rt->msg_mutex, &mattr); + pthread_mutex_init(ctx->mutex, &mattr); + ctx->msg_mutex = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(ctx->msg_mutex, &mattr); pthread_mutexattr_destroy(&mattr); - cli_rt->context = ctx; - JS_SetContextOpaque(ctx, cli_rt); JS_SetGCScanExternal(ctx, actor_gc_scan); - JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref); - JS_AddGCRef(ctx, &cli_rt->on_exception_ref); - JS_AddGCRef(ctx, &cli_rt->message_handle_ref); - 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)); + ctx->actor_sym_ref.val = JS_NewObject(ctx); + JS_CellStone(ctx, ctx->actor_sym_ref.val); + JS_SetActorSym(ctx, JS_DupValue(ctx, ctx->actor_sym_ref.val)); - root_cell = cli_rt; + root_ctx = 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); 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, "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); JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp); if (native_mode) @@ -763,7 +734,7 @@ int cell_init(int argc, char **argv) JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp); 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, "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); JS_SetPropertyStr(ctx, env_ref.val, "json", tmp); if (native_mode || !warn_mode || eval_script) { @@ -826,18 +797,11 @@ check_actors: } /* No actors spawned — clean up CLI context */ - JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref); - JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref); - JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref); - JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref); - JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref); - - 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; + pthread_mutex_destroy(ctx->mutex); + free(ctx->mutex); + pthread_mutex_destroy(ctx->msg_mutex); + free(ctx->msg_mutex); + root_ctx = NULL; JS_FreeContext(ctx); 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) { - struct cell_rt *rt = JS_GetContextOpaque(ctx); - 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; + ctx->actor_trace_hook = hook; } 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. */ if (has_exc) JS_GetException(js); - cell_rt *crt = JS_GetContextOpaque(js); - if (crt && !JS_IsNull(crt->on_exception_ref.val)) { + if (!JS_IsNull(js->on_exception_ref.val)) { /* Disable interruption so actor_die can send messages without being re-interrupted. */ JS_SetPauseFlag(js, 0); 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. */ if (JS_HasException(js)) JS_GetException(js); diff --git a/source/cell.h b/source/cell.h index cf317c77..96b7258a 100644 --- a/source/cell.h +++ b/source/cell.h @@ -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 #define CELL_H @@ -282,7 +257,7 @@ static inline JSValue MIST_TryNewImmediateASCII (const char *str, size_t len) { 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) */ JS_BOOL JS_IsArray(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_EXIT 2 -struct cell_rt; -typedef void (*cell_hook)(struct cell_rt *rt, int type); +typedef void (*cell_hook)(JSContext *ctx, int type); void cell_trace_sethook(cell_hook); cell_hook cell_trace_gethook(void); void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook); 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); /* ============================================================ diff --git a/source/cell_internal.h b/source/cell_internal.h deleted file mode 100644 index 54011057..00000000 --- a/source/cell_internal.h +++ /dev/null @@ -1,118 +0,0 @@ - -#include -#include - -/* 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); diff --git a/source/mach.c b/source/mach.c index ae77e4ca..87bc80b8 100644 --- a/source/mach.c +++ b/source/mach.c @@ -1,29 +1,4 @@ -/* - * 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 "pit_internal.h" #include /* ============================================================ diff --git a/source/quickjs-internal.h b/source/pit_internal.h similarity index 94% rename from source/quickjs-internal.h rename to source/pit_internal.h index 904402bf..050c6442 100644 --- a/source/quickjs-internal.h +++ b/source/pit_internal.h @@ -1,30 +1,6 @@ #ifndef 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 #include #include @@ -36,6 +12,9 @@ #include #include #include +#ifndef TARGET_PLAYDATE +#include +#endif #if defined(__APPLE__) #include #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_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size); void JS_FreeContext (JSContext *s); -void *JS_GetContextOpaque (JSContext *ctx); -void JS_SetContextOpaque (JSContext *ctx, void *opaque); typedef void (*JS_GCScanFn)(JSContext *ctx, uint8_t *from_base, uint8_t *from_end, @@ -864,6 +841,38 @@ typedef struct CCallRoot { struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */ } 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 { JSRuntime *rt; @@ -919,7 +928,6 @@ struct JSContext { /* if NULL, RegExp compilation is not supported */ JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags); - void *user_opaque; /* GC callback to scan external C-side roots (actor letters, timers) */ 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 size_t stack_depth; 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 ============================================================ */ diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index a6d327a8..e02a77ef 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -6,7 +6,7 @@ * string comparison, bitwise ops on floats, and boolean conversion. */ -#include "quickjs-internal.h" +#include "pit_internal.h" #include #include #include diff --git a/source/qjs_actor.c b/source/qjs_actor.c index c0f11afe..5f4f5228 100644 --- a/source/qjs_actor.c +++ b/source/qjs_actor.c @@ -1,13 +1,11 @@ #include "cell.h" -#include "cell_internal.h" -#include "quickjs-internal.h" +#include "pit_internal.h" #include #include JSC_CCALL(os_createactor, - cell_rt *rt = JS_GetContextOpaque(js); - if (rt->disrupt) + if (js->disrupt) return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting."); void *startup = value2wota(js, argv[0], JS_NULL, NULL); @@ -57,14 +55,12 @@ JSC_CCALL(os_mailbox_push, ) JSC_CCALL(os_register_actor, - cell_rt *rt = JS_GetContextOpaque(js); const char *id = JS_ToCString(js, argv[0]); double ar; 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); - rt->message_handle_ref.val = argv[1]; - rt->context = js; + js->message_handle_ref.val = argv[1]; JS_FreeCString(js, id); ) @@ -77,56 +73,49 @@ JSC_CCALL(os_mailbox_exist, ) JSC_CCALL(os_unneeded, - cell_rt *actor = JS_GetContextOpaque(js); double secs; JS_ToFloat64(js, &secs, argv[1]); - actor_unneeded(actor, argv[0], secs); + actor_unneeded(js, argv[0], secs); ) JSC_CCALL(actor_disrupt, - cell_rt *crt = JS_GetContextOpaque(js); - actor_disrupt(crt); + actor_disrupt(js); ) JSC_SCALL(actor_setname, - cell_rt *rt = JS_GetContextOpaque(js); - rt->name = strdup(str); + js->name = strdup(str); ) JSC_CCALL(actor_on_exception, - cell_rt *rt = JS_GetContextOpaque(js); - rt->on_exception_ref.val = argv[0]; + js->on_exception_ref.val = argv[0]; ) JSC_CCALL(actor_clock, if (!JS_IsFunction(argv[0])) return JS_RaiseDisrupt(js, "Argument must be a function."); - - cell_rt *actor = JS_GetContextOpaque(js); - actor_clock(actor, argv[0]); + + actor_clock(js, argv[0]); ) JSC_CCALL(actor_delay, if (!JS_IsFunction(argv[0])) return JS_RaiseDisrupt(js, "Argument must be a function."); - cell_rt *actor = JS_GetContextOpaque(js); double seconds; JS_ToFloat64(js, &seconds, argv[1]); if (seconds <= 0) { - actor_clock(actor, argv[0]); + actor_clock(js, argv[0]); 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); ) JSC_CCALL(actor_removetimer, - cell_rt *actor = JS_GetContextOpaque(js); uint32_t timer_id; 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]) diff --git a/source/runtime.c b/source/runtime.c index 94c146c6..ec5f3ed3 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -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 -#include "quickjs-internal.h" +#include "pit_internal.h" #include // #define DUMP_BUDDY @@ -87,7 +62,7 @@ static inline JS_BOOL JS_IsInteger (JSValue v) { JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; /* === 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_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 */ if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) { #ifdef ACTOR_TRACE - void *crt = ctx->user_opaque; - if (crt) - fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n", - 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 return -1; } @@ -2080,6 +2053,18 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { ctx->log_callback_js = JS_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) */ { 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; } -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) { ctx->gc_scan_external = fn; @@ -2190,6 +2171,11 @@ void JS_FreeContext (JSContext *ctx) { cell_rt_free_native_state(ctx); 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++) { } diff --git a/source/scheduler.c b/source/scheduler.c index f138164f..34c96438 100644 --- a/source/scheduler.c +++ b/source/scheduler.c @@ -9,15 +9,14 @@ #include "stb_ds.h" #include "cell.h" -#include "quickjs-internal.h" -#include "cell_internal.h" +#include "pit_internal.h" #ifdef _WIN32 #include #endif typedef struct actor_node { - cell_rt *actor; + JSContext *actor; struct actor_node *next; } actor_node; @@ -30,13 +29,13 @@ typedef enum { typedef struct { uint64_t execute_at_ns; - cell_rt *actor; + JSContext *actor; uint32_t timer_id; timer_type type; uint32_t turn_gen; /* generation at registration time */ } timer_node; -static timer_node *timer_heap = NULL; +static timer_node *timer_heap = NULL; // Priority queue indices #define PQ_READY 0 @@ -92,7 +91,7 @@ static int has_any_work(actor_node *heads[]) { } 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_shlen(NAME) ({ \ @@ -109,7 +108,7 @@ static struct { char *key; cell_rt *value; } *actors = NULL; }) #define lockless_shget(NAME, KEY) ({ \ pthread_mutex_lock(NAME##_mutex); \ - cell_rt *_actor = shget(NAME, KEY); \ + JSContext *_actor = shget(NAME, KEY); \ pthread_mutex_unlock(NAME##_mutex); \ _actor; \ }) @@ -122,21 +121,21 @@ static struct { char *key; cell_rt *value; } *actors = NULL; }) // Forward declarations -uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval); -void actor_turn(cell_rt *actor); +uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval); +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 }; if (type == TIMER_PAUSE || type == TIMER_KILL) node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed); arrput(timer_heap, node); - + // Bubble up int i = arrlen(timer_heap) - 1; while (i > 0) { int parent = (i - 1) / 2; if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break; - + timer_node tmp = timer_heap[i]; timer_heap[i] = timer_heap[parent]; 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 int heap_pop(timer_node *out) { if (arrlen(timer_heap) == 0) return 0; - + *out = timer_heap[0]; timer_node last = arrpop(timer_heap); - + if (arrlen(timer_heap) > 0) { timer_heap[0] = last; // Bubble down @@ -160,14 +159,14 @@ int heap_pop(timer_node *out) { int left = 2 * i + 1; int right = 2 * i + 2; int smallest = i; - + if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns) smallest = left; if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns) smallest = right; - + if (smallest == i) break; - + timer_node tmp = timer_heap[i]; timer_heap[i] = timer_heap[smallest]; timer_heap[smallest] = tmp; @@ -180,7 +179,7 @@ int heap_pop(timer_node *out) { void *timer_thread_func(void *arg) { while (1) { pthread_mutex_lock(&engine.lock); - + if (engine.shutting_down) { pthread_mutex_unlock(&engine.lock); 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); if (cur == t.turn_gen) { if (t.type == TIMER_PAUSE) { - JS_SetPauseFlag(t.actor->context, 1); + JS_SetPauseFlag(t.actor, 1); } else { t.actor->disrupt = 1; - JS_SetPauseFlag(t.actor->context, 2); + JS_SetPauseFlag(t.actor, 2); } } } else { @@ -216,26 +215,26 @@ void *timer_thread_func(void *arg) { JSValue cb = t.actor->timers[idx].value; hmdel(t.actor->timers, t.timer_id); actor_clock(t.actor, cb); - JS_FreeValue(t.actor->context, cb); + JS_FreeValue(t.actor, cb); } pthread_mutex_unlock(t.actor->msg_mutex); } - - continue; + + continue; } else { // --- WAIT FOR DEADLINE --- struct timespec ts; uint64_t wait_ns = timer_heap[0].execute_at_ns - now; - + // Convert relative wait time to absolute CLOCK_REALTIME struct timespec 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; ts.tv_sec = deadline_real_ns / 1000000000ULL; ts.tv_nsec = deadline_real_ns % 1000000000ULL; - + pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts); } } @@ -244,7 +243,7 @@ void *timer_thread_func(void *arg) { return NULL; } -void enqueue_actor_priority(cell_rt *actor) { +void enqueue_actor_priority(JSContext *actor) { actor_node *n = malloc(sizeof(actor_node)); n->actor = actor; n->next = NULL; @@ -301,10 +300,10 @@ void actor_initialize(void) { engine.mq_head[i] = NULL; engine.mq_tail[i] = NULL; } - + actors_mutex = malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(actors_mutex, NULL); - + // Start Timer Thread 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) { actor->is_quiescent = 0; @@ -364,42 +363,36 @@ void actor_free(cell_rt *actor) pthread_mutex_lock(actor->msg_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++) { - JS_FreeValue(js, actor->timers[i].value); + JS_FreeValue(actor, actor->timers[i].value); } - + hmfree(actor->timers); - + /* Free all letters in the queue */ for (int i = 0; i < arrlen(actor->letters); i++) { if (actor->letters[i].type == LETTER_BLOB) { blob_destroy(actor->letters[i].blob_data); } 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); - - JS_FreeContext(js); + free(actor->id); - - pthread_mutex_unlock(actor->mutex); - pthread_mutex_destroy(actor->mutex); - free(actor->mutex); - pthread_mutex_unlock(actor->msg_mutex); - pthread_mutex_destroy(actor->msg_mutex); - free(actor->msg_mutex); - - free(actor); + + pthread_mutex_t *m = actor->mutex; + pthread_mutex_t *mm = actor->msg_mutex; + + JS_FreeContext(actor); + + pthread_mutex_unlock(m); + pthread_mutex_destroy(m); + free(m); + pthread_mutex_unlock(mm); + pthread_mutex_destroy(mm); + free(mm); int actor_count = lockless_shlen(actors); if (actor_count == 0) { @@ -467,7 +460,7 @@ int actor_exists(const char *id) return idx != -1; } -void set_actor_state(cell_rt *actor) +void set_actor_state(JSContext *actor) { if (actor->disrupt) { #ifdef SCHEDULER_DEBUG @@ -548,7 +541,7 @@ void set_actor_state(cell_rt *actor) 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); // 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); return 0; } - + actor->disrupt = 1; - + if (!JS_IsNull(actor->unneeded_ref.val)) { - JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL); - uncaught_exception(actor->context, ret); + JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL); + uncaught_exception(actor, ret); } int should_free = (actor->state == ACTOR_IDLE); pthread_mutex_unlock(actor->mutex); - + if (should_free) actor_free(actor); 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; @@ -582,14 +575,14 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) actor->unneeded_ref.val = fn; actor->ar_secs = seconds; - + END: if (actor->ar) actor->ar = 0; set_actor_state(actor); } -cell_rt *get_actor(char *id) +JSContext *get_actor(char *id) { int idx = lockless_shgeti(actors, id); 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); -#ifdef HAVE_MIMALLOC - actor->heap = mi_heap_new(); -#endif + JSContext *actor = JS_NewContext(g_runtime); + if (!actor) return NULL; + 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); @@ -642,19 +632,22 @@ cell_rt *create_actor(void *wota) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(actor->mutex, &attr); 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); - + + JS_SetGCScanExternal(actor, actor_gc_scan); + JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT); + /* Lock actor->mutex while initializing JS runtime. */ pthread_mutex_lock(actor->mutex); script_startup(actor); set_actor_state(actor); - pthread_mutex_unlock(actor->mutex); + pthread_mutex_unlock(actor->mutex); 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->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) { - cell_rt *target = get_actor(id); + JSContext *target = get_actor(id); if (!target) { blob_destroy((blob *)msg); return "Could not get actor from id."; } - + letter l; l.type = LETTER_BLOB; l.blob_data = (blob *)msg; - + pthread_mutex_lock(target->msg_mutex); arrput(target->letters, l); pthread_mutex_unlock(target->msg_mutex); if (target->ar) target->ar = 0; - + set_actor_state(target); - + return NULL; } -void actor_turn(cell_rt *actor) +void actor_turn(JSContext *actor) { pthread_mutex_lock(actor->mutex); #ifdef ACTOR_TRACE @@ -710,21 +703,21 @@ void actor_turn(cell_rt *actor) actor->name ? actor->name : actor->id, prev_state); #endif - if (!actor->trace_hook) { + if (!actor->actor_trace_hook) { cell_hook gh = cell_trace_gethook(); - if (gh) actor->trace_hook = gh; + if (gh) actor->actor_trace_hook = gh; } - if (actor->trace_hook) - actor->trace_hook(actor, CELL_HOOK_ENTER); + if (actor->actor_trace_hook) + actor->actor_trace_hook(actor, CELL_HOOK_ENTER); JSValue result; if (actor->vm_suspended) { /* 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); - JS_SetPauseFlag(actor->context, 0); + JS_SetPauseFlag(actor, 0); actor->turn_start_ns = cell_ns(); /* Register kill timer only for resume */ @@ -734,7 +727,7 @@ void actor_turn(cell_rt *actor) pthread_cond_signal(&engine.timer_cond); pthread_mutex_unlock(&engine.lock); - result = JS_ResumeRegisterVM(actor->context); + result = JS_ResumeRegisterVM(actor); actor->vm_suspended = 0; if (JS_IsSuspended(result)) { @@ -743,7 +736,7 @@ void actor_turn(cell_rt *actor) goto ENDTURN; } if (JS_IsException(result)) { - if (!uncaught_exception(actor->context, result)) + if (!uncaught_exception(actor, result)) actor->disrupt = 1; } actor->slow_strikes++; @@ -778,7 +771,7 @@ void actor_turn(cell_rt *actor) pthread_mutex_unlock(actor->msg_mutex); 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(); /* Register both pause and kill timers */ @@ -790,35 +783,35 @@ void actor_turn(cell_rt *actor) pthread_cond_signal(&engine.timer_cond); pthread_mutex_unlock(&engine.lock); - g_crash_ctx = actor->context; + g_crash_ctx = actor; if (l.type == LETTER_BLOB) { 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); 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); if (JS_IsSuspended(result)) { actor->vm_suspended = 1; actor->state = ACTOR_SLOW; - JS_FreeValue(actor->context, arg); + JS_FreeValue(actor, arg); goto ENDTURN_SLOW; } - if (!uncaught_exception(actor->context, result)) + if (!uncaught_exception(actor, result)) actor->disrupt = 1; - JS_FreeValue(actor->context, arg); + JS_FreeValue(actor, arg); } 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)) { actor->vm_suspended = 1; actor->state = ACTOR_SLOW; - JS_FreeValue(actor->context, l.callback); + JS_FreeValue(actor, l.callback); goto ENDTURN_SLOW; } - if (!uncaught_exception(actor->context, result)) + if (!uncaught_exception(actor, result)) actor->disrupt = 1; - JS_FreeValue(actor->context, l.callback); + JS_FreeValue(actor, l.callback); } if (actor->disrupt) goto ENDTURN; @@ -830,8 +823,8 @@ ENDTURN: atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed); actor->state = ACTOR_IDLE; - if (actor->trace_hook) - actor->trace_hook(actor, CELL_HOOK_EXIT); + if (actor->actor_trace_hook) + actor->actor_trace_hook(actor, CELL_HOOK_EXIT); if (actor->disrupt) { #ifdef SCHEDULER_DEBUG @@ -857,8 +850,8 @@ ENDTURN_SLOW: fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n", actor->name ? actor->name : actor->id); #endif - if (actor->trace_hook) - actor->trace_hook(actor, CELL_HOOK_EXIT); + if (actor->actor_trace_hook) + actor->actor_trace_hook(actor, CELL_HOOK_EXIT); enqueue_actor_priority(actor); 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 *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 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++) { - if (actor->letters[i].type == LETTER_CALLBACK) { - actor->letters[i].callback = gc_copy_value(ctx, - actor->letters[i].callback, + for (int i = 0; i < arrlen(ctx->letters); i++) { + if (ctx->letters[i].type == LETTER_CALLBACK) { + ctx->letters[i].callback = gc_copy_value(ctx, + ctx->letters[i].callback, from_base, from_end, to_base, to_free, to_end); } } - for (int i = 0; i < hmlen(actor->timers); i++) { - actor->timers[i].value = gc_copy_value(ctx, - actor->timers[i].value, + for (int i = 0; i < hmlen(ctx->timers); i++) { + ctx->timers[i].value = gc_copy_value(ctx, + ctx->timers[i].value, 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; l.type = LETTER_CALLBACK; - l.callback = JS_DupValue(actor->context, fn); + l.callback = JS_DupValue(actor, fn); pthread_mutex_lock(actor->msg_mutex); arrput(actor->letters, l); pthread_mutex_unlock(actor->msg_mutex); 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; uint32_t id = global_timer_id++; - - JSValue cb = JS_DupValue(actor->context, fn); + + JSValue cb = JS_DupValue(actor, fn); hmput(actor->timers, id, cb); - + uint64_t now = cell_ns(); 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; } -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; pthread_mutex_lock(actor->msg_mutex); diff --git a/source/scheduler_playdate.c b/source/scheduler_playdate.c index 56af288f..60d437c3 100644 --- a/source/scheduler_playdate.c +++ b/source/scheduler_playdate.c @@ -1,19 +1,19 @@ #include "stb_ds.h" #include "cell.h" -#include "cell_internal.h" +#include "pit_internal.h" // --- Data Structures --- // Simple linked list for the ready queue typedef struct actor_node { - cell_rt *actor; + JSContext *actor; struct actor_node *next; } actor_node; // Timer node for the min-heap typedef struct { uint64_t execute_at_ns; - cell_rt *actor; + JSContext *actor; uint32_t timer_id; int is_native; // 1 for native remove timer, 0 for JS timer } timer_node; @@ -24,27 +24,27 @@ static actor_node *ready_head = NULL; static actor_node *ready_tail = NULL; 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; // --- Forward Declarations --- -void actor_turn(cell_rt *actor); -uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval); +void actor_turn(JSContext *actor); +uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval); // --- 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 }; arrput(timer_heap, node); - + // Bubble up int i = arrlen(timer_heap) - 1; while (i > 0) { int parent = (i - 1) / 2; if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break; - + timer_node tmp = timer_heap[i]; timer_heap[i] = timer_heap[parent]; 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) { if (arrlen(timer_heap) == 0) return 0; - + *out = timer_heap[0]; timer_node last = arrpop(timer_heap); - + if (arrlen(timer_heap) > 0) { timer_heap[0] = last; // Bubble down @@ -67,14 +67,14 @@ static int heap_pop(timer_node *out) { int left = 2 * i + 1; int right = 2 * i + 2; int smallest = i; - + if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns) smallest = left; if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns) smallest = right; - + if (smallest == i) break; - + timer_node tmp = timer_heap[i]; timer_heap[i] = timer_heap[smallest]; 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); - + // Remove from ready queue if present (O(N)) if (ready_head) { 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++) { - JS_FreeValue(js, actor->timers[i].value); + JS_FreeValue(actor, actor->timers[i].value); } - + hmfree(actor->timers); - + /* Free all letters in the queue */ for (int i = 0; i < arrlen(actor->letters); i++) { if (actor->letters[i].type == LETTER_BLOB) { blob_destroy(actor->letters[i].blob_data); } 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); - - JS_FreeContext(js); + free(actor->id); - - free(actor); + + JS_FreeContext(actor); int actor_count = shlen(actors); 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; @@ -162,15 +152,10 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) actor->unneeded_ref.val = fn; actor->ar_secs = seconds; - + 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) { - // In single threaded, we can't easily remove from heap O(N), - // but we can just let it fire and check ID match. - actor->ar = 0; + actor->ar = 0; } set_actor_state(actor); } @@ -195,15 +180,15 @@ int actor_exists(const char *id) return idx != -1; } -void set_actor_state(cell_rt *actor) +void set_actor_state(JSContext *actor) { if (actor->disrupt) { actor_free(actor); return; } - + // No mutex needed in single threaded - + switch(actor->state) { case ACTOR_RUNNING: case ACTOR_READY: @@ -212,59 +197,59 @@ void set_actor_state(cell_rt *actor) actor->ar = 0; } break; - + case ACTOR_IDLE: if (arrlen(actor->letters)) { actor->state = ACTOR_READY; - + // Add to ready queue actor_node *n = malloc(sizeof(actor_node)); n->actor = actor; n->next = NULL; - + if (ready_tail) { ready_tail->next = n; } else { ready_head = n; } ready_tail = n; - + } else if (!arrlen(actor->letters) && !hmlen(actor->timers)) { // Schedule remove timer static uint32_t global_timer_id = 1; uint32_t id = global_timer_id++; actor->ar = id; - + uint64_t now = cell_ns(); uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9); - + heap_push(execute_at, actor, id, 1); // 1 = native remove } 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) - if (actor->ar != id && id != 0) { + if (actor->ar != id && id != 0) { return 0; } - + actor->disrupt = 1; - + if (!JS_IsNull(actor->unneeded_ref.val)) { - JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL); - uncaught_exception(actor->context, ret); + JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL); + uncaught_exception(actor, ret); } int should_free = (actor->state == ACTOR_IDLE); - + if (should_free) actor_free(actor); return 0; } -cell_rt *get_actor(char *id) +JSContext *get_actor(char *id) { int idx = shgeti(actors, id); if (idx == -1) { @@ -282,19 +267,19 @@ void actor_loop() actor_node *node = ready_head; ready_head = node->next; if (!ready_head) ready_tail = NULL; - + actor_turn(node->actor); free(node); continue; // Loop again to check for more work or timers } - + // 2. Check Timers uint64_t now = cell_ns(); if (arrlen(timer_heap) > 0) { if (timer_heap[0].execute_at_ns <= now) { timer_node t; heap_pop(&t); - + if (t.is_native) { actor_remove_cb(t.actor, t.timer_id, 0); } else { @@ -304,7 +289,7 @@ void actor_loop() JSValue cb = t.actor->timers[idx].value; hmdel(t.actor->timers, t.timer_id); actor_clock(t.actor, cb); - JS_FreeValue(t.actor->context, cb); + JS_FreeValue(t.actor, cb); } } 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; - /* GCRef fields are registered after JSContext creation in script_startup. */ arrsetcap(actor->letters, 5); - // No mutexes needed - actor->mutex = NULL; - actor->msg_mutex = NULL; - + JS_SetGCScanExternal(actor, actor_gc_scan); + JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT); + script_startup(actor); set_actor_state(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->id = strdup(id); actor->ar_secs = ar; - + if (shgeti(actors, id) != -1) { free(actor->id); 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) { - cell_rt *target = get_actor(id); + JSContext *target = get_actor(id); if (!target) { blob_destroy((blob *)msg); return "Could not get actor from id."; } - + letter l; l.type = LETTER_BLOB; l.blob_data = (blob *)msg; - + arrput(target->letters, l); if (target->ar) { // Invalidate unneeded timer target->ar = 0; } - + set_actor_state(target); - + return NULL; } -void actor_turn(cell_rt *actor) +void actor_turn(JSContext *actor) { - actor->state = ACTOR_RUNNING; + actor->state = ACTOR_RUNNING; JSValue result; - + TAKETURN: if (!arrlen(actor->letters)) { goto ENDTURN; } letter l = actor->letters[0]; - arrdel(actor->letters, 0); - - if (l.type == LETTER_BLOB) { + arrdel(actor->letters, 0); + + if (l.type == LETTER_BLOB) { // Create a JS blob from the C blob 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); - result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg); - uncaught_exception(actor->context, result); - JS_FreeValue(actor->context, arg); + result = JS_Call(actor, actor->message_handle_ref.val, JS_NULL, 1, &arg); + uncaught_exception(actor, result); + JS_FreeValue(actor, arg); } else if (l.type == LETTER_CALLBACK) { - result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL); - uncaught_exception(actor->context, result); - JS_FreeValue(actor->context, l.callback); + result = JS_Call(actor, l.callback, JS_NULL, 0, NULL); + uncaught_exception(actor, result); + JS_FreeValue(actor, l.callback); } - + if (actor->disrupt) goto ENDTURN; - + ENDTURN: - actor->state = ACTOR_IDLE; - + actor->state = ACTOR_IDLE; + set_actor_state(actor); } -void actor_clock(cell_rt *actor, JSValue fn) +void actor_clock(JSContext *actor, JSValue fn) { letter l; l.type = LETTER_CALLBACK; - l.callback = JS_DupValue(actor->context, fn); + l.callback = JS_DupValue(actor, fn); arrput(actor->letters, l); 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; uint32_t id = global_timer_id++; - - JSValue cb = JS_DupValue(actor->context, fn); + + JSValue cb = JS_DupValue(actor, fn); hmput(actor->timers, id, cb); - + uint64_t now = cell_ns(); 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; } -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; int id = hmgeti(actor->timers, timer_id); diff --git a/source/suite.c b/source/suite.c index 2e63c111..57551026 100644 --- a/source/suite.c +++ b/source/suite.c @@ -5,7 +5,7 @@ */ #include "cell.h" -#include "quickjs-internal.h" +#include "pit_internal.h" #include #include #include