@scriptc/runtime 0.0.31 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/scr_async.c +54 -5
- package/src/scr_bytes.c +17 -0
- package/src/scr_ffi.c +139 -0
- package/src/scr_ffi_queue.c +379 -0
- package/src/scr_musl.c +170 -6
- package/src/scr_runtime.h +117 -10
- package/src/scr_tls.c +23 -10
- package/src/scr_tls_ca.c +133 -15
- package/vendor/README.md +1 -1
package/package.json
CHANGED
package/src/scr_async.c
CHANGED
|
@@ -2198,6 +2198,22 @@ void scr_loop_set_watch(bool (*pending)(void), void (*dispatch)(void), int (*pol
|
|
|
2198
2198
|
scr_watch_pollfd_fn = pollfd;
|
|
2199
2199
|
}
|
|
2200
2200
|
|
|
2201
|
+
/* The foreign-FFI queue hook (scr_ffi.c when a format-5 descriptor exists).
|
|
2202
|
+
* Its pending count includes live registrations (reffed by default) and
|
|
2203
|
+
* queued deliveries; dispatch runs exactly one callback per loop turn. */
|
|
2204
|
+
static bool (*scr_ffi_pending_fn)(void) = NULL;
|
|
2205
|
+
static bool (*scr_ffi_dispatch_fn)(void) = NULL;
|
|
2206
|
+
static int (*scr_ffi_pollfd_fn)(void) = NULL;
|
|
2207
|
+
static void (*scr_ffi_stop_fn)(void) = NULL;
|
|
2208
|
+
|
|
2209
|
+
void scr_loop_set_ffi(bool (*pending)(void), bool (*dispatch)(void),
|
|
2210
|
+
int (*pollfd)(void), void (*stop)(void)) {
|
|
2211
|
+
scr_ffi_pending_fn = pending;
|
|
2212
|
+
scr_ffi_dispatch_fn = dispatch;
|
|
2213
|
+
scr_ffi_pollfd_fn = pollfd;
|
|
2214
|
+
scr_ffi_stop_fn = stop;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2201
2217
|
/* The stream hook (scr_stream.c, when linked): `pending` keeps the loop
|
|
2202
2218
|
* alive while deferred stream ticks exist, `dispatch` drains them at the
|
|
2203
2219
|
* TOP of every turn — before the events/net stations, the closest
|
|
@@ -2295,6 +2311,12 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2295
2311
|
if (scr_exc_pending()) return false;
|
|
2296
2312
|
if (dispatched) continue;
|
|
2297
2313
|
}
|
|
2314
|
+
/* Foreign native callbacks are macrotasks. Deliver one, then restart at
|
|
2315
|
+
* the microtask checkpoint before considering the next queued post. */
|
|
2316
|
+
if (scr_ffi_dispatch_fn != NULL && scr_ffi_dispatch_fn()) {
|
|
2317
|
+
if (scr_exc_pending()) return false;
|
|
2318
|
+
if (scr_ready_len > 0 || scr_nt_head != NULL || scr_nunhandled > 0) continue;
|
|
2319
|
+
}
|
|
2298
2320
|
/* Stream tick dispatch (scr_stream.c, when linked): the deferred
|
|
2299
2321
|
* next-tick emissions ('data' flow kicks, 'readable'/'end'/'finish'/
|
|
2300
2322
|
* 'drain'/'error'/'close') fire now, FIRST — the nextTick station.
|
|
@@ -2363,6 +2385,7 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2363
2385
|
(scr_net_pending_fn != NULL && scr_net_pending_fn()) ||
|
|
2364
2386
|
(scr_dgram_pending_fn != NULL && scr_dgram_pending_fn()) ||
|
|
2365
2387
|
(scr_watch_pending_fn != NULL && scr_watch_pending_fn()) ||
|
|
2388
|
+
(scr_ffi_pending_fn != NULL && scr_ffi_pending_fn()) ||
|
|
2366
2389
|
scr_fs_renames_pending();
|
|
2367
2390
|
if (held) {
|
|
2368
2391
|
scr_children_poll();
|
|
@@ -2382,6 +2405,7 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2382
2405
|
bool net = scr_net_pending_fn != NULL && scr_net_pending_fn();
|
|
2383
2406
|
bool dgram = scr_dgram_pending_fn != NULL && scr_dgram_pending_fn();
|
|
2384
2407
|
bool watch = scr_watch_pending_fn != NULL && scr_watch_pending_fn();
|
|
2408
|
+
bool ffi = scr_ffi_pending_fn != NULL && scr_ffi_pending_fn();
|
|
2385
2409
|
bool renames = scr_fs_renames_pending();
|
|
2386
2410
|
/* Timer liveness counts only REF'd timers: an unref'd timer stays in
|
|
2387
2411
|
* the heap (and fires if the loop runs on for other reasons) but does
|
|
@@ -2389,7 +2413,7 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2389
2413
|
* Children follow the same rule: an unref'd child is still REAPED
|
|
2390
2414
|
* while the loop runs (kids drives the sweeps and sleeps above) but
|
|
2391
2415
|
* only reffed ones keep the process alive. */
|
|
2392
|
-
if (scr_reffed_timers == 0 && scr_reffed_immediates == 0 && !scr_children_reffed_pending() && !io && !events && !net && !dgram && !watch && !renames) break;
|
|
2416
|
+
if (scr_reffed_timers == 0 && scr_reffed_immediates == 0 && !scr_children_reffed_pending() && !io && !events && !net && !dgram && !watch && !ffi && !renames) break;
|
|
2393
2417
|
/* Sleep to the earliest deadline, then run every due timer (each may
|
|
2394
2418
|
* enqueue microtasks, which the next iteration drains first). Who
|
|
2395
2419
|
* sleeps depends on what is pending:
|
|
@@ -2430,11 +2454,11 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2430
2454
|
* on EINTR), so they re-impose a coarser cap — bounded Ctrl-C and
|
|
2431
2455
|
* socket latency during a fetch, without the reap-granularity
|
|
2432
2456
|
* cost. */
|
|
2433
|
-
else if ((evw || net || dgram || watch) && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
|
2457
|
+
else if ((evw || net || dgram || watch || ffi) && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
|
2434
2458
|
scr_io_poll_fn(due > now ? due - now : 0);
|
|
2435
2459
|
now = scr_now_ms();
|
|
2436
2460
|
if (scr_ready_len > 0) continue; /* io callbacks woke fibers */
|
|
2437
|
-
} else if (evw || net || dgram || watch) {
|
|
2461
|
+
} else if (evw || net || dgram || watch || ffi) {
|
|
2438
2462
|
#if defined(_WIN32) || defined(__wasi__)
|
|
2439
2463
|
/* The win32 arm, and WASI hosts whose poll_oneoff adapters do not
|
|
2440
2464
|
* reliably wake for a closed inherited stdin pipe: the sleep is a capped nanosleep and
|
|
@@ -2449,6 +2473,7 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2449
2473
|
* WaitForMultipleObjects over WSAEVENTs, or IOCP. */
|
|
2450
2474
|
if (evw && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
|
2451
2475
|
if ((net || dgram || watch) && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
2476
|
+
if (ffi && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
2452
2477
|
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
2453
2478
|
if (due > now) {
|
|
2454
2479
|
double wait = due - now;
|
|
@@ -2465,7 +2490,7 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2465
2490
|
* events are pending); unrepresentable children keep the ~1ms reap cap
|
|
2466
2491
|
* instead. Dispatch happens at the next turn's top — the poll only
|
|
2467
2492
|
* decides how long to sleep. */
|
|
2468
|
-
struct pollfd fds[
|
|
2493
|
+
struct pollfd fds[7];
|
|
2469
2494
|
int nfds = 0;
|
|
2470
2495
|
int evfds[2];
|
|
2471
2496
|
int nev = evw && scr_events_pollfds_fn != NULL ? scr_events_pollfds_fn(evfds) : 0;
|
|
@@ -2510,6 +2535,16 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2510
2535
|
due = now + SCR_SIGNAL_POLL_MS;
|
|
2511
2536
|
}
|
|
2512
2537
|
}
|
|
2538
|
+
if (ffi) {
|
|
2539
|
+
int ffd = scr_ffi_pollfd_fn != NULL ? scr_ffi_pollfd_fn() : -1;
|
|
2540
|
+
if (ffd >= 0) {
|
|
2541
|
+
fds[nfds].fd = ffd;
|
|
2542
|
+
fds[nfds].events = POLLIN;
|
|
2543
|
+
fds[nfds++].revents = 0;
|
|
2544
|
+
} else if (due > now + SCR_CHILD_POLL_MS) {
|
|
2545
|
+
due = now + SCR_CHILD_POLL_MS;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2513
2548
|
if (kids) {
|
|
2514
2549
|
int cfd = scr_children_wake_fd();
|
|
2515
2550
|
if (cfd >= 0) {
|
|
@@ -2618,7 +2653,21 @@ bool scr_loop_run(ScrPromise *top_level) {
|
|
|
2618
2653
|
* them as leaks. Uncaught callback throws still return above for main
|
|
2619
2654
|
* to report through the existing exceptional teardown path. */
|
|
2620
2655
|
scr_timers_teardown();
|
|
2621
|
-
/*
|
|
2656
|
+
/* No reffed foreign registration or queued invocation remains at ordinary
|
|
2657
|
+
* exhaustion. Disarm posting before exit listeners/global teardown so a
|
|
2658
|
+
* straggling native thread becomes a safe silent drop. */
|
|
2659
|
+
if (scr_ffi_stop_fn != NULL) scr_ffi_stop_fn();
|
|
2660
|
+
/* Retained native FFI registrations are deliberately NOT torn down
|
|
2661
|
+
* here: process 'exit' listeners run after the loop returns (inline in
|
|
2662
|
+
* main, before any atexit handler) and may legitimately release a
|
|
2663
|
+
* registration or pump a native library one last time — exactly like
|
|
2664
|
+
* the process.exit() path, where the ledger is also still intact. On
|
|
2665
|
+
* returns that reach atexit, the sweep scr_ffi_retain registered (LIFO
|
|
2666
|
+
* before the RC audit) then drops the ledger's references. It does NOT
|
|
2667
|
+
* run on process.exit(): scr_process_exit ends in _Exit, skipping every
|
|
2668
|
+
* atexit handler — the sweep and the RC audit alike — and leaves the
|
|
2669
|
+
* ledger to the OS. */
|
|
2670
|
+
/* Unref'd children the loop never reaped: release the
|
|
2622
2671
|
* registry's references (their listeners never fire — the process is
|
|
2623
2672
|
* exiting, Node's behavior; the OS reparents the children). */
|
|
2624
2673
|
scr_children_teardown();
|
package/src/scr_bytes.c
CHANGED
|
@@ -63,6 +63,15 @@ ScrBytes *scr_bytes_new(ScrBytesElem elem, double n) {
|
|
|
63
63
|
return scr_bytes_alloc(elem, (size_t)t);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
ScrBytes *scr_bytes_from_data(const uint8_t *data, size_t len) {
|
|
67
|
+
if (data == NULL && len != 0) {
|
|
68
|
+
scr_trap("scriptc: native callback passed a NULL span with nonzero length\n");
|
|
69
|
+
}
|
|
70
|
+
ScrBytes *b = scr_bytes_alloc(SCR_BYTES_U8, len);
|
|
71
|
+
if (len != 0) memcpy(b->data, data, len);
|
|
72
|
+
return b;
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
ScrBytes *scr_bytes_copy(const ScrBytes *src) {
|
|
67
76
|
ScrBytes *b = scr_bytes_alloc(src->elem, src->len);
|
|
68
77
|
memcpy(b->data, src->data, src->len * scr_bytes_elem_size(src->elem));
|
|
@@ -410,6 +419,10 @@ static const char scr_hex_digits[] = "0123456789abcdef";
|
|
|
410
419
|
* — what Buffer.prototype.toString("utf8") does. Output re-encodes as
|
|
411
420
|
* (now valid) UTF-8: worst case 3 bytes per input byte. */
|
|
412
421
|
static ScrStr *scr_bytes_decode_utf8(const uint8_t *in, size_t n) {
|
|
422
|
+
if (in == NULL && n != 0) {
|
|
423
|
+
scr_trap("scriptc: native callback passed a NULL span with nonzero length\n");
|
|
424
|
+
}
|
|
425
|
+
if (n > (SIZE_MAX - 1) / 3) scr_bytes_oom();
|
|
413
426
|
char *out = malloc(n * 3 + 1);
|
|
414
427
|
if (!out) scr_bytes_oom();
|
|
415
428
|
size_t o = 0;
|
|
@@ -478,6 +491,10 @@ static ScrStr *scr_bytes_decode_utf8(const uint8_t *in, size_t n) {
|
|
|
478
491
|
return s;
|
|
479
492
|
}
|
|
480
493
|
|
|
494
|
+
ScrStr *scr_str_from_utf8_lossy(const uint8_t *bytes, size_t len) {
|
|
495
|
+
return scr_bytes_decode_utf8(bytes, len);
|
|
496
|
+
}
|
|
497
|
+
|
|
481
498
|
/* WHATWG TextDecoder.decode (utf-8, default options): the SAME maximal-
|
|
482
499
|
* subpart replacement decode as toString("utf8") above, with one
|
|
483
500
|
* difference — a leading UTF-8 BOM is stripped (ignoreBOM defaults to
|
package/src/scr_ffi.c
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#include "scr_runtime.h"
|
|
2
|
+
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
|
|
5
|
+
/* The process-global retained-registration ledger. Format-4 script-thread
|
|
6
|
+
* tables use the lock-free path in this always-linked unit. Format-5 foreign
|
|
7
|
+
* tables install an optional teardown hook owned by scr_ffi_queue.c, keeping
|
|
8
|
+
* all queue/thread machinery out of unrelated binaries. */
|
|
9
|
+
static ScrFfiTable *scr_ffi_tables;
|
|
10
|
+
static bool scr_ffi_exit_registered;
|
|
11
|
+
|
|
12
|
+
static void scr_ffi_oom(void) { scr_trap("scriptc: out of memory\n"); }
|
|
13
|
+
|
|
14
|
+
void scr_ffi_link(ScrFfiTable *table) {
|
|
15
|
+
if (!table->linked) {
|
|
16
|
+
table->linked = true;
|
|
17
|
+
table->next = scr_ffi_tables;
|
|
18
|
+
scr_ffi_tables = table;
|
|
19
|
+
}
|
|
20
|
+
if (!scr_ffi_exit_registered) {
|
|
21
|
+
scr_ffi_exit_registered = true;
|
|
22
|
+
scr_atexit(scr_ffi_teardown_all);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void scr_ffi_teardown(ScrFfiTable *table) {
|
|
27
|
+
if (table->teardown != NULL) {
|
|
28
|
+
table->teardown(table);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
/* Disarm the raw trampoline slot FIRST: a closure release below cannot
|
|
32
|
+
* run script code today, but the slot must never dangle over freed
|
|
33
|
+
* entries — a native exit-path invocation takes the NULL trap instead. */
|
|
34
|
+
if (table->slot != NULL) *table->slot = NULL;
|
|
35
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
36
|
+
scr_closure_release(table->entries[i]);
|
|
37
|
+
}
|
|
38
|
+
free(table->entries);
|
|
39
|
+
table->entries = NULL;
|
|
40
|
+
table->len = 0;
|
|
41
|
+
table->cap = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void scr_ffi_teardown_all(void) {
|
|
45
|
+
for (ScrFfiTable *table = scr_ffi_tables; table != NULL; table = table->next) {
|
|
46
|
+
scr_ffi_teardown(table);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void scr_ffi_retain(ScrFfiTable *table, ScrClosure *callback) {
|
|
51
|
+
scr_ffi_link(table);
|
|
52
|
+
if (table->len == table->cap) {
|
|
53
|
+
size_t cap = table->cap == 0 ? 4 : table->cap * 2;
|
|
54
|
+
if (cap < table->cap || cap > SIZE_MAX / sizeof *table->entries) scr_ffi_oom();
|
|
55
|
+
ScrClosure **entries = realloc(table->entries, cap * sizeof *entries);
|
|
56
|
+
if (entries == NULL) scr_ffi_oom();
|
|
57
|
+
table->entries = entries;
|
|
58
|
+
table->cap = cap;
|
|
59
|
+
}
|
|
60
|
+
table->entries[table->len++] = scr_closure_retain(callback);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Raw singleton registration runs in two halves around the native set
|
|
64
|
+
* call. The first half pins the incoming closure and records the slot,
|
|
65
|
+
* but leaves the CURRENT registration untouched: a native setter may
|
|
66
|
+
* flush the outgoing callback one last time mid-replace, and it must
|
|
67
|
+
* dispatch a live closure. An empty slot arms immediately so a native
|
|
68
|
+
* fire-on-subscribe during the set call reaches the new closure. */
|
|
69
|
+
void scr_ffi_retain_slot(ScrFfiTable *table, ScrClosure **slot, ScrClosure *callback) {
|
|
70
|
+
table->slot = slot;
|
|
71
|
+
scr_ffi_retain(table, callback);
|
|
72
|
+
if (*slot == NULL) *slot = callback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The second half, after the native call returns: point the slot at the
|
|
76
|
+
* new registration and retire every pin it superseded. A callback pumped
|
|
77
|
+
* during the native call may have released or replaced this registration
|
|
78
|
+
* already (nested rawSet/rawRemove) — commit only a still-live entry. */
|
|
79
|
+
void scr_ffi_commit_slot(ScrFfiTable *table, ScrClosure *callback) {
|
|
80
|
+
bool live = false;
|
|
81
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
82
|
+
if (table->entries[i] == callback) {
|
|
83
|
+
live = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!live) return;
|
|
88
|
+
*table->slot = callback;
|
|
89
|
+
bool kept = false;
|
|
90
|
+
size_t i = 0;
|
|
91
|
+
while (i < table->len) {
|
|
92
|
+
ScrClosure *entry = table->entries[i];
|
|
93
|
+
if (entry == callback && !kept) {
|
|
94
|
+
kept = true;
|
|
95
|
+
i++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
/* Swap-remove BEFORE releasing so the table stays consistent while
|
|
99
|
+
* the release runs; the swapped-in tail entry is re-examined at i. */
|
|
100
|
+
table->len--;
|
|
101
|
+
table->entries[i] = table->entries[table->len];
|
|
102
|
+
scr_closure_release(entry);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Pre-call validation for an explicit release: emitted before the native
|
|
107
|
+
* removal call, so releasing an unregistered value traps before native
|
|
108
|
+
* code can act on the bogus pointer pair. */
|
|
109
|
+
void scr_ffi_require(ScrFfiTable *table, ScrClosure *callback) {
|
|
110
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
111
|
+
if (table->entries[i] == callback) return;
|
|
112
|
+
}
|
|
113
|
+
scr_trap("scriptc: releasing a native callback registration that does not exist\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void scr_ffi_release(ScrFfiTable *table, ScrClosure *callback) {
|
|
117
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
118
|
+
if (table->entries[i] != callback) continue;
|
|
119
|
+
ScrClosure *owned = table->entries[i];
|
|
120
|
+
table->len--;
|
|
121
|
+
if (i != table->len) table->entries[i] = table->entries[table->len];
|
|
122
|
+
/* If the raw slot dispatched to this registration and no duplicate
|
|
123
|
+
* pin remains, disarm it — the trampoline must not reach a released
|
|
124
|
+
* closure. */
|
|
125
|
+
if (table->slot != NULL && *table->slot == callback) {
|
|
126
|
+
bool remaining = false;
|
|
127
|
+
for (size_t j = 0; j < table->len; j++) {
|
|
128
|
+
if (table->entries[j] == callback) {
|
|
129
|
+
remaining = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!remaining) *table->slot = NULL;
|
|
134
|
+
}
|
|
135
|
+
scr_closure_release(owned);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
scr_trap("scriptc: releasing a native callback registration that does not exist\n");
|
|
139
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#include "scr_runtime.h"
|
|
2
|
+
|
|
3
|
+
#include <stdint.h>
|
|
4
|
+
#include <stdio.h>
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
#ifdef _WIN32
|
|
9
|
+
#include <windows.h>
|
|
10
|
+
#elif defined(__wasi__)
|
|
11
|
+
/* Native FFI is rejected for WASI. This keeps direct runtime builds honest. */
|
|
12
|
+
#else
|
|
13
|
+
#include <fcntl.h>
|
|
14
|
+
#include <pthread.h>
|
|
15
|
+
#include <unistd.h>
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
typedef enum {
|
|
19
|
+
SCR_FFI_ARG_NONE = 0,
|
|
20
|
+
SCR_FFI_ARG_F64,
|
|
21
|
+
SCR_FFI_ARG_BOOL,
|
|
22
|
+
SCR_FFI_ARG_U8,
|
|
23
|
+
SCR_FFI_ARG_U32,
|
|
24
|
+
SCR_FFI_ARG_I32,
|
|
25
|
+
SCR_FFI_ARG_DATA,
|
|
26
|
+
} ScrFfiArgKind;
|
|
27
|
+
|
|
28
|
+
typedef struct {
|
|
29
|
+
ScrFfiArgKind kind;
|
|
30
|
+
union {
|
|
31
|
+
double f64;
|
|
32
|
+
uint32_t u32;
|
|
33
|
+
int32_t i32;
|
|
34
|
+
struct {
|
|
35
|
+
uint8_t *data;
|
|
36
|
+
size_t len;
|
|
37
|
+
} span;
|
|
38
|
+
} value;
|
|
39
|
+
} ScrFfiArg;
|
|
40
|
+
|
|
41
|
+
struct ScrFfiCall {
|
|
42
|
+
ScrFfiTable *table; /* opaque on the posting thread */
|
|
43
|
+
void *loop; /* reserved instance/loop identity */
|
|
44
|
+
ScrClosure *callback; /* opaque; the table owns its script RC pin */
|
|
45
|
+
ScrFfiDispatchFn dispatch;
|
|
46
|
+
size_t nargs;
|
|
47
|
+
ScrFfiArg *args;
|
|
48
|
+
struct ScrFfiCall *next;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
static ScrFfiCall *scr_ffi_head;
|
|
52
|
+
static ScrFfiCall *scr_ffi_tail;
|
|
53
|
+
static size_t scr_ffi_foreign_registrations;
|
|
54
|
+
static bool scr_ffi_installed;
|
|
55
|
+
static bool scr_ffi_stopping;
|
|
56
|
+
static int scr_ffi_wake_pipe[2] = {-1, -1};
|
|
57
|
+
|
|
58
|
+
#ifdef _WIN32
|
|
59
|
+
static SRWLOCK scr_ffi_lock = SRWLOCK_INIT;
|
|
60
|
+
static void scr_ffi_lock_enter(void) { AcquireSRWLockExclusive(&scr_ffi_lock); }
|
|
61
|
+
static void scr_ffi_lock_leave(void) { ReleaseSRWLockExclusive(&scr_ffi_lock); }
|
|
62
|
+
#elif defined(__wasi__)
|
|
63
|
+
static void scr_ffi_lock_enter(void) {}
|
|
64
|
+
static void scr_ffi_lock_leave(void) {}
|
|
65
|
+
#else
|
|
66
|
+
static pthread_mutex_t scr_ffi_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
67
|
+
static void scr_ffi_lock_enter(void) { (void)pthread_mutex_lock(&scr_ffi_lock); }
|
|
68
|
+
static void scr_ffi_lock_leave(void) { (void)pthread_mutex_unlock(&scr_ffi_lock); }
|
|
69
|
+
#endif
|
|
70
|
+
|
|
71
|
+
static void scr_ffi_oom(void) { scr_trap("scriptc: out of memory\n"); }
|
|
72
|
+
|
|
73
|
+
/* Foreign trampolines cannot enter the script exception machinery. Allocation
|
|
74
|
+
* failure and malformed native pointers are fatal process-boundary failures,
|
|
75
|
+
* reported without touching any runtime object. */
|
|
76
|
+
static void scr_ffi_foreign_fatal(const char *message) {
|
|
77
|
+
fputs(message, stderr);
|
|
78
|
+
abort();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static void *scr_ffi_foreign_alloc(size_t size) {
|
|
82
|
+
void *ptr = malloc(size == 0 ? 1 : size);
|
|
83
|
+
if (ptr == NULL) scr_ffi_foreign_fatal("scriptc: out of memory\n");
|
|
84
|
+
return ptr;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static void scr_ffi_call_free(ScrFfiCall *call) {
|
|
88
|
+
if (call == NULL) return;
|
|
89
|
+
for (size_t i = 0; i < call->nargs; i++) {
|
|
90
|
+
if (call->args[i].kind == SCR_FFI_ARG_DATA) free(call->args[i].value.span.data);
|
|
91
|
+
}
|
|
92
|
+
free(call->args);
|
|
93
|
+
free(call);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static bool scr_ffi_table_contains(const ScrFfiTable *table, const ScrClosure *callback) {
|
|
97
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
98
|
+
if (table->entries[i] == callback) return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static void scr_ffi_table_retire(ScrFfiTable *table, ScrClosure *callback) {
|
|
104
|
+
if (table->retired_len == table->retired_cap) {
|
|
105
|
+
size_t cap = table->retired_cap == 0 ? 4 : table->retired_cap * 2;
|
|
106
|
+
if (cap < table->retired_cap || cap > SIZE_MAX / sizeof *table->retired) scr_ffi_oom();
|
|
107
|
+
ScrClosure **retired = realloc(table->retired, cap * sizeof *retired);
|
|
108
|
+
if (retired == NULL) scr_ffi_oom();
|
|
109
|
+
table->retired = retired;
|
|
110
|
+
table->retired_cap = cap;
|
|
111
|
+
}
|
|
112
|
+
table->retired[table->retired_len++] = callback;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void scr_ffi_release_retired(ScrFfiTable *table) {
|
|
116
|
+
ScrClosure **retired = NULL;
|
|
117
|
+
size_t len = 0;
|
|
118
|
+
scr_ffi_lock_enter();
|
|
119
|
+
if (table->queued == 0 && table->retired_len > 0) {
|
|
120
|
+
retired = table->retired;
|
|
121
|
+
len = table->retired_len;
|
|
122
|
+
table->retired = NULL;
|
|
123
|
+
table->retired_len = 0;
|
|
124
|
+
table->retired_cap = 0;
|
|
125
|
+
}
|
|
126
|
+
scr_ffi_lock_leave();
|
|
127
|
+
for (size_t i = 0; i < len; i++) scr_closure_release(retired[i]);
|
|
128
|
+
free(retired);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void scr_ffi_retain_foreign(ScrFfiTable *table, ScrClosure *callback) {
|
|
132
|
+
table->teardown = &scr_ffi_teardown_foreign;
|
|
133
|
+
scr_ffi_link(table);
|
|
134
|
+
scr_ffi_lock_enter();
|
|
135
|
+
if (table->len == table->cap) {
|
|
136
|
+
size_t cap = table->cap == 0 ? 4 : table->cap * 2;
|
|
137
|
+
if (cap < table->cap || cap > SIZE_MAX / sizeof *table->entries) scr_ffi_oom();
|
|
138
|
+
ScrClosure **entries = realloc(table->entries, cap * sizeof *entries);
|
|
139
|
+
if (entries == NULL) scr_ffi_oom();
|
|
140
|
+
table->entries = entries;
|
|
141
|
+
table->cap = cap;
|
|
142
|
+
}
|
|
143
|
+
table->entries[table->len++] = scr_closure_retain(callback);
|
|
144
|
+
scr_ffi_foreign_registrations++;
|
|
145
|
+
scr_ffi_lock_leave();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
void scr_ffi_require_foreign(ScrFfiTable *table, ScrClosure *callback) {
|
|
149
|
+
scr_ffi_lock_enter();
|
|
150
|
+
bool found = scr_ffi_table_contains(table, callback);
|
|
151
|
+
scr_ffi_lock_leave();
|
|
152
|
+
if (!found) scr_trap("scriptc: releasing a native callback registration that does not exist\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
void scr_ffi_release_foreign(ScrFfiTable *table, ScrClosure *callback) {
|
|
156
|
+
scr_ffi_lock_enter();
|
|
157
|
+
for (size_t i = 0; i < table->len; i++) {
|
|
158
|
+
if (table->entries[i] != callback) continue;
|
|
159
|
+
ScrClosure *owned = table->entries[i];
|
|
160
|
+
table->len--;
|
|
161
|
+
if (i != table->len) table->entries[i] = table->entries[table->len];
|
|
162
|
+
if (scr_ffi_foreign_registrations > 0) scr_ffi_foreign_registrations--;
|
|
163
|
+
bool deferred = table->queued > 0;
|
|
164
|
+
if (deferred) scr_ffi_table_retire(table, owned);
|
|
165
|
+
scr_ffi_lock_leave();
|
|
166
|
+
if (!deferred) scr_closure_release(owned);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
scr_ffi_lock_leave();
|
|
170
|
+
scr_trap("scriptc: releasing a native callback registration that does not exist\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
ScrFfiCall *scr_ffi_call_new(ScrFfiTable *table, ScrClosure *callback,
|
|
174
|
+
ScrFfiDispatchFn dispatch, size_t nargs) {
|
|
175
|
+
ScrFfiCall *call = scr_ffi_foreign_alloc(sizeof *call);
|
|
176
|
+
call->table = table;
|
|
177
|
+
call->loop = table->loop;
|
|
178
|
+
call->callback = callback;
|
|
179
|
+
call->dispatch = dispatch;
|
|
180
|
+
call->nargs = nargs;
|
|
181
|
+
call->args = nargs == 0 ? NULL : scr_ffi_foreign_alloc(nargs * sizeof *call->args);
|
|
182
|
+
if (nargs > 0) memset(call->args, 0, nargs * sizeof *call->args);
|
|
183
|
+
call->next = NULL;
|
|
184
|
+
return call;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static ScrFfiArg *scr_ffi_call_arg(ScrFfiCall *call, size_t index) {
|
|
188
|
+
if (index >= call->nargs) scr_ffi_foreign_fatal("scriptc: invalid staged FFI callback argument\n");
|
|
189
|
+
return &call->args[index];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void scr_ffi_call_set_f64(ScrFfiCall *call, size_t i, double v) {
|
|
193
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i); arg->kind = SCR_FFI_ARG_F64; arg->value.f64 = v;
|
|
194
|
+
}
|
|
195
|
+
void scr_ffi_call_set_bool(ScrFfiCall *call, size_t i, uint8_t v) {
|
|
196
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i); arg->kind = SCR_FFI_ARG_BOOL; arg->value.u32 = v != 0;
|
|
197
|
+
}
|
|
198
|
+
void scr_ffi_call_set_u8(ScrFfiCall *call, size_t i, uint8_t v) {
|
|
199
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i); arg->kind = SCR_FFI_ARG_U8; arg->value.u32 = v;
|
|
200
|
+
}
|
|
201
|
+
void scr_ffi_call_set_u32(ScrFfiCall *call, size_t i, uint32_t v) {
|
|
202
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i); arg->kind = SCR_FFI_ARG_U32; arg->value.u32 = v;
|
|
203
|
+
}
|
|
204
|
+
void scr_ffi_call_set_i32(ScrFfiCall *call, size_t i, int32_t v) {
|
|
205
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i); arg->kind = SCR_FFI_ARG_I32; arg->value.i32 = v;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static void scr_ffi_call_copy_data(ScrFfiCall *call, size_t i,
|
|
209
|
+
const uint8_t *v, size_t len,
|
|
210
|
+
const char *null_message) {
|
|
211
|
+
if (v == NULL && len != 0) scr_ffi_foreign_fatal(null_message);
|
|
212
|
+
ScrFfiArg *arg = scr_ffi_call_arg(call, i);
|
|
213
|
+
arg->kind = SCR_FFI_ARG_DATA;
|
|
214
|
+
arg->value.span.len = len;
|
|
215
|
+
arg->value.span.data = len == 0 ? NULL : scr_ffi_foreign_alloc(len);
|
|
216
|
+
if (len > 0) memcpy(arg->value.span.data, v, len);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
void scr_ffi_call_copy_string(ScrFfiCall *call, size_t i,
|
|
220
|
+
const uint8_t *v, size_t len) {
|
|
221
|
+
scr_ffi_call_copy_data(call, i, v, len,
|
|
222
|
+
"scriptc: native callback passed a NULL string span with nonzero length\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
void scr_ffi_call_copy_bytes(ScrFfiCall *call, size_t i,
|
|
226
|
+
const uint8_t *v, size_t len) {
|
|
227
|
+
scr_ffi_call_copy_data(call, i, v, len,
|
|
228
|
+
"scriptc: native callback passed a NULL bytes span with nonzero length\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
void scr_ffi_call_copy_cstring(ScrFfiCall *call, size_t i, const char *v) {
|
|
232
|
+
if (v == NULL) scr_ffi_foreign_fatal("scriptc: native callback passed a NULL cstring\n");
|
|
233
|
+
scr_ffi_call_copy_data(call, i, (const uint8_t *)v, strlen(v),
|
|
234
|
+
"scriptc: native callback passed a NULL cstring\n");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
void scr_ffi_post(ScrFfiCall *call) {
|
|
238
|
+
scr_ffi_lock_enter();
|
|
239
|
+
if (scr_ffi_stopping || !scr_ffi_table_contains(call->table, call->callback)) {
|
|
240
|
+
scr_ffi_lock_leave();
|
|
241
|
+
scr_ffi_call_free(call);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
call->table->queued++;
|
|
245
|
+
if (scr_ffi_tail != NULL) scr_ffi_tail->next = call;
|
|
246
|
+
else scr_ffi_head = call;
|
|
247
|
+
scr_ffi_tail = call;
|
|
248
|
+
#if !defined(_WIN32) && !defined(__wasi__)
|
|
249
|
+
if (scr_ffi_wake_pipe[1] >= 0) {
|
|
250
|
+
ssize_t ignored = write(scr_ffi_wake_pipe[1], "f", 1);
|
|
251
|
+
(void)ignored; /* a full pipe is already readable */
|
|
252
|
+
}
|
|
253
|
+
#endif
|
|
254
|
+
scr_ffi_lock_leave();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static const ScrFfiArg *scr_ffi_get_arg(const ScrFfiCall *call, size_t index) {
|
|
258
|
+
if (index >= call->nargs) scr_trap("scriptc: invalid staged FFI callback argument\n");
|
|
259
|
+
return &call->args[index];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
double scr_ffi_call_get_f64(const ScrFfiCall *call, size_t i) { return scr_ffi_get_arg(call, i)->value.f64; }
|
|
263
|
+
bool scr_ffi_call_get_bool(const ScrFfiCall *call, size_t i) { return scr_ffi_get_arg(call, i)->value.u32 != 0; }
|
|
264
|
+
double scr_ffi_call_get_u8(const ScrFfiCall *call, size_t i) { return (double)scr_ffi_get_arg(call, i)->value.u32; }
|
|
265
|
+
double scr_ffi_call_get_u32(const ScrFfiCall *call, size_t i) { return (double)scr_ffi_get_arg(call, i)->value.u32; }
|
|
266
|
+
double scr_ffi_call_get_i32(const ScrFfiCall *call, size_t i) { return (double)scr_ffi_get_arg(call, i)->value.i32; }
|
|
267
|
+
const uint8_t *scr_ffi_call_get_data(const ScrFfiCall *call, size_t i) { return scr_ffi_get_arg(call, i)->value.span.data; }
|
|
268
|
+
size_t scr_ffi_call_get_len(const ScrFfiCall *call, size_t i) { return scr_ffi_get_arg(call, i)->value.span.len; }
|
|
269
|
+
|
|
270
|
+
static bool scr_ffi_pending(void) {
|
|
271
|
+
bool pending;
|
|
272
|
+
scr_ffi_lock_enter();
|
|
273
|
+
pending = scr_ffi_foreign_registrations > 0 || scr_ffi_head != NULL;
|
|
274
|
+
scr_ffi_lock_leave();
|
|
275
|
+
return pending;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static void scr_ffi_wake_drain(void) {
|
|
279
|
+
#if !defined(_WIN32) && !defined(__wasi__)
|
|
280
|
+
if (scr_ffi_wake_pipe[0] < 0) return;
|
|
281
|
+
char buf[64];
|
|
282
|
+
while (read(scr_ffi_wake_pipe[0], buf, sizeof buf) > 0) {}
|
|
283
|
+
#endif
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static bool scr_ffi_dispatch(void) {
|
|
287
|
+
scr_ffi_lock_enter();
|
|
288
|
+
ScrFfiCall *call = scr_ffi_head;
|
|
289
|
+
if (call != NULL) {
|
|
290
|
+
scr_ffi_head = call->next;
|
|
291
|
+
if (scr_ffi_head == NULL) scr_ffi_tail = NULL;
|
|
292
|
+
}
|
|
293
|
+
scr_ffi_lock_leave();
|
|
294
|
+
if (call == NULL) return false;
|
|
295
|
+
|
|
296
|
+
call->dispatch(call->callback, call);
|
|
297
|
+
|
|
298
|
+
ScrFfiTable *table = call->table;
|
|
299
|
+
scr_ffi_lock_enter();
|
|
300
|
+
if (table->queued > 0) table->queued--;
|
|
301
|
+
/* Keep the wake fd readable while queued calls remain. Drain only while
|
|
302
|
+
* holding the same lock writers use, closing the post-vs-drain lost-wakeup
|
|
303
|
+
* race for the transition to an empty queue. */
|
|
304
|
+
bool more = scr_ffi_head != NULL;
|
|
305
|
+
if (!more) scr_ffi_wake_drain();
|
|
306
|
+
scr_ffi_lock_leave();
|
|
307
|
+
scr_ffi_call_free(call);
|
|
308
|
+
scr_ffi_release_retired(table);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static int scr_ffi_pollfd(void) { return scr_ffi_wake_pipe[0]; }
|
|
313
|
+
|
|
314
|
+
void scr_ffi_install(void) {
|
|
315
|
+
if (scr_ffi_installed) return;
|
|
316
|
+
scr_ffi_installed = true;
|
|
317
|
+
#if !defined(_WIN32) && !defined(__wasi__)
|
|
318
|
+
if (pipe(scr_ffi_wake_pipe) == 0) {
|
|
319
|
+
for (int i = 0; i < 2; i++) {
|
|
320
|
+
(void)fcntl(scr_ffi_wake_pipe[i], F_SETFL, O_NONBLOCK);
|
|
321
|
+
(void)fcntl(scr_ffi_wake_pipe[i], F_SETFD, FD_CLOEXEC);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
scr_ffi_wake_pipe[0] = scr_ffi_wake_pipe[1] = -1;
|
|
325
|
+
}
|
|
326
|
+
#endif
|
|
327
|
+
scr_loop_set_ffi(&scr_ffi_pending, &scr_ffi_dispatch, &scr_ffi_pollfd,
|
|
328
|
+
&scr_ffi_stop);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
void scr_ffi_stop(void) {
|
|
332
|
+
scr_ffi_lock_enter();
|
|
333
|
+
if (scr_ffi_stopping) {
|
|
334
|
+
scr_ffi_lock_leave();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
scr_ffi_stopping = true;
|
|
338
|
+
ScrFfiCall *calls = scr_ffi_head;
|
|
339
|
+
scr_ffi_head = scr_ffi_tail = NULL;
|
|
340
|
+
for (ScrFfiCall *call = calls; call != NULL; call = call->next) {
|
|
341
|
+
if (call->table->queued > 0) call->table->queued--;
|
|
342
|
+
}
|
|
343
|
+
scr_ffi_wake_drain();
|
|
344
|
+
#if !defined(_WIN32) && !defined(__wasi__)
|
|
345
|
+
if (scr_ffi_wake_pipe[0] >= 0) close(scr_ffi_wake_pipe[0]);
|
|
346
|
+
if (scr_ffi_wake_pipe[1] >= 0) close(scr_ffi_wake_pipe[1]);
|
|
347
|
+
#endif
|
|
348
|
+
scr_ffi_wake_pipe[0] = scr_ffi_wake_pipe[1] = -1;
|
|
349
|
+
scr_ffi_lock_leave();
|
|
350
|
+
while (calls != NULL) {
|
|
351
|
+
ScrFfiCall *next = calls->next;
|
|
352
|
+
ScrFfiTable *table = calls->table;
|
|
353
|
+
scr_ffi_call_free(calls);
|
|
354
|
+
scr_ffi_release_retired(table);
|
|
355
|
+
calls = next;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
void scr_ffi_teardown_foreign(ScrFfiTable *table) {
|
|
360
|
+
scr_ffi_stop();
|
|
361
|
+
scr_ffi_lock_enter();
|
|
362
|
+
ScrClosure **entries = table->entries;
|
|
363
|
+
size_t len = table->len;
|
|
364
|
+
ScrClosure **retired = table->retired;
|
|
365
|
+
size_t retired_len = table->retired_len;
|
|
366
|
+
if (scr_ffi_foreign_registrations >= len) scr_ffi_foreign_registrations -= len;
|
|
367
|
+
table->entries = NULL;
|
|
368
|
+
table->len = 0;
|
|
369
|
+
table->cap = 0;
|
|
370
|
+
table->retired = NULL;
|
|
371
|
+
table->retired_len = 0;
|
|
372
|
+
table->retired_cap = 0;
|
|
373
|
+
table->queued = 0;
|
|
374
|
+
scr_ffi_lock_leave();
|
|
375
|
+
for (size_t i = 0; i < len; i++) scr_closure_release(entries[i]);
|
|
376
|
+
free(entries);
|
|
377
|
+
for (size_t i = 0; i < retired_len; i++) scr_closure_release(retired[i]);
|
|
378
|
+
free(retired);
|
|
379
|
+
}
|
package/src/scr_musl.c
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* this TU together with -DSCR_MUSL). musl deliberately provides no libc
|
|
3
3
|
* identification macro, so the target-selected project macro is the guard.
|
|
4
4
|
*
|
|
5
|
-
* The x86_64 ucontext
|
|
5
|
+
* The x86_64 and AArch64 ucontext implementations below are derived from libucontext
|
|
6
6
|
* commit 49e671dd52ff6791295d8161ad3b6da7dc5f6f9d:
|
|
7
7
|
* https://github.com/kaniini/libucontext
|
|
8
8
|
*
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
#include <ucontext.h>
|
|
28
28
|
#endif
|
|
29
29
|
|
|
30
|
-
#if !defined(__x86_64__)
|
|
31
|
-
#error "scriptc's musl runtime currently supports x86_64 only"
|
|
30
|
+
#if !defined(__x86_64__) && !defined(__aarch64__)
|
|
31
|
+
#error "scriptc's musl runtime currently supports x86_64 and AArch64 only"
|
|
32
32
|
#endif
|
|
33
33
|
|
|
34
34
|
/* The scriptc runtime uses arc4random_buf as its infallible CSPRNG contract.
|
|
@@ -57,9 +57,10 @@ void arc4random_buf(void *buf, size_t n) {
|
|
|
57
57
|
|
|
58
58
|
/* musl exposes the POSIX ucontext types but intentionally omits these legacy
|
|
59
59
|
* functions. scriptc's async/generator fibers need only user-space register
|
|
60
|
-
* swaps; they never use a context to mutate the process signal mask.
|
|
61
|
-
* libucontext's fast (non-POSIX-signal-mask)
|
|
62
|
-
*
|
|
60
|
+
* swaps; they never use a context to mutate the process signal mask. These are
|
|
61
|
+
* libucontext's fast (non-POSIX-signal-mask) implementations, emitted under
|
|
62
|
+
* the standard symbol names that musl leaves unresolved. */
|
|
63
|
+
#if defined(__x86_64__)
|
|
63
64
|
_Static_assert(offsetof(ucontext_t, uc_mcontext.gregs) == 40,
|
|
64
65
|
"unexpected musl x86_64 ucontext layout");
|
|
65
66
|
_Static_assert(offsetof(ucontext_t, uc_mcontext.fpregs) == 224,
|
|
@@ -265,6 +266,169 @@ void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) {
|
|
|
265
266
|
#undef SCR_STR
|
|
266
267
|
#undef SCR_STR_INNER
|
|
267
268
|
|
|
269
|
+
#elif defined(__aarch64__)
|
|
270
|
+
|
|
271
|
+
/* musl's AArch64 ucontext embeds the kernel sigcontext after a 128-byte
|
|
272
|
+
* signal mask. Keep the offsets pinned to the Zig 0.16 musl headers: the
|
|
273
|
+
* assembly below saves the ABI's callee-preserved GPR and SIMD state there. */
|
|
274
|
+
_Static_assert(offsetof(ucontext_t, uc_mcontext.regs[0]) == 184,
|
|
275
|
+
"unexpected musl AArch64 register layout");
|
|
276
|
+
_Static_assert(offsetof(ucontext_t, uc_mcontext.sp) == 432,
|
|
277
|
+
"unexpected musl AArch64 stack-pointer layout");
|
|
278
|
+
_Static_assert(offsetof(ucontext_t, uc_mcontext.pc) == 440,
|
|
279
|
+
"unexpected musl AArch64 program-counter layout");
|
|
280
|
+
_Static_assert(offsetof(ucontext_t, uc_mcontext.pstate) == 448,
|
|
281
|
+
"unexpected musl AArch64 processor-state layout");
|
|
282
|
+
_Static_assert(offsetof(ucontext_t, uc_mcontext.__reserved) == 464,
|
|
283
|
+
"unexpected musl AArch64 SIMD-state layout");
|
|
284
|
+
|
|
285
|
+
#define SCR_STR_INNER(x) #x
|
|
286
|
+
#define SCR_STR(x) SCR_STR_INNER(x)
|
|
287
|
+
#define SCR_A64_UC_REG(reg) (184 + ((reg) * 8))
|
|
288
|
+
#define SCR_A64_UC_SP 432
|
|
289
|
+
#define SCR_A64_UC_PC 440
|
|
290
|
+
#define SCR_A64_UC_PSTATE 448
|
|
291
|
+
#define SCR_A64_UC_FPSIMD 464
|
|
292
|
+
|
|
293
|
+
__asm__(
|
|
294
|
+
".text\n"
|
|
295
|
+
".p2align 2\n"
|
|
296
|
+
".global getcontext\n"
|
|
297
|
+
"getcontext:\n"
|
|
298
|
+
" str xzr, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
|
|
299
|
+
" stp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
|
|
300
|
+
" str x30, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
|
|
301
|
+
" mov x2, sp\n"
|
|
302
|
+
" str x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
|
|
303
|
+
" str xzr, [x0, #" SCR_STR(SCR_A64_UC_PSTATE) "]\n"
|
|
304
|
+
" add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
|
|
305
|
+
" stp q8, q9, [x2, #144]\n"
|
|
306
|
+
" stp q10, q11, [x2, #176]\n"
|
|
307
|
+
" stp q12, q13, [x2, #208]\n"
|
|
308
|
+
" stp q14, q15, [x2, #240]\n"
|
|
309
|
+
" mov x2, x0\n"
|
|
310
|
+
" mov x0, #0\n"
|
|
311
|
+
" stp x0, x1, [x2, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
|
|
312
|
+
" stp x4, x5, [x2, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
|
|
313
|
+
" stp x6, x7, [x2, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
|
|
314
|
+
" stp x8, x9, [x2, #" SCR_STR(SCR_A64_UC_REG(8)) "]\n"
|
|
315
|
+
" stp x10, x11, [x2, #" SCR_STR(SCR_A64_UC_REG(10)) "]\n"
|
|
316
|
+
" stp x12, x13, [x2, #" SCR_STR(SCR_A64_UC_REG(12)) "]\n"
|
|
317
|
+
" stp x14, x15, [x2, #" SCR_STR(SCR_A64_UC_REG(14)) "]\n"
|
|
318
|
+
" stp x16, x17, [x2, #" SCR_STR(SCR_A64_UC_REG(16)) "]\n"
|
|
319
|
+
" stp x18, x19, [x2, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
|
|
320
|
+
" stp x20, x21, [x2, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
|
|
321
|
+
" stp x22, x23, [x2, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
|
|
322
|
+
" stp x24, x25, [x2, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
|
|
323
|
+
" stp x26, x27, [x2, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
|
|
324
|
+
" stp x28, x29, [x2, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
|
|
325
|
+
" str x30, [x2, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
|
|
326
|
+
" ret\n"
|
|
327
|
+
|
|
328
|
+
".p2align 2\n"
|
|
329
|
+
".global setcontext\n"
|
|
330
|
+
"setcontext:\n"
|
|
331
|
+
" ldp x18, x19, [x0, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
|
|
332
|
+
" ldp x20, x21, [x0, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
|
|
333
|
+
" ldp x22, x23, [x0, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
|
|
334
|
+
" ldp x24, x25, [x0, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
|
|
335
|
+
" ldp x26, x27, [x0, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
|
|
336
|
+
" ldp x28, x29, [x0, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
|
|
337
|
+
" ldr x30, [x0, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
|
|
338
|
+
" ldr x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
|
|
339
|
+
" mov sp, x2\n"
|
|
340
|
+
" add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
|
|
341
|
+
" ldp q8, q9, [x2, #144]\n"
|
|
342
|
+
" ldp q10, q11, [x2, #176]\n"
|
|
343
|
+
" ldp q12, q13, [x2, #208]\n"
|
|
344
|
+
" ldp q14, q15, [x2, #240]\n"
|
|
345
|
+
" ldr x16, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
|
|
346
|
+
" ldp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
|
|
347
|
+
" ldp x4, x5, [x0, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
|
|
348
|
+
" ldp x6, x7, [x0, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
|
|
349
|
+
" ldp x0, x1, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
|
|
350
|
+
" br x16\n"
|
|
351
|
+
|
|
352
|
+
".p2align 2\n"
|
|
353
|
+
".global swapcontext\n"
|
|
354
|
+
"swapcontext:\n"
|
|
355
|
+
" str xzr, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
|
|
356
|
+
" stp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
|
|
357
|
+
" stp x4, x5, [x0, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
|
|
358
|
+
" stp x6, x7, [x0, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
|
|
359
|
+
" stp x8, x9, [x0, #" SCR_STR(SCR_A64_UC_REG(8)) "]\n"
|
|
360
|
+
" stp x10, x11, [x0, #" SCR_STR(SCR_A64_UC_REG(10)) "]\n"
|
|
361
|
+
" stp x12, x13, [x0, #" SCR_STR(SCR_A64_UC_REG(12)) "]\n"
|
|
362
|
+
" stp x14, x15, [x0, #" SCR_STR(SCR_A64_UC_REG(14)) "]\n"
|
|
363
|
+
" stp x16, x17, [x0, #" SCR_STR(SCR_A64_UC_REG(16)) "]\n"
|
|
364
|
+
" stp x18, x19, [x0, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
|
|
365
|
+
" stp x20, x21, [x0, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
|
|
366
|
+
" stp x22, x23, [x0, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
|
|
367
|
+
" stp x24, x25, [x0, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
|
|
368
|
+
" stp x26, x27, [x0, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
|
|
369
|
+
" stp x28, x29, [x0, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
|
|
370
|
+
" str x30, [x0, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
|
|
371
|
+
" str x30, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
|
|
372
|
+
" mov x2, sp\n"
|
|
373
|
+
" str x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
|
|
374
|
+
" str xzr, [x0, #" SCR_STR(SCR_A64_UC_PSTATE) "]\n"
|
|
375
|
+
" add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
|
|
376
|
+
" stp q8, q9, [x2, #144]\n"
|
|
377
|
+
" stp q10, q11, [x2, #176]\n"
|
|
378
|
+
" stp q12, q13, [x2, #208]\n"
|
|
379
|
+
" stp q14, q15, [x2, #240]\n"
|
|
380
|
+
" mov x0, x1\n"
|
|
381
|
+
" b setcontext\n"
|
|
382
|
+
|
|
383
|
+
".p2align 2\n"
|
|
384
|
+
".hidden scr_musl_context_trampoline\n"
|
|
385
|
+
"scr_musl_context_trampoline:\n"
|
|
386
|
+
" mov x0, x19\n"
|
|
387
|
+
" cbz x0, 1f\n"
|
|
388
|
+
" bl setcontext\n"
|
|
389
|
+
" brk #0\n"
|
|
390
|
+
"1:\n"
|
|
391
|
+
" mov w0, #0\n"
|
|
392
|
+
" bl exit\n"
|
|
393
|
+
" brk #0\n");
|
|
394
|
+
|
|
395
|
+
extern void scr_musl_context_trampoline(void);
|
|
396
|
+
|
|
397
|
+
void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) {
|
|
398
|
+
greg_t *sp;
|
|
399
|
+
va_list va;
|
|
400
|
+
int i;
|
|
401
|
+
|
|
402
|
+
sp = (greg_t *)((uintptr_t)ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size);
|
|
403
|
+
sp -= argc < 8 ? 0 : argc - 8;
|
|
404
|
+
sp = (greg_t *)((uintptr_t)sp & ~(uintptr_t)15);
|
|
405
|
+
|
|
406
|
+
ucp->uc_mcontext.sp = (greg_t)(uintptr_t)sp;
|
|
407
|
+
ucp->uc_mcontext.pc = (greg_t)(uintptr_t)func;
|
|
408
|
+
ucp->uc_mcontext.regs[19] = (greg_t)(uintptr_t)ucp->uc_link;
|
|
409
|
+
ucp->uc_mcontext.regs[30] =
|
|
410
|
+
(greg_t)(uintptr_t)&scr_musl_context_trampoline;
|
|
411
|
+
|
|
412
|
+
va_start(va, argc);
|
|
413
|
+
for (i = 0; i < argc && i < 8; i++) {
|
|
414
|
+
ucp->uc_mcontext.regs[i] = va_arg(va, greg_t);
|
|
415
|
+
}
|
|
416
|
+
for (; i < argc; i++) {
|
|
417
|
+
*sp++ = va_arg(va, greg_t);
|
|
418
|
+
}
|
|
419
|
+
va_end(va);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#undef SCR_A64_UC_FPSIMD
|
|
423
|
+
#undef SCR_A64_UC_PSTATE
|
|
424
|
+
#undef SCR_A64_UC_PC
|
|
425
|
+
#undef SCR_A64_UC_SP
|
|
426
|
+
#undef SCR_A64_UC_REG
|
|
427
|
+
#undef SCR_STR
|
|
428
|
+
#undef SCR_STR_INNER
|
|
429
|
+
|
|
430
|
+
#endif /* architecture */
|
|
431
|
+
|
|
268
432
|
#endif /* !SCR_LIB */
|
|
269
433
|
|
|
270
434
|
#else /* !SCR_MUSL */
|
package/src/scr_runtime.h
CHANGED
|
@@ -389,6 +389,9 @@ typedef struct ScrStr {
|
|
|
389
389
|
} ScrStr;
|
|
390
390
|
|
|
391
391
|
ScrStr *scr_str_new(const char *bytes, size_t len); /* returns +1 */
|
|
392
|
+
/* Native callback boundary: copy and WHATWG-decode a UTF-8 span, replacing
|
|
393
|
+
* malformed subsequences with U+FFFD. NULL with len == 0 is an empty span. */
|
|
394
|
+
ScrStr *scr_str_from_utf8_lossy(const uint8_t *bytes, size_t len); /* +1 */
|
|
392
395
|
|
|
393
396
|
/* Internal allocators for buffer builders (scr_json.c): a +1 string with
|
|
394
397
|
* UNINITIALIZED data (the builder fills bytes, then len and the NUL), and
|
|
@@ -1316,6 +1319,100 @@ static inline ScrClosure *scr_closure_retain(ScrClosure *c) {
|
|
|
1316
1319
|
|
|
1317
1320
|
void scr_closure_release(ScrClosure *c); /* releases the boxes; NULL-tolerant */
|
|
1318
1321
|
|
|
1322
|
+
/* ── outbound FFI retained callbacks (scr_ffi.c) ─────────────────────
|
|
1323
|
+
* One compiler-emitted table per retained callback descriptor. For
|
|
1324
|
+
* context-bearing descriptors, entries are counted rather than
|
|
1325
|
+
* deduplicated: registering the same closure twice requires two matching
|
|
1326
|
+
* releases. Raw singleton slots replace instead: commit_slot retires
|
|
1327
|
+
* every pin the new registration superseded — a duplicate of the same
|
|
1328
|
+
* closure included — so after any number of set calls exactly one
|
|
1329
|
+
* release is pending. The table owns one closure reference per entry and
|
|
1330
|
+
* joins a process-global teardown list on first use. Script-thread retained
|
|
1331
|
+
* callbacks do not contribute event-loop liveness; foreign registrations do,
|
|
1332
|
+
* and their queued invocations keep released closure pins alive until the
|
|
1333
|
+
* script-thread dispatch has finished. */
|
|
1334
|
+
typedef struct ScrFfiTable {
|
|
1335
|
+
ScrClosure **entries;
|
|
1336
|
+
size_t len;
|
|
1337
|
+
size_t cap;
|
|
1338
|
+
struct ScrFfiTable *next;
|
|
1339
|
+
bool linked;
|
|
1340
|
+
/* Raw retained singletons only: the compiler-emitted global the
|
|
1341
|
+
* trampoline dispatches through. Teardown and release disarm it so a
|
|
1342
|
+
* late native invocation hits the trampoline's NULL trap instead of a
|
|
1343
|
+
* freed closure. NULL for context-bearing descriptors. */
|
|
1344
|
+
ScrClosure **slot;
|
|
1345
|
+
/* Foreign descriptors only. The global FFI queue lock protects these
|
|
1346
|
+
* fields together with entries/len/cap. Retired pins were explicitly
|
|
1347
|
+
* released while a queued invocation could still name them; dispatch
|
|
1348
|
+
* drops them on the script thread after this table's queue reaches zero. */
|
|
1349
|
+
ScrClosure **retired;
|
|
1350
|
+
size_t retired_len;
|
|
1351
|
+
size_t retired_cap;
|
|
1352
|
+
size_t queued;
|
|
1353
|
+
/* Reserved process-loop identity captured by queued calls. Executables use
|
|
1354
|
+
* one global loop today; library mode can make this per instance later
|
|
1355
|
+
* without changing the post/call ABI. */
|
|
1356
|
+
void *loop;
|
|
1357
|
+
/* Optional format-5 teardown. NULL keeps format-4 tables on the compact
|
|
1358
|
+
* always-linked path; foreign tables point into scr_ffi_queue.c. */
|
|
1359
|
+
void (*teardown)(struct ScrFfiTable *table);
|
|
1360
|
+
} ScrFfiTable;
|
|
1361
|
+
|
|
1362
|
+
void scr_ffi_link(ScrFfiTable *table);
|
|
1363
|
+
void scr_ffi_retain(ScrFfiTable *table, ScrClosure *callback);
|
|
1364
|
+
void scr_ffi_retain_foreign(ScrFfiTable *table, ScrClosure *callback);
|
|
1365
|
+
/* Raw singleton registration, split around the native set call:
|
|
1366
|
+
* retain_slot pins the incoming closure BEFORE the call without touching
|
|
1367
|
+
* the current registration; commit_slot repoints the slot and retires the
|
|
1368
|
+
* superseded pins after the call returns. */
|
|
1369
|
+
void scr_ffi_retain_slot(ScrFfiTable *table, ScrClosure **slot, ScrClosure *callback);
|
|
1370
|
+
void scr_ffi_commit_slot(ScrFfiTable *table, ScrClosure *callback);
|
|
1371
|
+
/* Traps unless the registration exists — emitted BEFORE a native release
|
|
1372
|
+
* call so a bogus release cannot reach native code. */
|
|
1373
|
+
void scr_ffi_require(ScrFfiTable *table, ScrClosure *callback);
|
|
1374
|
+
void scr_ffi_release(ScrFfiTable *table, ScrClosure *callback);
|
|
1375
|
+
void scr_ffi_require_foreign(ScrFfiTable *table, ScrClosure *callback);
|
|
1376
|
+
void scr_ffi_release_foreign(ScrFfiTable *table, ScrClosure *callback);
|
|
1377
|
+
void scr_ffi_teardown(ScrFfiTable *table);
|
|
1378
|
+
void scr_ffi_teardown_all(void);
|
|
1379
|
+
|
|
1380
|
+
/* Format-5 foreign-thread delivery. A generated native trampoline creates a
|
|
1381
|
+
* plain malloc-backed call, stages scalar values or copied byte spans, and
|
|
1382
|
+
* posts it. It never touches closure RC, exception cells, fibers, or other
|
|
1383
|
+
* script-thread-only runtime state. The generated dispatch thunk runs later
|
|
1384
|
+
* on the event loop and materializes script strings/bytes there. */
|
|
1385
|
+
typedef struct ScrFfiCall ScrFfiCall;
|
|
1386
|
+
typedef void (*ScrFfiDispatchFn)(ScrClosure *callback, ScrFfiCall *call);
|
|
1387
|
+
|
|
1388
|
+
ScrFfiCall *scr_ffi_call_new(ScrFfiTable *table, ScrClosure *callback,
|
|
1389
|
+
ScrFfiDispatchFn dispatch, size_t nargs);
|
|
1390
|
+
void scr_ffi_call_set_f64(ScrFfiCall *call, size_t index, double value);
|
|
1391
|
+
void scr_ffi_call_set_bool(ScrFfiCall *call, size_t index, uint8_t value);
|
|
1392
|
+
void scr_ffi_call_set_u8(ScrFfiCall *call, size_t index, uint8_t value);
|
|
1393
|
+
void scr_ffi_call_set_u32(ScrFfiCall *call, size_t index, uint32_t value);
|
|
1394
|
+
void scr_ffi_call_set_i32(ScrFfiCall *call, size_t index, int32_t value);
|
|
1395
|
+
void scr_ffi_call_copy_cstring(ScrFfiCall *call, size_t index, const char *value);
|
|
1396
|
+
void scr_ffi_call_copy_string(ScrFfiCall *call, size_t index,
|
|
1397
|
+
const uint8_t *value, size_t len);
|
|
1398
|
+
void scr_ffi_call_copy_bytes(ScrFfiCall *call, size_t index,
|
|
1399
|
+
const uint8_t *value, size_t len);
|
|
1400
|
+
void scr_ffi_post(ScrFfiCall *call);
|
|
1401
|
+
|
|
1402
|
+
double scr_ffi_call_get_f64(const ScrFfiCall *call, size_t index);
|
|
1403
|
+
bool scr_ffi_call_get_bool(const ScrFfiCall *call, size_t index);
|
|
1404
|
+
double scr_ffi_call_get_u8(const ScrFfiCall *call, size_t index);
|
|
1405
|
+
double scr_ffi_call_get_u32(const ScrFfiCall *call, size_t index);
|
|
1406
|
+
double scr_ffi_call_get_i32(const ScrFfiCall *call, size_t index);
|
|
1407
|
+
const uint8_t *scr_ffi_call_get_data(const ScrFfiCall *call, size_t index);
|
|
1408
|
+
size_t scr_ffi_call_get_len(const ScrFfiCall *call, size_t index);
|
|
1409
|
+
|
|
1410
|
+
void scr_ffi_install(void);
|
|
1411
|
+
void scr_ffi_stop(void);
|
|
1412
|
+
void scr_ffi_teardown_foreign(ScrFfiTable *table);
|
|
1413
|
+
void scr_loop_set_ffi(bool (*pending)(void), bool (*dispatch)(void),
|
|
1414
|
+
int (*pollfd)(void), void (*stop)(void));
|
|
1415
|
+
|
|
1319
1416
|
/* ── unions ─────────────────────────────────────────────────────────
|
|
1320
1417
|
* A union value (`A | B`) is an IMMUTABLE tagged box: a refcounted header,
|
|
1321
1418
|
* the arm's tag (its index in the compiler's canonical arm order), and one
|
|
@@ -4735,6 +4832,9 @@ ScrJsval *scr_jsval_from_bytes(const ScrBytes *b);
|
|
|
4735
4832
|
* THROWS Node's "Invalid typed array length" RangeError catchably and
|
|
4736
4833
|
* returns NULL with the exception pending). */
|
|
4737
4834
|
ScrBytes *scr_bytes_new(ScrBytesElem elem, double n); /* +1 */
|
|
4835
|
+
/* Native callback boundary: exact owned u8 copy. NULL with len == 0 is an
|
|
4836
|
+
* empty span. */
|
|
4837
|
+
ScrBytes *scr_bytes_from_data(const uint8_t *data, size_t len); /* +1 */
|
|
4738
4838
|
|
|
4739
4839
|
/* `new Uint8Array(src)` / Buffer.from(u8): a same-elem-kind copy (the
|
|
4740
4840
|
* compiler fences cross-kind construction). Borrows src. Never throws. */
|
|
@@ -5493,18 +5593,25 @@ long scr_secure_ctx_live_count(void);
|
|
|
5493
5593
|
#endif
|
|
5494
5594
|
|
|
5495
5595
|
/* ── node:tls, the CA-store introspection slice (scr_tls_ca.c — its own
|
|
5496
|
-
* unit and link gate;
|
|
5497
|
-
*
|
|
5498
|
-
*
|
|
5499
|
-
*
|
|
5500
|
-
*
|
|
5501
|
-
*
|
|
5502
|
-
*
|
|
5503
|
-
*
|
|
5504
|
-
*
|
|
5505
|
-
* with strictEqual). */
|
|
5596
|
+
* unit and link gate; NO mbedTLS, so a getCACertificates-only binary never
|
|
5597
|
+
* builds the archive; cc.ts also compiles it whenever scr_tls.c does). The
|
|
5598
|
+
* HOST roots (Windows' system certificate stores, the established bundle probe on
|
|
5599
|
+
* POSIX) stand in for both Node's compiled-in Mozilla roots ('bundled',
|
|
5600
|
+
* rootCertificates) and the platform store ('system') — the established
|
|
5601
|
+
* SEMANTICS divergence, extended to introspection; 'extra' uses the
|
|
5602
|
+
* NODE_EXTRA_CA_CERTS file captured before user code runs. Arrays are cached
|
|
5603
|
+
* per type (+1 retained answers each call — Node's own caching, and the
|
|
5604
|
+
* identity the suite pins with strictEqual). */
|
|
5506
5605
|
void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */
|
|
5507
5606
|
bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */
|
|
5607
|
+
#ifdef _WIN32
|
|
5608
|
+
/* Enumerates Windows' trusted ROOT, intermediate CA, and TrustedPeople store
|
|
5609
|
+
* locations, yielding only certificates whose effective Windows EKU policy
|
|
5610
|
+
* permits TLS server authentication. DER is borrowed for the call. */
|
|
5611
|
+
typedef void (*ScrTlsCaWindowsCertFn)(void *ctx, const unsigned char *der, size_t len);
|
|
5612
|
+
bool scr_tls_ca_windows_cert_server_auth(const void *cert_context);
|
|
5613
|
+
void scr_tls_ca_windows_certs(ScrTlsCaWindowsCertFn fn, void *ctx);
|
|
5614
|
+
#endif
|
|
5508
5615
|
ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
|
|
5509
5616
|
ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
|
|
5510
5617
|
/* Replaces the 'default' set: entries filter to their PEM certificate
|
package/src/scr_tls.c
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
* head buffers in the socket's write buffer until the handshake
|
|
57
57
|
* establishes (the mid-connect buffering path, reused verbatim).
|
|
58
58
|
* rejectUnauthorized:false maps to VERIFY_NONE; `ca` replaces the trust
|
|
59
|
-
* anchors; with neither,
|
|
59
|
+
* anchors; with neither, the host trust store stands in for Node's bundled
|
|
60
60
|
* Mozilla roots (SEMANTICS.md documents the divergence). Verify
|
|
61
61
|
* failures surface as Node-shaped 'error' messages on the request
|
|
62
62
|
* ("self-signed certificate", "self-signed certificate in certificate
|
|
@@ -336,15 +336,15 @@ typedef struct ScrTlsCli {
|
|
|
336
336
|
bool verify_recorded;
|
|
337
337
|
} ScrTlsCli;
|
|
338
338
|
|
|
339
|
-
/* The default trust anchors when no `ca` option is given: the
|
|
340
|
-
*
|
|
341
|
-
* NODE_EXTRA_CA_CERTS.
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* bundle
|
|
339
|
+
/* The default trust anchors when no `ca` option is given: the host trust
|
|
340
|
+
* store, standing in for Node's compiled-in Mozilla roots, plus
|
|
341
|
+
* NODE_EXTRA_CA_CERTS. Windows certificates live as DER entries across the
|
|
342
|
+
* machine, enterprise, current-user, and policy ROOT/CA/TrustedPeople store
|
|
343
|
+
* locations; mbedTLS copies each successfully parsed entry into its own chain.
|
|
344
|
+
* POSIX hosts probe the established bundle spellings — /etc/ssl/cert.pem
|
|
345
|
+
* first (macOS ships it; Alpine links it), then Debian/Ubuntu's
|
|
346
|
+
* ca-certificates.crt, Fedora/RHEL's ca-bundle.crt, and openSUSE's
|
|
347
|
+
* ca-bundle.pem. */
|
|
348
348
|
static mbedtls_x509_crt scr_tls_system_roots;
|
|
349
349
|
static bool scr_tls_system_roots_loaded = false;
|
|
350
350
|
|
|
@@ -356,6 +356,12 @@ static bool scr_tls_system_roots_loaded = false;
|
|
|
356
356
|
static mbedtls_x509_crt scr_tls_override_roots;
|
|
357
357
|
static uint64_t scr_tls_override_parsed_gen = 0;
|
|
358
358
|
|
|
359
|
+
#ifdef _WIN32
|
|
360
|
+
static void scr_tls_add_windows_ca(void *ctx, const unsigned char *der, size_t len) {
|
|
361
|
+
(void)mbedtls_x509_crt_parse_der((mbedtls_x509_crt *)ctx, der, len);
|
|
362
|
+
}
|
|
363
|
+
#endif
|
|
364
|
+
|
|
359
365
|
static mbedtls_x509_crt *scr_tls_system_ca(void) {
|
|
360
366
|
const char *pem = NULL;
|
|
361
367
|
size_t pem_len = 0;
|
|
@@ -375,6 +381,12 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
|
|
|
375
381
|
if (!scr_tls_system_roots_loaded) {
|
|
376
382
|
scr_tls_system_roots_loaded = true;
|
|
377
383
|
mbedtls_x509_crt_init(&scr_tls_system_roots);
|
|
384
|
+
#ifdef _WIN32
|
|
385
|
+
/* The shared enumerator applies Windows' effective server-auth EKU
|
|
386
|
+
* policy before yielding DER. mbedTLS copies every successfully parsed
|
|
387
|
+
* entry, so the store contexts may die immediately after each callback. */
|
|
388
|
+
scr_tls_ca_windows_certs(scr_tls_add_windows_ca, &scr_tls_system_roots);
|
|
389
|
+
#else
|
|
378
390
|
static const char *const bundles[] = {
|
|
379
391
|
"/etc/ssl/cert.pem", /* macOS, Alpine */
|
|
380
392
|
"/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
|
|
@@ -384,6 +396,7 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
|
|
|
384
396
|
for (size_t i = 0; i < sizeof bundles / sizeof bundles[0]; i++) {
|
|
385
397
|
if (mbedtls_x509_crt_parse_file(&scr_tls_system_roots, bundles[i]) == 0) break;
|
|
386
398
|
}
|
|
399
|
+
#endif
|
|
387
400
|
const char *extra = NULL;
|
|
388
401
|
size_t extra_len = 0;
|
|
389
402
|
if (scr_tls_ca_extra_pem(&extra, &extra_len) && extra_len > 0) {
|
package/src/scr_tls_ca.c
CHANGED
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
* rootCertificates, and setDefaultCACertificates (scr_runtime.h has the
|
|
3
3
|
* contracts). Its own link gate ("tlsca." libCalls), deliberately free
|
|
4
4
|
* of mbedTLS: everything here is PEM-BLOCK bookkeeping over the host
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* alongside it) consults
|
|
8
|
-
* trust anchors, which is what
|
|
9
|
-
* semantics: the next https/tls dial
|
|
5
|
+
* roots (plus crypt32 store access on Windows), so a program that only
|
|
6
|
+
* inspects the CA store never builds the TLS archive. scr_tls.c (when it
|
|
7
|
+
* links — cc.ts compiles this unit alongside it) consults
|
|
8
|
+
* scr_tls_ca_default_override for its client trust anchors, which is what
|
|
9
|
+
* makes setDefaultCACertificates LIVE semantics: the next https/tls dial
|
|
10
|
+
* verifies against the replaced set.
|
|
10
11
|
*
|
|
11
12
|
* Divergences, documented: the host bundle stands in for Node's
|
|
12
13
|
* compiled-in Mozilla roots ('bundled' and rootCertificates) AND for the
|
|
13
|
-
* platform store ('system') —
|
|
14
|
-
* scr_tls.c's anchors
|
|
15
|
-
* shaped (a well-formed block with corrupt DER
|
|
16
|
-
* Node's X509 parse would drop it — such a cert
|
|
17
|
-
* instead, the same observable outcome one step
|
|
18
|
-
* byte-exact over the extracted block where Node
|
|
19
|
-
* identities; re-encoded duplicates of one certificate
|
|
20
|
-
* entries, which no equality the tests observe can distinguish
|
|
21
|
-
* parser. */
|
|
14
|
+
* platform store ('system') — Windows' system certificate stores and the POSIX
|
|
15
|
+
* bundle probe are the same sources scr_tls.c's anchors use; and set-time
|
|
16
|
+
* validation is PEM-BLOCK shaped (a well-formed block with corrupt DER
|
|
17
|
+
* inside is kept here where Node's X509 parse would drop it — such a cert
|
|
18
|
+
* then fails verification instead, the same observable outcome one step
|
|
19
|
+
* later). Deduplication is byte-exact over the extracted block where Node
|
|
20
|
+
* compares parsed X509 identities; re-encoded duplicates of one certificate
|
|
21
|
+
* stay distinct entries, which no equality the tests observe can distinguish
|
|
22
|
+
* without a parser. */
|
|
22
23
|
#include "scr_runtime.h"
|
|
23
24
|
|
|
24
25
|
#include <stdio.h>
|
|
25
26
|
#include <stdlib.h>
|
|
26
27
|
#include <string.h>
|
|
28
|
+
#ifdef _WIN32
|
|
29
|
+
#include <windows.h>
|
|
30
|
+
#include <wincrypt.h>
|
|
31
|
+
#endif
|
|
27
32
|
|
|
28
33
|
/* The per-type caches: +1 held here for the process lifetime (an atexit
|
|
29
34
|
* teardown releases them so the RC audit stays clean). 'default' also
|
|
@@ -154,9 +159,121 @@ bool scr_tls_ca_extra_pem(const char **pem, size_t *len) {
|
|
|
154
159
|
return true;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
|
|
162
|
+
#ifdef _WIN32
|
|
163
|
+
/* Windows can restrict a certificate's purposes in a store property that is
|
|
164
|
+
* not part of its DER. CertGetEnhancedKeyUsage with flags 0 intersects that
|
|
165
|
+
* property with the encoded EKU extension. An empty result means either all
|
|
166
|
+
* purposes (CRYPT_E_NOT_FOUND) or no purposes (ERROR_SUCCESS); every other
|
|
167
|
+
* API failure is fail-closed too. */
|
|
168
|
+
bool scr_tls_ca_windows_cert_server_auth(const void *cert_context) {
|
|
169
|
+
PCCERT_CONTEXT cert = (PCCERT_CONTEXT)cert_context;
|
|
170
|
+
DWORD usage_len = 0;
|
|
171
|
+
if (!CertGetEnhancedKeyUsage(cert, 0, NULL, &usage_len) || usage_len == 0) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
CERT_ENHKEY_USAGE *usage = malloc((size_t)usage_len);
|
|
175
|
+
if (usage == NULL) scr_ca_oom();
|
|
176
|
+
if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_len)) {
|
|
177
|
+
free(usage);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
DWORD usage_error = GetLastError();
|
|
181
|
+
bool allowed = usage->cUsageIdentifier == 0 &&
|
|
182
|
+
usage_error == CRYPT_E_NOT_FOUND;
|
|
183
|
+
for (DWORD i = 0; !allowed && usage->rgpszUsageIdentifier != NULL &&
|
|
184
|
+
i < usage->cUsageIdentifier; i++) {
|
|
185
|
+
const char *oid = usage->rgpszUsageIdentifier[i];
|
|
186
|
+
allowed = oid != NULL &&
|
|
187
|
+
(strcmp(oid, szOID_PKIX_KP_SERVER_AUTH) == 0 ||
|
|
188
|
+
strcmp(oid, szOID_ANY_ENHANCED_KEY_USAGE) == 0);
|
|
189
|
+
}
|
|
190
|
+
free(usage);
|
|
191
|
+
return allowed;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static void scr_tls_ca_windows_certs_at(
|
|
195
|
+
DWORD location, const char *store_name, HCERTSTORE seen,
|
|
196
|
+
ScrTlsCaWindowsCertFn fn, void *ctx) {
|
|
197
|
+
HCERTSTORE store = CertOpenStore(
|
|
198
|
+
CERT_STORE_PROV_SYSTEM_A, 0, 0,
|
|
199
|
+
location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG,
|
|
200
|
+
store_name);
|
|
201
|
+
if (store == NULL) return;
|
|
202
|
+
PCCERT_CONTEXT cert = NULL;
|
|
203
|
+
/* CertEnumCertificatesInStore frees the previous context on every call,
|
|
204
|
+
* including the final NULL result. Each callback must copy DER it keeps. */
|
|
205
|
+
while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) {
|
|
206
|
+
/* The logical stores overlap (notably CurrentUser inherits machine
|
|
207
|
+
* roots). CERT_STORE_ADD_NEW turns the memory store into a process-local
|
|
208
|
+
* identity set: only the first allowed context reaches the consumer. */
|
|
209
|
+
if (scr_tls_ca_windows_cert_server_auth(cert) &&
|
|
210
|
+
CertAddCertificateContextToStore(
|
|
211
|
+
seen, cert, CERT_STORE_ADD_NEW, NULL)) {
|
|
212
|
+
fn(ctx, cert->pbCertEncoded, (size_t)cert->cbCertEncoded);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
(void)CertCloseStore(store, 0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
void scr_tls_ca_windows_certs(ScrTlsCaWindowsCertFn fn, void *ctx) {
|
|
219
|
+
/* Match Node's Windows system-certificate sources: roots and intermediate
|
|
220
|
+
* CAs from all five applicable locations, plus explicitly trusted server
|
|
221
|
+
* certificates from the three local-machine TrustedPeople locations.
|
|
222
|
+
* A memory store deduplicates their overlapping contents. */
|
|
223
|
+
typedef struct ScrTlsCaWindowsStore {
|
|
224
|
+
DWORD location;
|
|
225
|
+
const char *name;
|
|
226
|
+
} ScrTlsCaWindowsStore;
|
|
227
|
+
static const ScrTlsCaWindowsStore stores[] = {
|
|
228
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE, "ROOT"},
|
|
229
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "ROOT"},
|
|
230
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "ROOT"},
|
|
231
|
+
{CERT_SYSTEM_STORE_CURRENT_USER, "ROOT"},
|
|
232
|
+
{CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, "ROOT"},
|
|
233
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE, "CA"},
|
|
234
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "CA"},
|
|
235
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "CA"},
|
|
236
|
+
{CERT_SYSTEM_STORE_CURRENT_USER, "CA"},
|
|
237
|
+
{CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, "CA"},
|
|
238
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE, "TrustedPeople"},
|
|
239
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "TrustedPeople"},
|
|
240
|
+
{CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "TrustedPeople"},
|
|
241
|
+
};
|
|
242
|
+
HCERTSTORE seen = CertOpenStore(
|
|
243
|
+
CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
|
|
244
|
+
if (seen == NULL) return;
|
|
245
|
+
for (size_t i = 0; i < sizeof stores / sizeof stores[0]; i++) {
|
|
246
|
+
scr_tls_ca_windows_certs_at(
|
|
247
|
+
stores[i].location, stores[i].name, seen, fn, ctx);
|
|
248
|
+
}
|
|
249
|
+
(void)CertCloseStore(seen, 0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static void scr_ca_push_windows_cert(void *ctx, const unsigned char *der, size_t len) {
|
|
253
|
+
if (len > UINT32_MAX) return;
|
|
254
|
+
DWORD pem_cap = 0;
|
|
255
|
+
DWORD flags = CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR;
|
|
256
|
+
if (!CryptBinaryToStringA(der, (DWORD)len, flags, NULL, &pem_cap) ||
|
|
257
|
+
pem_cap == 0) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
char *pem = malloc((size_t)pem_cap);
|
|
261
|
+
if (pem == NULL) scr_ca_oom();
|
|
262
|
+
DWORD pem_len = pem_cap;
|
|
263
|
+
if (CryptBinaryToStringA(der, (DWORD)len, flags, pem, &pem_len)) {
|
|
264
|
+
scr_arr_push_ref((ScrArr *)ctx, scr_str_new(pem, (size_t)pem_len));
|
|
265
|
+
}
|
|
266
|
+
free(pem);
|
|
267
|
+
}
|
|
268
|
+
#endif
|
|
269
|
+
|
|
270
|
+
/* The host roots, from the Windows logical store or the POSIX bundle probe
|
|
271
|
+
* shared with scr_tls.c. */
|
|
158
272
|
static ScrArr *scr_ca_load_host_bundle(void) {
|
|
159
273
|
ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 128);
|
|
274
|
+
#ifdef _WIN32
|
|
275
|
+
scr_tls_ca_windows_certs(scr_ca_push_windows_cert, arr);
|
|
276
|
+
#else
|
|
160
277
|
static const char *const bundles[] = {
|
|
161
278
|
"/etc/ssl/cert.pem", /* macOS, Alpine */
|
|
162
279
|
"/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
|
|
@@ -171,6 +288,7 @@ static ScrArr *scr_ca_load_host_bundle(void) {
|
|
|
171
288
|
free(text);
|
|
172
289
|
break; /* first bundle wins, like the anchor probe */
|
|
173
290
|
}
|
|
291
|
+
#endif
|
|
174
292
|
return arr;
|
|
175
293
|
}
|
|
176
294
|
|
package/vendor/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- Commit: 49e671dd52ff6791295d8161ad3b6da7dc5f6f9d
|
|
7
7
|
- License: ISC (copyright and permission notice reproduced in `src/scr_musl.c`)
|
|
8
8
|
|
|
9
|
-
The x86_64 register-save/restore and `makecontext` logic is adapted into `src/scr_musl.c`, rather than carried as a separate library, to supply the legacy `ucontext` functions that musl declares but does not implement. It is compiled
|
|
9
|
+
The x86_64 and AArch64 register-save/restore and `makecontext` logic is adapted into `src/scr_musl.c`, rather than carried as a separate library, to supply the legacy `ucontext` functions that musl declares but does not implement. It is compiled for the explicit `x86_64-linux-musl` and `aarch64-linux-musl` targets and uses musl's public `ucontext_t` layouts. The signal-mask compatibility wrapper is deliberately not included: scriptc fibers perform user-space register swaps and do not change a per-fiber signal mask.
|
|
10
10
|
|
|
11
11
|
## ryu/
|
|
12
12
|
|