Merge branch 'master' into fix_aot
This commit is contained in:
@@ -252,6 +252,7 @@ const char* cell_get_core_path(void) {
|
||||
void actor_disrupt(cell_rt *crt)
|
||||
{
|
||||
crt->disrupt = 1;
|
||||
JS_SetPauseFlag(crt->context, 2);
|
||||
if (crt->state != ACTOR_RUNNING)
|
||||
actor_free(crt);
|
||||
}
|
||||
@@ -265,11 +266,13 @@ void script_startup(cell_rt *prt)
|
||||
g_runtime = JS_NewRuntime();
|
||||
}
|
||||
JSContext *js = JS_NewContext(g_runtime);
|
||||
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
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);
|
||||
@@ -345,7 +348,8 @@ 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_NewString(js, "__ACTOR__");
|
||||
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));
|
||||
|
||||
@@ -444,6 +448,7 @@ static void print_usage(const char *prog)
|
||||
printf(" CELL_SHOP Shop path (default: ~/.cell)\n");
|
||||
printf("\nRecompile after changes: make\n");
|
||||
printf("Bootstrap from scratch: make bootstrap\n");
|
||||
printf("Run the 'help' script like 'cell help' to see available scripts\n");
|
||||
}
|
||||
|
||||
int cell_init(int argc, char **argv)
|
||||
@@ -554,7 +559,6 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
cli_rt->context = ctx;
|
||||
JS_SetContextOpaque(ctx, cli_rt);
|
||||
JS_SetInterruptHandler(ctx, (JSInterruptHandler *)actor_interrupt_cb, cli_rt);
|
||||
|
||||
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
|
||||
@@ -565,7 +569,8 @@ int cell_init(int argc, char **argv)
|
||||
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_NewString(ctx, "__ACTOR__");
|
||||
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;
|
||||
@@ -704,7 +709,6 @@ check_actors:
|
||||
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);
|
||||
JS_SetInterruptHandler(ctx, NULL, NULL);
|
||||
|
||||
pthread_mutex_destroy(cli_rt->mutex);
|
||||
free(cli_rt->mutex);
|
||||
@@ -757,11 +761,24 @@ void cell_trace_sethook(cell_hook)
|
||||
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
{
|
||||
(void)v;
|
||||
if (!JS_HasException(js))
|
||||
int has_exc = JS_HasException(js);
|
||||
int is_exc = JS_IsException(v);
|
||||
if (!has_exc && !is_exc)
|
||||
return 1;
|
||||
/* Error message and backtrace were already printed to stderr
|
||||
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
|
||||
JS_GetException(js);
|
||||
if (has_exc)
|
||||
JS_GetException(js);
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
if (crt && !JS_IsNull(crt->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);
|
||||
/* Clear any secondary exception from the callback. */
|
||||
if (JS_HasException(js))
|
||||
JS_GetException(js);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
/* Letter type for unified message queue */
|
||||
typedef enum {
|
||||
@@ -21,6 +22,14 @@ typedef struct letter {
|
||||
#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 (5000ULL * 1000000) // 5s 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;
|
||||
@@ -58,6 +67,11 @@ typedef struct cell_rt {
|
||||
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;
|
||||
@@ -74,17 +88,19 @@ 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);
|
||||
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt);
|
||||
void actor_loop();
|
||||
void actor_initialize(void);
|
||||
void actor_free(cell_rt *actor);
|
||||
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);
|
||||
|
||||
113
source/mach.c
113
source/mach.c
@@ -704,18 +704,6 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||
return JS_ThrowTypeError(ctx, "type mismatch in binary operation");
|
||||
}
|
||||
|
||||
/* Check for interrupt */
|
||||
int reg_vm_check_interrupt(JSContext *ctx) {
|
||||
if (--ctx->interrupt_counter <= 0) {
|
||||
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
|
||||
if (ctx->interrupt_handler) {
|
||||
if (ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_ASAN
|
||||
void __asan_on_error(void) {
|
||||
@@ -757,6 +745,31 @@ void __asan_on_error(void) {
|
||||
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
JSValue this_obj, int argc, JSValue *argv,
|
||||
JSValue env, JSValue outer_frame) {
|
||||
JSGCRef frame_ref;
|
||||
JSFrameRegister *frame;
|
||||
uint32_t pc;
|
||||
JSValue result;
|
||||
|
||||
/* Resume path: if VM was suspended, restore state and jump into dispatch */
|
||||
if (ctx->suspended) {
|
||||
ctx->suspended = 0;
|
||||
JS_AddGCRef(ctx, &frame_ref);
|
||||
frame_ref.val = ctx->suspended_frame_ref.val;
|
||||
ctx->suspended_frame_ref.val = JS_NULL;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
code = fn->u.reg.code;
|
||||
env = fn->u.reg.env_record;
|
||||
pc = ctx->suspended_pc;
|
||||
result = JS_NULL;
|
||||
#ifdef HAVE_ASAN
|
||||
__asan_js_ctx = ctx;
|
||||
#endif
|
||||
goto vm_dispatch;
|
||||
}
|
||||
|
||||
{
|
||||
/* Normal path: set up a new call */
|
||||
/* Protect env and outer_frame from GC — alloc_frame_register can trigger
|
||||
collection which moves heap objects, invalidating stack-local copies */
|
||||
JSGCRef env_gc, of_gc;
|
||||
@@ -778,7 +791,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
}
|
||||
|
||||
/* Allocate initial frame */
|
||||
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
|
||||
frame = alloc_frame_register(ctx, code->nr_slots);
|
||||
if (!frame) {
|
||||
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
|
||||
JS_PopGCRef(ctx, &this_gc);
|
||||
@@ -788,7 +801,6 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
}
|
||||
|
||||
/* Protect frame from GC */
|
||||
JSGCRef frame_ref;
|
||||
JS_AddGCRef(ctx, &frame_ref);
|
||||
frame_ref.val = JS_MKPTR(frame);
|
||||
#ifdef HAVE_ASAN
|
||||
@@ -812,9 +824,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
JS_PopGCRef(ctx, &of_gc);
|
||||
JS_PopGCRef(ctx, &env_gc);
|
||||
|
||||
uint32_t pc = code->entry_point;
|
||||
JSValue result = JS_NULL;
|
||||
pc = code->entry_point;
|
||||
result = JS_NULL;
|
||||
} /* end normal path block */
|
||||
|
||||
vm_dispatch:
|
||||
/* Execution loop — 32-bit instruction dispatch */
|
||||
for (;;) {
|
||||
#ifndef NDEBUG
|
||||
@@ -1405,9 +1419,18 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
VM_CASE(MACH_JMP): {
|
||||
int offset = MACH_GET_sJ(instr);
|
||||
pc = (uint32_t)((int32_t)pc + offset);
|
||||
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
if (offset < 0) {
|
||||
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
||||
if (pf == 2) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
}
|
||||
if (pf == 1) {
|
||||
if (ctx->vm_call_depth > 0)
|
||||
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
||||
else
|
||||
goto suspend;
|
||||
}
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
@@ -1421,9 +1444,18 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
if (cond) {
|
||||
int offset = MACH_GET_sBx(instr);
|
||||
pc = (uint32_t)((int32_t)pc + offset);
|
||||
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
if (offset < 0) {
|
||||
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
||||
if (pf == 2) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
}
|
||||
if (pf == 1) {
|
||||
if (ctx->vm_call_depth > 0)
|
||||
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
||||
else
|
||||
goto suspend;
|
||||
}
|
||||
}
|
||||
}
|
||||
VM_BREAK();
|
||||
@@ -1438,9 +1470,18 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
if (!cond) {
|
||||
int offset = MACH_GET_sBx(instr);
|
||||
pc = (uint32_t)((int32_t)pc + offset);
|
||||
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
if (offset < 0) {
|
||||
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
||||
if (pf == 2) {
|
||||
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||
goto done;
|
||||
}
|
||||
if (pf == 1) {
|
||||
if (ctx->vm_call_depth > 0)
|
||||
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
||||
else
|
||||
goto suspend;
|
||||
}
|
||||
}
|
||||
}
|
||||
VM_BREAK();
|
||||
@@ -1953,6 +1994,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
/* C, native, or bytecode function */
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
ctx->vm_call_depth++;
|
||||
JSValue ret;
|
||||
if (fn->kind == JS_FUNC_KIND_C)
|
||||
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||
@@ -1960,6 +2002,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||
else
|
||||
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
||||
ctx->vm_call_depth--;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) goto disrupt;
|
||||
@@ -2038,6 +2081,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
/* C, native, or bytecode function: call it, then return result to our caller */
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
ctx->vm_call_depth++;
|
||||
JSValue ret;
|
||||
if (fn->kind == JS_FUNC_KIND_C)
|
||||
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||
@@ -2045,6 +2089,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||
else
|
||||
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
||||
ctx->vm_call_depth--;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) goto disrupt;
|
||||
@@ -2174,6 +2219,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
}
|
||||
}
|
||||
|
||||
suspend:
|
||||
ctx->suspended = 1;
|
||||
ctx->suspended_pc = pc;
|
||||
ctx->suspended_frame_ref.val = frame_ref.val;
|
||||
result = JS_SUSPENDED;
|
||||
JS_DeleteGCRef(ctx, &frame_ref);
|
||||
#ifdef HAVE_ASAN
|
||||
__asan_js_ctx = NULL;
|
||||
#endif
|
||||
return result;
|
||||
|
||||
done:
|
||||
#ifdef HAVE_ASAN
|
||||
__asan_js_ctx = NULL;
|
||||
@@ -2187,6 +2243,13 @@ done:
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue JS_ResumeRegisterVM(JSContext *ctx) {
|
||||
if (!ctx->suspended)
|
||||
return JS_ThrowInternalError(ctx, "no suspended VM to resume");
|
||||
/* ctx->suspended is set; JS_CallRegisterVM will take the resume path */
|
||||
return JS_CallRegisterVM(ctx, NULL, JS_NULL, 0, NULL, JS_NULL, JS_NULL);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
MCODE Lowering — mcode JSON IR → MachInstr32
|
||||
============================================================ */
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#if defined(__APPLE__)
|
||||
@@ -898,12 +899,12 @@ typedef struct JSArray {
|
||||
JSValue values[]; /* inline flexible array member */
|
||||
} JSArray;
|
||||
|
||||
/* JSBlob — not allocated on GC heap (blobs use JSRecord + opaque).
|
||||
Struct kept for reference; gc_object_size/gc_scan_object do not handle OBJ_BLOB. */
|
||||
/* JSBlob — inline bit data on the GC heap.
|
||||
cap56 = capacity in bits, S bit = stone (immutable). */
|
||||
typedef struct JSBlob {
|
||||
objhdr_t mist_hdr;
|
||||
word_t length;
|
||||
uint8_t bits[];
|
||||
objhdr_t mist_hdr; /* type=OBJ_BLOB, cap56=capacity_bits, S=stone */
|
||||
word_t length; /* used bits */
|
||||
word_t bits[]; /* inline bit data, ceil(cap56/64) words */
|
||||
} JSBlob;
|
||||
|
||||
typedef struct JSText {
|
||||
@@ -1063,10 +1064,6 @@ static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) {
|
||||
|
||||
/* Forward declarations for stone arena functions (defined after JSContext) */
|
||||
|
||||
/* must be large enough to have a negligible runtime cost and small
|
||||
enough to call the interrupt callback often. */
|
||||
#define JS_INTERRUPT_COUNTER_INIT 10000
|
||||
|
||||
/* Auto-rooted C call argv — GC updates values in-place */
|
||||
typedef struct CCallRoot {
|
||||
JSValue *argv; /* points to C-stack-local array */
|
||||
@@ -1123,8 +1120,8 @@ struct JSContext {
|
||||
|
||||
uint64_t random_state;
|
||||
|
||||
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
|
||||
int interrupt_counter;
|
||||
/* 0 = normal, 1 = suspend (fast timer), 2 = kill (slow timer) */
|
||||
_Atomic int pause_flag;
|
||||
|
||||
/* if NULL, RegExp compilation is not supported */
|
||||
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
|
||||
@@ -1138,8 +1135,12 @@ struct JSContext {
|
||||
JSValue reg_current_frame; /* current JSFrameRegister being executed */
|
||||
uint32_t current_register_pc; /* PC at exception time */
|
||||
|
||||
JSInterruptHandler *interrupt_handler;
|
||||
void *interrupt_opaque;
|
||||
/* VM suspend/resume state */
|
||||
int suspended; /* 1 = VM was suspended (not exception) */
|
||||
JSGCRef suspended_frame_ref; /* GC-rooted saved frame for resume */
|
||||
uint32_t suspended_pc; /* saved PC for resume */
|
||||
int vm_call_depth; /* 0 = pure bytecode, >0 = C frames on stack */
|
||||
size_t heap_memory_limit; /* 0 = no limit, else max heap bytes */
|
||||
|
||||
JSValue current_exception;
|
||||
|
||||
@@ -1434,7 +1435,7 @@ static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int a
|
||||
static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
cJSON *JS_GetStack(JSContext *ctx);
|
||||
JSValue JS_GetStack(JSContext *ctx);
|
||||
JSValue JS_ThrowOutOfMemory (JSContext *ctx);
|
||||
|
||||
|
||||
@@ -1545,25 +1546,14 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
|
||||
|
||||
void JS_ThrowInterrupted (JSContext *ctx);
|
||||
|
||||
static no_inline __exception int __js_poll_interrupts (JSContext *ctx) {
|
||||
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
|
||||
if (ctx->interrupt_handler) {
|
||||
if (ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)) {
|
||||
JS_ThrowInterrupted (ctx);
|
||||
return -1;
|
||||
}
|
||||
static inline __exception int js_poll_interrupts (JSContext *ctx) {
|
||||
if (unlikely (atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2)) {
|
||||
JS_ThrowInterrupted (ctx);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline __exception int js_poll_interrupts (JSContext *ctx) {
|
||||
if (unlikely (--ctx->interrupt_counter <= 0)) {
|
||||
return __js_poll_interrupts (ctx);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */
|
||||
typedef struct PPretext {
|
||||
uint32_t *data;
|
||||
@@ -1613,7 +1603,7 @@ uint64_t get_text_hash (JSText *text);
|
||||
void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed);
|
||||
int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b);
|
||||
|
||||
void print_backtrace (JSContext *ctx, const char *filename, int line_num, int col_num);
|
||||
void print_backtrace (JSContext *ctx, JSValue stack, const char *filename, int line_num, int col_num);
|
||||
JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace);
|
||||
JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
|
||||
PPretext *ppretext_init (int capacity);
|
||||
@@ -1662,7 +1652,6 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
|
||||
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
||||
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame);
|
||||
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
|
||||
int reg_vm_check_interrupt(JSContext *ctx);
|
||||
|
||||
|
||||
#endif /* QUICKJS_INTERNAL_H */
|
||||
|
||||
@@ -294,6 +294,12 @@ JS_IsShortFloat (JSValue v) {
|
||||
#define JS_FALSE ((JSValue)JS_TAG_BOOL)
|
||||
#define JS_TRUE ((JSValue)(JS_TAG_BOOL | (1 << 5)))
|
||||
#define JS_EXCEPTION ((JSValue)JS_TAG_EXCEPTION)
|
||||
#define JS_TAG_SUSPENDED 0x13 /* 10011 - distinct special tag */
|
||||
#define JS_SUSPENDED ((JSValue)JS_TAG_SUSPENDED)
|
||||
|
||||
static inline JS_BOOL JS_IsSuspended(JSValue v) {
|
||||
return JS_VALUE_GET_TAG(v) == JS_TAG_SUSPENDED;
|
||||
}
|
||||
|
||||
#ifndef JS_DEFAULT_STACK_SIZE
|
||||
#define JS_DEFAULT_STACK_SIZE (1024 * 1024)
|
||||
@@ -333,10 +339,14 @@ void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
|
||||
used to check stack overflow. */
|
||||
void JS_UpdateStackTop (JSContext *ctx);
|
||||
|
||||
/* return != 0 if the JS code needs to be interrupted */
|
||||
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
|
||||
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
|
||||
void *opaque);
|
||||
/* Returns the current VM call depth (0 = pure bytecode, >0 = C frames) */
|
||||
int JS_GetVMCallDepth(JSContext *ctx);
|
||||
|
||||
/* Set per-context heap memory limit (0 = no limit) */
|
||||
void JS_SetHeapMemoryLimit(JSContext *ctx, size_t limit);
|
||||
|
||||
/* Set the pause flag on a context (0=normal, 1=suspend, 2=kill) */
|
||||
void JS_SetPauseFlag(JSContext *ctx, int value);
|
||||
|
||||
JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj);
|
||||
|
||||
@@ -1037,10 +1047,10 @@ void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue en
|
||||
/* Compile mcode JSON IR to MachCode binary. */
|
||||
MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
||||
|
||||
/* Get stack trace as cJSON array of frame objects.
|
||||
Returns NULL if no register VM frame is active.
|
||||
Caller must call cJSON_Delete() on the result. */
|
||||
struct cJSON *JS_GetStack (JSContext *ctx);
|
||||
/* Get stack trace as JS array of {fn, file, line, col} objects.
|
||||
Returns an empty array if no register VM frame is active.
|
||||
Does NOT clear reg_current_frame — caller is responsible if needed. */
|
||||
JSValue JS_GetStack (JSContext *ctx);
|
||||
|
||||
#undef js_unlikely
|
||||
#undef inline
|
||||
|
||||
900
source/runtime.c
900
source/runtime.c
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,9 @@ typedef struct actor_node {
|
||||
|
||||
typedef enum {
|
||||
TIMER_JS,
|
||||
TIMER_NATIVE_REMOVE
|
||||
TIMER_NATIVE_REMOVE,
|
||||
TIMER_PAUSE,
|
||||
TIMER_KILL
|
||||
} timer_type;
|
||||
|
||||
typedef struct {
|
||||
@@ -30,23 +32,28 @@ typedef struct {
|
||||
cell_rt *actor;
|
||||
uint32_t timer_id;
|
||||
timer_type type;
|
||||
uint32_t turn_gen; /* generation at registration time */
|
||||
} timer_node;
|
||||
|
||||
static timer_node *timer_heap = NULL;
|
||||
|
||||
// Priority queue indices
|
||||
#define PQ_READY 0
|
||||
#define PQ_REFRESHED 1
|
||||
#define PQ_EXHAUSTED 2
|
||||
#define PQ_SLOW 3
|
||||
#define PQ_COUNT 4
|
||||
|
||||
// --- 3. The Global Engine State ---
|
||||
static struct {
|
||||
pthread_mutex_t lock; // Protects queue, shutdown flag, and timers
|
||||
pthread_cond_t wake_cond; // Wakes up workers
|
||||
pthread_cond_t timer_cond; // Wakes up the timer thread
|
||||
pthread_cond_t main_cond; // Wakes up the main thread
|
||||
|
||||
actor_node *head; // Ready Queue Head
|
||||
actor_node *tail; // Ready Queue Tail
|
||||
|
||||
actor_node *main_head; // Main Thread Queue Head
|
||||
actor_node *main_tail; // Main Thread Queue Tail
|
||||
|
||||
|
||||
actor_node *q_head[PQ_COUNT], *q_tail[PQ_COUNT]; // worker queues
|
||||
actor_node *mq_head[PQ_COUNT], *mq_tail[PQ_COUNT]; // main-thread queues
|
||||
|
||||
int shutting_down;
|
||||
int quiescence_enabled; // set after bootstrap, before actor_loop
|
||||
_Atomic int quiescent_count; // actors idle with no messages and no timers
|
||||
@@ -56,6 +63,33 @@ static struct {
|
||||
pthread_t timer_thread;
|
||||
} engine;
|
||||
|
||||
static int state_to_pq(int state) {
|
||||
switch (state) {
|
||||
case ACTOR_REFRESHED: return PQ_REFRESHED;
|
||||
case ACTOR_EXHAUSTED: return PQ_EXHAUSTED;
|
||||
case ACTOR_SLOW: return PQ_SLOW;
|
||||
default: return PQ_READY;
|
||||
}
|
||||
}
|
||||
|
||||
static actor_node *dequeue_priority(actor_node *heads[], actor_node *tails[]) {
|
||||
for (int i = 0; i < PQ_COUNT; i++) {
|
||||
if (heads[i]) {
|
||||
actor_node *n = heads[i];
|
||||
heads[i] = n->next;
|
||||
if (!heads[i]) tails[i] = NULL;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int has_any_work(actor_node *heads[]) {
|
||||
for (int i = 0; i < PQ_COUNT; i++)
|
||||
if (heads[i]) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pthread_mutex_t *actors_mutex;
|
||||
static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||
|
||||
@@ -91,7 +125,9 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
|
||||
void actor_turn(cell_rt *actor);
|
||||
|
||||
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) {
|
||||
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .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
|
||||
@@ -161,8 +197,19 @@ void *timer_thread_func(void *arg) {
|
||||
|
||||
if (t.type == TIMER_NATIVE_REMOVE) {
|
||||
actor_remove_cb(t.actor, t.timer_id, 0);
|
||||
} else if (t.type == TIMER_PAUSE || t.type == TIMER_KILL) {
|
||||
/* Only fire if turn_gen still matches (stale timers are ignored) */
|
||||
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);
|
||||
} else {
|
||||
t.actor->disrupt = 1;
|
||||
JS_SetPauseFlag(t.actor->context, 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pthread_mutex_lock(t.actor->msg_mutex);
|
||||
pthread_mutex_lock(t.actor->msg_mutex);
|
||||
int idx = hmgeti(t.actor->timers, t.timer_id);
|
||||
if (idx != -1) {
|
||||
JSValue cb = t.actor->timers[idx].value;
|
||||
@@ -196,33 +243,48 @@ void *timer_thread_func(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void enqueue_actor_priority(cell_rt *actor) {
|
||||
actor_node *n = malloc(sizeof(actor_node));
|
||||
n->actor = actor;
|
||||
n->next = NULL;
|
||||
int pq = state_to_pq(actor->state);
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
if (actor->main_thread_only) {
|
||||
if (engine.mq_tail[pq]) engine.mq_tail[pq]->next = n;
|
||||
else engine.mq_head[pq] = n;
|
||||
engine.mq_tail[pq] = n;
|
||||
pthread_cond_signal(&engine.main_cond);
|
||||
} else {
|
||||
if (engine.q_tail[pq]) engine.q_tail[pq]->next = n;
|
||||
else engine.q_head[pq] = n;
|
||||
engine.q_tail[pq] = n;
|
||||
pthread_cond_signal(&engine.wake_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
}
|
||||
|
||||
void *actor_runner(void *arg) {
|
||||
while (1) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
|
||||
// Wait while queue is empty AND not shutting down
|
||||
while (engine.head == NULL && !engine.shutting_down) {
|
||||
pthread_cond_wait(&engine.wake_cond, &engine.lock);
|
||||
}
|
||||
while (1) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
|
||||
if (engine.shutting_down && engine.head == NULL) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break; // Exit thread
|
||||
}
|
||||
|
||||
// Pop from Linked List
|
||||
actor_node *node = engine.head;
|
||||
engine.head = node->next;
|
||||
if (engine.head == NULL) engine.tail = NULL;
|
||||
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (node) {
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
}
|
||||
while (!has_any_work(engine.q_head) && !engine.shutting_down) {
|
||||
pthread_cond_wait(&engine.wake_cond, &engine.lock);
|
||||
}
|
||||
return NULL;
|
||||
|
||||
if (engine.shutting_down && !has_any_work(engine.q_head)) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break;
|
||||
}
|
||||
|
||||
actor_node *node = dequeue_priority(engine.q_head, engine.q_tail);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (node) {
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void actor_initialize(void) {
|
||||
@@ -230,12 +292,14 @@ void actor_initialize(void) {
|
||||
pthread_cond_init(&engine.wake_cond, NULL);
|
||||
pthread_cond_init(&engine.timer_cond, NULL);
|
||||
pthread_cond_init(&engine.main_cond, NULL);
|
||||
|
||||
|
||||
engine.shutting_down = 0;
|
||||
engine.head = NULL;
|
||||
engine.tail = NULL;
|
||||
engine.main_head = NULL;
|
||||
engine.main_tail = NULL;
|
||||
for (int i = 0; i < PQ_COUNT; i++) {
|
||||
engine.q_head[i] = NULL;
|
||||
engine.q_tail[i] = NULL;
|
||||
engine.mq_head[i] = NULL;
|
||||
engine.mq_tail[i] = NULL;
|
||||
}
|
||||
|
||||
actors_mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(actors_mutex, NULL);
|
||||
@@ -295,7 +359,6 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_SetInterruptHandler(js, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
free(actor->id);
|
||||
|
||||
@@ -406,30 +469,7 @@ void set_actor_state(cell_rt *actor)
|
||||
#endif
|
||||
actor->state = ACTOR_READY;
|
||||
actor->ar = 0;
|
||||
|
||||
actor_node *n = malloc(sizeof(actor_node));
|
||||
n->actor = actor;
|
||||
n->next = NULL;
|
||||
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
if (actor->main_thread_only) {
|
||||
if (engine.main_tail) {
|
||||
engine.main_tail->next = n;
|
||||
} else {
|
||||
engine.main_head = n;
|
||||
}
|
||||
engine.main_tail = n;
|
||||
pthread_cond_signal(&engine.main_cond);
|
||||
} else {
|
||||
if (engine.tail) {
|
||||
engine.tail->next = n;
|
||||
} else {
|
||||
engine.head = n;
|
||||
}
|
||||
engine.tail = n;
|
||||
pthread_cond_signal(&engine.wake_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
enqueue_actor_priority(actor);
|
||||
|
||||
} else if (!hmlen(actor->timers)) {
|
||||
// No messages AND no timers
|
||||
@@ -531,26 +571,23 @@ cell_rt *get_actor(char *id)
|
||||
|
||||
void actor_loop()
|
||||
{
|
||||
while (!engine.shutting_down) { // Direct read safe enough here or use lock
|
||||
while (!engine.shutting_down) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
while (engine.main_head == NULL && !engine.shutting_down) {
|
||||
while (!has_any_work(engine.mq_head) && !engine.shutting_down) {
|
||||
pthread_cond_wait(&engine.main_cond, &engine.lock);
|
||||
}
|
||||
|
||||
if (engine.shutting_down && engine.main_head == NULL) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break;
|
||||
|
||||
if (engine.shutting_down && !has_any_work(engine.mq_head)) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break;
|
||||
}
|
||||
|
||||
actor_node *node = engine.main_head;
|
||||
engine.main_head = node->next;
|
||||
if (engine.main_head == NULL) engine.main_tail = NULL;
|
||||
|
||||
actor_node *node = dequeue_priority(engine.mq_head, engine.mq_tail);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (node) {
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,10 +643,6 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
|
||||
{
|
||||
return engine.shutting_down || crt->disrupt;
|
||||
}
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
@@ -638,58 +671,131 @@ const char *send_message(const char *id, void *msg)
|
||||
void actor_turn(cell_rt *actor)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
|
||||
int prev_state = actor->state;
|
||||
actor->state = ACTOR_RUNNING;
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: %d -> RUNNING\n",
|
||||
actor->name ? actor->name : actor->id, prev_state);
|
||||
#endif
|
||||
|
||||
if (actor->trace_hook)
|
||||
actor->trace_hook(actor->name, CELL_HOOK_ENTER);
|
||||
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
JSValue result;
|
||||
|
||||
if (actor->vm_suspended) {
|
||||
/* RESUME path: continue suspended turn under kill timer only */
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
|
||||
/* Register kill timer only for resume */
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
heap_push(actor->turn_start_ns + ACTOR_SLOW_TIMER_NS,
|
||||
actor, 0, TIMER_KILL);
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
result = JS_ResumeRegisterVM(actor->context);
|
||||
actor->vm_suspended = 0;
|
||||
|
||||
if (JS_IsSuspended(result)) {
|
||||
/* Still suspended after kill timer — shouldn't happen, kill it */
|
||||
actor->disrupt = 1;
|
||||
goto ENDTURN;
|
||||
}
|
||||
if (JS_IsException(result)) {
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
actor->disrupt = 1;
|
||||
}
|
||||
actor->slow_strikes++;
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: slow strike %d/%d\n",
|
||||
actor->name ? actor->name : actor->id,
|
||||
actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX);
|
||||
#endif
|
||||
if (actor->slow_strikes >= ACTOR_SLOW_STRIKES_MAX) {
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: %d slow strikes, killing\n",
|
||||
actor->name ? actor->name : actor->id, actor->slow_strikes);
|
||||
#endif
|
||||
actor->disrupt = 1;
|
||||
}
|
||||
goto ENDTURN;
|
||||
}
|
||||
|
||||
/* NORMAL path: pop a message and execute */
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
int pending = arrlen(actor->letters);
|
||||
if (!pending) {
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
goto ENDTURN;
|
||||
}
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
fprintf(stderr, "actor_turn: %s has %d letters, type=%d\n", actor->name ? actor->name : actor->id, pending, actor->letters[0].type);
|
||||
fprintf(stderr, "actor_turn: %s has %d letters, type=%d\n",
|
||||
actor->name ? actor->name : actor->id, pending, actor->letters[0].type);
|
||||
#endif
|
||||
letter l = actor->letters[0];
|
||||
arrdel(actor->letters, 0);
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
|
||||
/* Register both pause and kill timers */
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
heap_push(actor->turn_start_ns + ACTOR_FAST_TIMER_NS,
|
||||
actor, 0, TIMER_PAUSE);
|
||||
heap_push(actor->turn_start_ns + ACTOR_SLOW_TIMER_NS + ACTOR_FAST_TIMER_NS,
|
||||
actor, 0, TIMER_KILL);
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
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
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
fprintf(stderr, "actor_turn BLOB: %s size=%zu, calling message_handle\n", actor->name ? actor->name : actor->id, size);
|
||||
#endif
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
|
||||
size_t size = blob_length(l.blob_data) / 8;
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
||||
(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);
|
||||
result = JS_Call(actor->context, 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);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor->context, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL);
|
||||
if (JS_IsSuspended(result)) {
|
||||
actor->vm_suspended = 1;
|
||||
actor->state = ACTOR_SLOW;
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
actor->slow_strikes = 0; /* completed within fast timer */
|
||||
|
||||
ENDTURN:
|
||||
ENDTURN:
|
||||
/* Invalidate any outstanding pause/kill timers for this turn */
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
if (actor->trace_hook)
|
||||
actor->trace_hook(actor->name, CELL_HOOK_EXIT);
|
||||
|
||||
if (actor->disrupt) {
|
||||
/* Actor must die. Unlock before freeing so actor_free can
|
||||
lock/unlock/destroy the mutex without use-after-free. */
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
fprintf(stderr, "actor_turn ENDTURN: %s disrupted, freeing\n", actor->name ? actor->name : actor->id);
|
||||
fprintf(stderr, "actor_turn ENDTURN: %s disrupted, freeing\n",
|
||||
actor->name ? actor->name : actor->id);
|
||||
#endif
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
actor_free(actor);
|
||||
@@ -697,10 +803,21 @@ void actor_turn(cell_rt *actor)
|
||||
}
|
||||
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
fprintf(stderr, "actor_turn ENDTURN: %s has %ld letters, calling set_actor_state\n", actor->name ? actor->name : actor->id, (long)arrlen(actor->letters));
|
||||
fprintf(stderr, "actor_turn ENDTURN: %s has %ld letters, calling set_actor_state\n",
|
||||
actor->name ? actor->name : actor->id, (long)arrlen(actor->letters));
|
||||
#endif
|
||||
set_actor_state(actor);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
return;
|
||||
|
||||
ENDTURN_SLOW:
|
||||
#ifdef ACTOR_TRACE
|
||||
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->name, CELL_HOOK_EXIT);
|
||||
enqueue_actor_priority(actor);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,6 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_SetInterruptHandler(js, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
free(actor->id);
|
||||
|
||||
@@ -353,10 +352,6 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
|
||||
{
|
||||
return shutting_down || crt->disrupt;
|
||||
}
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user