diff --git a/packages/runtime/src/scr_node_island.c b/packages/runtime/src/scr_node_island.c index 1a6ce307f..c3104393f 100644 --- a/packages/runtime/src/scr_node_island.c +++ b/packages/runtime/src/scr_node_island.c @@ -499,13 +499,27 @@ static const ScrNodeBridgeHost ni_host = { &scr_ni_host_shared_tdz, }; +typedef struct { + uint32_t event, listener; + const char *name; + int arg; +} NiEmit; + +static void ni_emit_job(void *p) { + NiEmit *e = p; + ni_bridge->emit(e->event, e->listener, e->name, e->arg); +} + +/* An island listener of a process event, on Node's stack (an exit listener + * runs from the runtime's exit sequence, on whatever stack called it). */ void scr_node_island_emit(uint32_t event, uint32_t listener, ScrStr *name, int arg) { if (ni_bridge == NULL) return; char *n = malloc(name->len + 1); if (n == NULL) ni_oom(); memcpy(n, name->data, name->len); n[name->len] = '\0'; - ni_bridge->emit(event, listener, n, arg); + NiEmit e = {event, listener, n, arg}; + ni_on_node_stack(ni_emit_job, &e); free(n); } @@ -548,7 +562,75 @@ static int ni_run_loop(int continue_turn) { return a.code; } -/* The bridge as the hosted loop sees it: Node's loop runs on Node's stack. */ +/* The bridge as the hosted loop sees it. Every entry that runs JavaScript + * (scheduling a tick, timer or immediate goes through the bootstrap's + * helpers; a checkpoint drains queues; Node's loop) runs on Node's stack, + * below the mark when it comes from an async fiber that a host callback + * resumed, like every other entry: V8 scans only Node's stack, so no frame + * of Node may live on a fiber's stack or the main thread's. */ +typedef struct { + int op; + uint32_t a, b; + double d0, d1; + int i0; + uint32_t result; +} NiHosted; + +enum { + NI_H_MICROTASK, + NI_H_TICK, + NI_H_TIMER, + NI_H_TIMER_CLEAR, + NI_H_TIMER_REF, + NI_H_TIMER_REFRESH, + NI_H_IMMEDIATE, + NI_H_IMMEDIATE_CLEAR, + NI_H_IMMEDIATE_REF, + NI_H_CHECKPOINT, +}; + +static void ni_hosted_job(void *p) { + NiHosted *h = p; + switch (h->op) { + case NI_H_MICROTASK: ni_bridge->microtask(h->a); break; + case NI_H_TICK: ni_bridge->tick(h->a); break; + case NI_H_TIMER: h->result = ni_bridge->timer(h->d0, h->d1, h->i0, h->a); break; + case NI_H_TIMER_CLEAR: ni_bridge->timer_clear(h->a); break; + case NI_H_TIMER_REF: ni_bridge->timer_ref(h->a, h->i0); break; + case NI_H_TIMER_REFRESH: ni_bridge->timer_refresh(h->a); break; + case NI_H_IMMEDIATE: h->result = ni_bridge->immediate(h->i0, h->a); break; + case NI_H_IMMEDIATE_CLEAR: ni_bridge->immediate_clear(h->a); break; + case NI_H_IMMEDIATE_REF: ni_bridge->immediate_ref(h->a, h->i0); break; + case NI_H_CHECKPOINT: ni_bridge->checkpoint(h->i0); break; + default: break; + } +} + +static uint32_t ni_hosted_run(int op, uint32_t a, double d0, double d1, int i0) { + NiHosted h = {op, a, 0, d0, d1, i0, 0}; + ni_on_node_stack(ni_hosted_job, &h); + return h.result; +} + +static void ni_h_microtask(uint32_t token) { ni_hosted_run(NI_H_MICROTASK, token, 0, 0, 0); } +static void ni_h_tick(uint32_t token) { ni_hosted_run(NI_H_TICK, token, 0, 0, 0); } +static uint32_t ni_h_timer(double delay, double repeat, int ref, uint32_t token) { + return ni_hosted_run(NI_H_TIMER, token, delay, repeat, ref); +} +static void ni_h_timer_clear(uint32_t h) { ni_hosted_run(NI_H_TIMER_CLEAR, h, 0, 0, 0); } +static void ni_h_timer_ref(uint32_t h, int ref) { ni_hosted_run(NI_H_TIMER_REF, h, 0, 0, ref); } +static void ni_h_timer_refresh(uint32_t h) { ni_hosted_run(NI_H_TIMER_REFRESH, h, 0, 0, 0); } +static uint32_t ni_h_immediate(int ref, uint32_t token) { + return ni_hosted_run(NI_H_IMMEDIATE, token, 0, 0, ref); +} +static void ni_h_immediate_clear(uint32_t h) { ni_hosted_run(NI_H_IMMEDIATE_CLEAR, h, 0, 0, 0); } +static void ni_h_immediate_ref(uint32_t h, int ref) { + ni_hosted_run(NI_H_IMMEDIATE_REF, h, 0, 0, ref); +} +static void ni_h_checkpoint(int microtasks_first) { + ni_hosted_run(NI_H_CHECKPOINT, 0, 0, 0, microtasks_first); +} + static ScrNodeBridge ni_hosted; /* ── loading Node ─────────────────────────────────────────────────────── */ @@ -622,6 +704,16 @@ static void ni_start(void) { scr_atexit(ni_stop_atexit); ni_hosted = *bridge; ni_hosted.run_loop = &ni_run_loop; + ni_hosted.microtask = &ni_h_microtask; + ni_hosted.tick = &ni_h_tick; + ni_hosted.timer = &ni_h_timer; + ni_hosted.timer_clear = &ni_h_timer_clear; + ni_hosted.timer_ref = &ni_h_timer_ref; + ni_hosted.timer_refresh = &ni_h_timer_refresh; + ni_hosted.immediate = &ni_h_immediate; + ni_hosted.immediate_clear = &ni_h_immediate_clear; + ni_hosted.immediate_ref = &ni_h_immediate_ref; + ni_hosted.checkpoint = &ni_h_checkpoint; scr_loop_host(&ni_hosted, since); /* NI-7: deferred island modules evaluate now, before the operation that * needed Node, in their import order. */