@scriptc/runtime 0.0.25 → 0.0.27

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
package/src/scr_array.c CHANGED
@@ -8,7 +8,7 @@
8
8
  /* Live heap-array count for the RC audit lane (-DSCR_RC_AUDIT); same
9
9
  * contract as scr_str_live_count in scr_string.c. */
10
10
  #ifdef SCR_RC_AUDIT
11
- static long scr_live_arrays = 0;
11
+ static SCR_TL long scr_live_arrays = 0;
12
12
  long scr_arr_live_count(void) { return scr_live_arrays; }
13
13
  #endif
14
14
 
package/src/scr_assert.c CHANGED
@@ -158,9 +158,9 @@ void scr_assert_ok(bool pass, ScrStr *message) {
158
158
  * PAIR already being compared answers equal (the coinductive step —
159
159
  * Node's memo behavior exactly). The stack is global (comparisons never
160
160
  * interleave; the emitted walks cannot throw mid-compare). */
161
- static struct { const void *a, *b; } *g_deq_stack;
162
- static size_t g_deq_len;
163
- static size_t g_deq_cap;
161
+ static SCR_TL struct { const void *a, *b; } *g_deq_stack;
162
+ static SCR_TL size_t g_deq_len;
163
+ static SCR_TL size_t g_deq_cap;
164
164
 
165
165
  bool scr_assert_deq_enter(const void *a, const void *b) {
166
166
  for (size_t i = 0; i < g_deq_len; i++) {
@@ -1228,7 +1228,7 @@ typedef struct {
1228
1228
  ScrStr *val; /* owned: the string value, or the regex's /source/flags */
1229
1229
  } ScrShapeSlot;
1230
1230
 
1231
- static struct {
1231
+ static SCR_TL struct {
1232
1232
  ScrError *err; /* borrowed — alive across the straight-line sequence */
1233
1233
  ScrShapeSlot slots[3];
1234
1234
  } scr_shape;
package/src/scr_async.c CHANGED
@@ -37,6 +37,8 @@
37
37
  * served by a capped nanosleep — dispatch at the next turn's top, the
38
38
  * cap bounding signal/stdin latency instead of a pollable wake fd. */
39
39
  #include <windows.h>
40
+ #elif defined(__wasi__)
41
+ #include <poll.h>
40
42
  #else
41
43
  #include <poll.h>
42
44
  #include <pthread.h>
@@ -45,6 +47,23 @@
45
47
  #include <unistd.h>
46
48
  #endif
47
49
 
50
+ #ifdef __wasi__
51
+ /* WASI Preview 1 has no process-spawn capability. The child unit is not
52
+ * linked for this target, but the generic loop keeps its quiescence hooks;
53
+ * their empty implementation makes the absence explicit at link time
54
+ * without weakening async/timer support. */
55
+ bool scr_children_pending(void) { return false; }
56
+ bool scr_children_reffed_pending(void) { return false; }
57
+ bool scr_children_failed_pending(void) { return false; }
58
+ void scr_children_poll(void) {}
59
+ int scr_children_wake_fd(void) { return -1; }
60
+ bool scr_children_wait(double max_wait_ms) {
61
+ (void)max_wait_ms;
62
+ return false;
63
+ }
64
+ void scr_children_teardown(void) {}
65
+ #endif
66
+
48
67
  #if defined(__has_feature)
49
68
  #if __has_feature(address_sanitizer)
50
69
  #define SCR_ASAN_FIBERS 1
@@ -274,12 +293,18 @@ void scr_promise_trace_v(void *p, ScrTraceVisit visit, void *ctx) {
274
293
  * so the slot only needs to name the destination; `from` is unused. */
275
294
  #ifdef _WIN32
276
295
  typedef void *ScrCtx;
296
+ #elif defined(__wasi__)
297
+ typedef unsigned char ScrCtx;
277
298
  #else
278
299
  typedef ucontext_t ScrCtx;
279
300
  #endif
280
301
 
281
302
  struct ScrFiber {
282
303
  ScrCtx ctx;
304
+ /* wasm32 uses LLVM's stackless switched-coroutine frame instead of a
305
+ * native saved stack. The compiler emits these two generic wrappers in
306
+ * every WASI module. */
307
+ void *coro;
283
308
  ScrCtx *return_to; /* whoever resumed us last (spawner or the loop) */
284
309
  char *stack; /* POSIX only; Windows fibers own their stack (NULL) */
285
310
  ScrPromise *promise; /* the promise this fiber settles (owned +1); NULL
@@ -350,6 +375,19 @@ long scr_abandoned_fiber_count(void) { return scr_fibers_abandoned; }
350
375
  bool scr_on_fiber(void) { return scr_current != NULL; }
351
376
  void *scr_fiber_self(void) { return scr_current; }
352
377
 
378
+ #ifdef __wasi__
379
+ extern void scr_wasi_coro_resume(void *handle);
380
+ extern void scr_wasi_coro_destroy(void *handle);
381
+
382
+ void scr_wasi_coro_started(void *handle) {
383
+ if (scr_current == NULL) {
384
+ fputs("scriptc: internal error: coroutine started outside a fiber\n", stderr);
385
+ abort();
386
+ }
387
+ scr_current->coro = handle;
388
+ }
389
+ #endif
390
+
353
391
  /* Microtask queue: ready fibers, FIFO. */
354
392
  static ScrFiber **scr_ready = NULL;
355
393
  static size_t scr_ready_head = 0, scr_ready_len = 0, scr_ready_cap = 0;
@@ -867,6 +905,10 @@ static void scr_switch(ScrCtx *from, ScrCtx *to, ScrFiber *to_fiber) {
867
905
  * fiber object — `from` has nothing to record. */
868
906
  (void)from;
869
907
  SwitchToFiber(*to);
908
+ #elif defined(__wasi__)
909
+ (void)from;
910
+ (void)to;
911
+ scr_trap("scriptc: internal error: native stack switch reached on WASI\n");
870
912
  #elif defined(SCR_ASAN_FIBERS)
871
913
  void **save = from_fiber ? &from_fiber->fake_stack : &scr_main_fake_stack;
872
914
  const void *bottom = to_fiber ? to_fiber->stack : NULL;
@@ -1154,6 +1196,8 @@ static void scr_trampoline(void) {
1154
1196
  static void scr_fiber_destroy(ScrFiber *f) {
1155
1197
  #ifdef _WIN32
1156
1198
  DeleteFiber(f->ctx);
1199
+ #elif defined(__wasi__)
1200
+ if (f->coro != NULL) scr_wasi_coro_destroy(f->coro);
1157
1201
  #endif
1158
1202
  scr_als_ctx_release(f->als);
1159
1203
  free(f->stack);
@@ -1179,6 +1223,22 @@ ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack) {
1179
1223
  ScrCtx here = scr_win_self();
1180
1224
  f->ctx = CreateFiber(SCR_FIBER_STACK, scr_trampoline, NULL);
1181
1225
  if (f->ctx == NULL) scr_oom();
1226
+ #elif defined(__wasi__)
1227
+ ScrFiber *spawner = scr_current;
1228
+ scr_current = f;
1229
+ scr_exc_swap_cell(&f->exc);
1230
+ scr_als_active = &f->als;
1231
+ entry(f, argpack); /* eager prefix; returns at suspend/final suspend */
1232
+ scr_current = spawner;
1233
+ scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
1234
+ scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
1235
+ ScrPromise *result = scr_promise_retain(f->promise);
1236
+ if (f->done) {
1237
+ scr_promise_release(f->promise);
1238
+ scr_fiber_destroy(f);
1239
+ scr_fibers_live--;
1240
+ }
1241
+ return result;
1182
1242
  #else
1183
1243
  f->stack = malloc(SCR_FIBER_STACK);
1184
1244
  if (!f->stack) scr_oom();
@@ -1190,6 +1250,7 @@ ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack) {
1190
1250
 
1191
1251
  ucontext_t here;
1192
1252
  #endif
1253
+ #ifndef __wasi__
1193
1254
  f->return_to = &here;
1194
1255
  ScrFiber *spawner = scr_current;
1195
1256
  scr_switch(&here, &f->ctx, f);
@@ -1209,6 +1270,42 @@ ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack) {
1209
1270
  scr_fibers_live--;
1210
1271
  }
1211
1272
  return result;
1273
+ #endif
1274
+ }
1275
+
1276
+ /* A runtime-authored C continuation whose first operation awaits one known
1277
+ * dependency. Native fibers can park normally. wasm32 has no resumable C
1278
+ * stack, so queue the fiber only after the dependency settles; the entry's
1279
+ * await then extracts the result without attempting a stack switch. The
1280
+ * READY queue supplies the settled-await microtask hop in both cases. */
1281
+ ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
1282
+ void (*entry)(ScrFiber *, void *),
1283
+ void *argpack) {
1284
+ #ifndef __wasi__
1285
+ (void)dependency;
1286
+ return scr_async_spawn(entry, argpack);
1287
+ #else
1288
+ ScrFiber *f = calloc(1, sizeof *f);
1289
+ if (!f) scr_oom();
1290
+ f->promise = scr_promise_new();
1291
+ f->entry = entry;
1292
+ f->argpack = argpack;
1293
+ f->als = scr_als_ctx_retain(*scr_als_active);
1294
+ scr_fibers_live++;
1295
+
1296
+ if (dependency->state == SCR_PROM_PENDING) {
1297
+ if (dependency->nwaiters == dependency->waiters_cap) {
1298
+ dependency->waiters_cap = dependency->waiters_cap ? dependency->waiters_cap * 2 : 4;
1299
+ dependency->waiters = realloc(
1300
+ dependency->waiters, dependency->waiters_cap * sizeof *dependency->waiters);
1301
+ if (!dependency->waiters) scr_oom();
1302
+ }
1303
+ dependency->waiters[dependency->nwaiters++] = f;
1304
+ } else {
1305
+ scr_ready_push(f);
1306
+ }
1307
+ return scr_promise_retain(f->promise);
1308
+ #endif
1212
1309
  }
1213
1310
 
1214
1311
  /* Parks the current fiber on `p` until it settles. Only fibers await. */
@@ -1228,6 +1325,42 @@ static void scr_await_park(ScrPromise *p) {
1228
1325
  /* Resumed by the loop: return_to must now point at the loop's context. */
1229
1326
  }
1230
1327
 
1328
+ #ifdef __wasi__
1329
+ /* The emitted coroutine suspends immediately after these preparation
1330
+ * calls. Settled promises still queue one ready turn; pending promises own
1331
+ * the continuation through their waiter list. */
1332
+ void scr_wasi_await_prepare(ScrFiber *self, ScrPromise *p) {
1333
+ if (p->state == SCR_PROM_PENDING) {
1334
+ if (p->nwaiters == p->waiters_cap) {
1335
+ p->waiters_cap = p->waiters_cap ? p->waiters_cap * 2 : 4;
1336
+ p->waiters = realloc(p->waiters, p->waiters_cap * sizeof *p->waiters);
1337
+ if (!p->waiters) scr_oom();
1338
+ }
1339
+ p->waiters[p->nwaiters++] = self;
1340
+ } else {
1341
+ scr_ready_push(self);
1342
+ }
1343
+ }
1344
+
1345
+ void scr_wasi_await_hop_prepare(ScrFiber *self) { scr_ready_push(self); }
1346
+
1347
+ bool scr_wasi_module_await_prepare(ScrFiber *self, ScrPromise *p) {
1348
+ if (p->state != SCR_PROM_PENDING) return false;
1349
+ scr_wasi_await_prepare(self, p);
1350
+ return true;
1351
+ }
1352
+
1353
+ void scr_wasi_async_finish(ScrFiber *self) { scr_fiber_finish(self); }
1354
+
1355
+ void scr_wasi_gen_finish(ScrFiber *self) {
1356
+ if (scr_exc_genret_pending()) {
1357
+ scr_exc_clear();
1358
+ scr_gen_ret_to_out(self->gen);
1359
+ }
1360
+ scr_fiber_finish(self);
1361
+ }
1362
+ #endif
1363
+
1231
1364
  /* One microtask hop: park the current fiber on the READY queue itself and
1232
1365
  * yield. JS's `await` ALWAYS suspends — even on an already-settled promise
1233
1366
  * the continuation runs as a microtask — so without this hop an awaiter of
@@ -1268,8 +1401,15 @@ static void scr_promise_rethrow(ScrPromise *p) {
1268
1401
 
1269
1402
  /* Await result extraction. Rejection re-throws into the awaiter. */
1270
1403
  static bool scr_await_settled(ScrPromise *p) {
1404
+ #ifdef __wasi__
1405
+ if (p->state == SCR_PROM_PENDING) {
1406
+ fputs("scriptc: internal error: resumed before awaited promise settled\n", stderr);
1407
+ abort();
1408
+ }
1409
+ #else
1271
1410
  if (p->state != SCR_PROM_PENDING) scr_await_yield();
1272
1411
  while (p->state == SCR_PROM_PENDING) scr_await_park(p);
1412
+ #endif
1273
1413
  scr_prom_observe(p);
1274
1414
  if (p->state == SCR_PROM_REJECTED) {
1275
1415
  scr_promise_rethrow(p);
@@ -1295,7 +1435,9 @@ void scr_await_void(ScrPromise *p) { scr_await_settled(p); }
1295
1435
  * dependency still parks this module fiber, and rejection propagates into
1296
1436
  * it exactly like an ordinary await. */
1297
1437
  void scr_module_await(ScrPromise *p) {
1438
+ #ifndef __wasi__
1298
1439
  while (p->state == SCR_PROM_PENDING) scr_await_park(p);
1440
+ #endif
1299
1441
  scr_prom_observe(p);
1300
1442
  if (p->state == SCR_PROM_REJECTED) scr_promise_rethrow(p);
1301
1443
  }
@@ -1524,7 +1666,7 @@ ScrPromise *scr_fsp_rename(ScrStr *oldpath, ScrStr *newpath) {
1524
1666
  * libuv's default filesystem concurrency without creating one OS thread per
1525
1667
  * request. A platform mutex publishes each result and the syscall's filesystem
1526
1668
  * effects before the loop removes it from the completion queue. */
1527
- #ifndef SCR_LIB
1669
+ #if !defined(SCR_LIB) && !defined(__wasi__)
1528
1670
  typedef struct ScrFsRenameOp {
1529
1671
  ScrStr *oldpath;
1530
1672
  ScrStr *newpath;
@@ -1678,6 +1820,49 @@ static bool scr_fs_renames_dispatch(void) {
1678
1820
  scr_fs_rename_op_release(op);
1679
1821
  return true;
1680
1822
  }
1823
+ #elif defined(__wasi__)
1824
+ /* WASI Preview 1 has no threads, but rename itself is available. Queue the
1825
+ * request and perform it on the next loop turn so callback timing remains
1826
+ * asynchronous and the request remains a liveness handle. */
1827
+ typedef struct ScrFsRenameOp {
1828
+ ScrStr *oldpath;
1829
+ ScrStr *newpath;
1830
+ ScrClosure *cb;
1831
+ ScrFsRenameFn fn;
1832
+ struct ScrFsRenameOp *next;
1833
+ } ScrFsRenameOp;
1834
+
1835
+ static ScrFsRenameOp *scr_fs_rename_done = NULL;
1836
+ static ScrFsRenameOp **scr_fs_rename_done_tail = &scr_fs_rename_done;
1837
+ static size_t scr_fs_rename_pending_count = 0;
1838
+
1839
+ static bool scr_fs_renames_pending(void) { return scr_fs_rename_pending_count != 0; }
1840
+
1841
+ static bool scr_fs_renames_dispatch(void) {
1842
+ ScrFsRenameOp *op = scr_fs_rename_done;
1843
+ if (op == NULL) return false;
1844
+ scr_fs_rename_done = op->next;
1845
+ if (scr_fs_rename_done == NULL) scr_fs_rename_done_tail = &scr_fs_rename_done;
1846
+ scr_fs_rename_pending_count--;
1847
+
1848
+ int error = scr_fs_rename_raw(op->oldpath, op->newpath);
1849
+ ScrCaught *caught = NULL;
1850
+ ScrError *err = NULL;
1851
+ if (error != 0) {
1852
+ scr_fs_rename_error(error, op->oldpath, op->newpath);
1853
+ caught = scr_exc_take();
1854
+ if (caught && caught->kind == SCR_EXC_OBJ && scr_error_is(caught->payload)) {
1855
+ err = (ScrError *)caught->payload;
1856
+ }
1857
+ }
1858
+ op->fn(op->cb, err);
1859
+ scr_caught_release(caught);
1860
+ scr_str_release(op->oldpath);
1861
+ scr_str_release(op->newpath);
1862
+ scr_closure_release(op->cb);
1863
+ free(op);
1864
+ return true;
1865
+ }
1681
1866
  #else
1682
1867
  static bool scr_fs_renames_pending(void) { return false; }
1683
1868
  static bool scr_fs_renames_dispatch(void) { return false; }
@@ -1685,10 +1870,20 @@ static bool scr_fs_renames_dispatch(void) { return false; }
1685
1870
 
1686
1871
  void scr_fs_rename_async(ScrStr *oldpath, ScrStr *newpath,
1687
1872
  ScrClosure *cb, ScrFsRenameFn fn) {
1688
- #ifdef SCR_LIB
1873
+ #if defined(SCR_LIB)
1689
1874
  (void)oldpath; (void)newpath; (void)fn;
1690
1875
  scr_closure_release(cb);
1691
- scr_trap("scriptc: internal error: fs.rename reached in a library build — please report this");
1876
+ scr_trap("scriptc: callback-style fs.rename is not supported by this target\n");
1877
+ #elif defined(__wasi__)
1878
+ ScrFsRenameOp *op = calloc(1, sizeof *op);
1879
+ if (!op) scr_trap("scriptc: out of memory\n");
1880
+ op->oldpath = scr_str_retain(oldpath);
1881
+ op->newpath = scr_str_retain(newpath);
1882
+ op->cb = cb;
1883
+ op->fn = fn;
1884
+ *scr_fs_rename_done_tail = op;
1885
+ scr_fs_rename_done_tail = &op->next;
1886
+ scr_fs_rename_pending_count++;
1692
1887
  #else
1693
1888
  if (!scr_fs_rename_shutdown_registered) {
1694
1889
  if (atexit(scr_fs_renames_shutdown) != 0) {
@@ -1893,7 +2088,25 @@ static void scr_resume_fiber(ScrFiber *f) {
1893
2088
  (void)scr_win_self();
1894
2089
  #endif
1895
2090
  f->return_to = &scr_loop_ctx;
2091
+ #ifdef __wasi__
2092
+ scr_current = f;
2093
+ scr_exc_swap_cell(&f->exc);
2094
+ scr_als_active = &f->als;
2095
+ if (f->coro != NULL) {
2096
+ scr_wasi_coro_resume(f->coro);
2097
+ } else {
2098
+ /* scr_async_spawn_after's runtime C continuation. Its dependency is
2099
+ * settled, so the entry can extract through scr_await_* without a
2100
+ * stack switch and then completes in this turn. */
2101
+ f->entry(f, f->argpack);
2102
+ scr_fiber_finish(f);
2103
+ }
2104
+ scr_current = NULL;
2105
+ scr_exc_swap_cell(NULL);
2106
+ scr_als_active = &scr_als_main_slot;
2107
+ #else
1896
2108
  scr_switch(&scr_loop_ctx, &f->ctx, f);
2109
+ #endif
1897
2110
  if (f->done) {
1898
2111
  scr_promise_release(f->promise);
1899
2112
  scr_fiber_destroy(f);
@@ -2222,8 +2435,9 @@ bool scr_loop_run(ScrPromise *top_level) {
2222
2435
  now = scr_now_ms();
2223
2436
  if (scr_ready_len > 0) continue; /* io callbacks woke fibers */
2224
2437
  } else if (evw || net || dgram || watch) {
2225
- #ifdef _WIN32
2226
- /* The win32 arm: no poll(2), so the sleep is a capped nanosleep and
2438
+ #if defined(_WIN32) || defined(__wasi__)
2439
+ /* The win32 arm, and WASI hosts whose poll_oneoff adapters do not
2440
+ * reliably wake for a closed inherited stdin pipe: the sleep is a capped nanosleep and
2227
2441
  * the next turn's dispatch does the work — signal flags (set by the
2228
2442
  * CRT handler on msvcrt's console-ctrl thread) and the stdin probe
2229
2443
  * at SCR_SIGNAL_POLL_MS granularity (scr_events.c), socket/dgram
@@ -2315,7 +2529,7 @@ bool scr_loop_run(ScrPromise *top_level) {
2315
2529
  * WNOHANG sweep at the next turn's top stays the reaper. */
2316
2530
  if (kids) (void)scr_children_wait(0);
2317
2531
  now = scr_now_ms();
2318
- #endif /* !_WIN32 */
2532
+ #endif /* !_WIN32 && !__wasi__ */
2319
2533
  } else if (kids && scr_children_wait(due > now ? due - now : 0)) {
2320
2534
  now = scr_now_ms(); /* woke early on an exit, or hit the deadline */
2321
2535
  } else {
@@ -2727,6 +2941,8 @@ ScrGen *scr_gen_new(void (*entry)(ScrFiber *, void *), void *argpack,
2727
2941
  #ifdef _WIN32
2728
2942
  f->ctx = CreateFiber(SCR_FIBER_STACK, scr_trampoline, NULL);
2729
2943
  if (f->ctx == NULL) scr_oom();
2944
+ #elif defined(__wasi__)
2945
+ f->ctx = 0;
2730
2946
  #else
2731
2947
  f->stack = malloc(SCR_FIBER_STACK);
2732
2948
  if (!f->stack) scr_oom();
@@ -2898,16 +3114,29 @@ static void scr_gen_switch_in(ScrGen *g) {
2898
3114
  g->state = SCR_GEN_RUNNING;
2899
3115
  #ifdef _WIN32
2900
3116
  ScrCtx here = scr_win_self();
3117
+ #elif defined(__wasi__)
3118
+ ScrCtx here = 0;
2901
3119
  #else
2902
3120
  ucontext_t here;
2903
3121
  #endif
2904
3122
  f->return_to = &here;
2905
3123
  ScrFiber *me = scr_current;
3124
+ #ifdef __wasi__
3125
+ scr_current = f;
3126
+ scr_exc_swap_cell(&f->exc);
3127
+ scr_als_active = &f->als;
3128
+ if (f->coro == NULL) f->entry(f, f->argpack);
3129
+ else scr_wasi_coro_resume(f->coro);
3130
+ scr_current = me;
3131
+ scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
3132
+ scr_als_active = me != NULL ? &me->als : &scr_als_main_slot;
3133
+ #else
2906
3134
  scr_switch(&here, &f->ctx, f);
2907
3135
  /* Back on the consumer: restore identity + the consumer's cell (the
2908
3136
  * yield/finish switch targeted NULL — main's cell). */
2909
3137
  scr_current = me;
2910
3138
  scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
3139
+ #endif
2911
3140
  if (f->done) {
2912
3141
  g->state = SCR_GEN_DONE;
2913
3142
  if (f->exc.kind != SCR_EXC_NONE) {
package/src/scr_bytes.c CHANGED
@@ -17,7 +17,7 @@
17
17
  #include <string.h>
18
18
 
19
19
  #ifdef SCR_RC_AUDIT
20
- static long scr_live_bytes = 0;
20
+ static SCR_TL long scr_live_bytes = 0;
21
21
  long scr_bytes_live_count(void) { return scr_live_bytes; }
22
22
  #endif
23
23
 
package/src/scr_child.c CHANGED
@@ -2292,7 +2292,7 @@ ScrStr *scr_exec_sync_core(ScrStr *cmd, ScrArr *args, const ScrExecOpts *o) {
2292
2292
  } else if (o->stderr_mode != 3) {
2293
2293
  posix_spawn_file_actions_addopen(&fa, 2, "/dev/null", O_WRONLY, 0);
2294
2294
  } /* mode 3 (inherit): the child keeps the parent's fd 2 */
2295
- #if defined(__APPLE__) || defined(__GLIBC__)
2295
+ #if defined(__APPLE__) || defined(__GLIBC__) || defined(SCR_MUSL)
2296
2296
  if (o->cwd != NULL) {
2297
2297
  posix_spawn_file_actions_addchdir_np(&fa, o->cwd->data);
2298
2298
  }
@@ -3180,7 +3180,7 @@ ScrChild *scr_spawn_opts(ScrStr *cmd, ScrArr *args, double in_mode,
3180
3180
  } else if ((int)err_mode == 3) {
3181
3181
  posix_spawn_file_actions_adddup2(&fa, err_pipe[1], 2);
3182
3182
  }
3183
- #if defined(__APPLE__) || defined(__GLIBC__)
3183
+ #if defined(__APPLE__) || defined(__GLIBC__) || defined(SCR_MUSL)
3184
3184
  if (cwd != NULL && cwd->len > 0) {
3185
3185
  posix_spawn_file_actions_addchdir_np(&fa, cwd->data);
3186
3186
  }
package/src/scr_closure.c CHANGED
@@ -5,8 +5,8 @@
5
5
  #include <string.h>
6
6
 
7
7
  #ifdef SCR_RC_AUDIT
8
- static long scr_live_boxes = 0;
9
- static long scr_live_closures = 0;
8
+ static SCR_TL long scr_live_boxes = 0;
9
+ static SCR_TL long scr_live_closures = 0;
10
10
  long scr_box_live_count(void) { return scr_live_boxes; }
11
11
  long scr_closure_live_count(void) { return scr_live_closures; }
12
12
  #endif
package/src/scr_console.c CHANGED
@@ -11,7 +11,7 @@
11
11
  /* Set by scr_async.c at loop exhaustion; lives here (unconditionally) so
12
12
  * binaries that link the console without the async runtime still link, and
13
13
  * plain builds satisfy scr_async's reference. */
14
- static long scr_abandoned_fibers = 0;
14
+ static SCR_TL long scr_abandoned_fibers = 0;
15
15
  void scr_note_abandoned_fibers(long n) { scr_abandoned_fibers = n; }
16
16
 
17
17
  #ifndef SCR_LIB
package/src/scr_cycle.c CHANGED
@@ -95,7 +95,7 @@ static void scr_cyc_oom(void) {
95
95
  }
96
96
 
97
97
  /* Live cycle-headered objects — what the full-pass trigger watches. */
98
- static size_t scr_cyc_live = 0;
98
+ static SCR_TL size_t scr_cyc_live = 0;
99
99
 
100
100
  void *scr_cyc_alloc(size_t size, ScrTraceFn trace, ScrCycFreeFn free_fn) {
101
101
  ScrCycHdr *h = calloc(1, sizeof(ScrCycHdr) + size);
@@ -137,25 +137,25 @@ static void scr_cyc_push(ScrVec *vec, void *obj) {
137
137
 
138
138
  /* ── the candidate-root buffers (one per generation) ──────────────────── */
139
139
 
140
- static ScrVec scr_roots[SCR_CYC_NGENS];
141
- static bool scr_collecting = false;
140
+ static SCR_TL ScrVec scr_roots[SCR_CYC_NGENS];
141
+ static SCR_TL bool scr_collecting = false;
142
142
 
143
143
  /* The candidates of the pass in flight, gathered across the generations it
144
144
  * collects so the phases can walk them without re-filtering the buffers. */
145
- static ScrVec scr_cands;
145
+ static SCR_TL ScrVec scr_cands;
146
146
 
147
147
  /* Nursery survivors awaiting promotion (see scr_scan_black). */
148
- static ScrVec scr_promote;
148
+ static SCR_TL ScrVec scr_promote;
149
149
 
150
150
  /* The gathered white set (freed after the walk completes). */
151
- static ScrVec scr_white;
151
+ static SCR_TL ScrVec scr_white;
152
152
 
153
153
  /* Cross-generation targets pinned by the white set (see scr_xg_pin_visit),
154
154
  * one entry per skipped edge. Drained after the teardowns. */
155
- static ScrVec scr_xgen;
155
+ static SCR_TL ScrVec scr_xgen;
156
156
 
157
157
  /* Objects above this generation are invisible to the pass in flight. */
158
- static unsigned scr_gen_limit = SCR_CYC_MATURE;
158
+ static SCR_TL unsigned scr_gen_limit = SCR_CYC_MATURE;
159
159
 
160
160
  static size_t scr_cyc_pass(unsigned gen_limit);
161
161
 
@@ -166,10 +166,10 @@ static size_t scr_cyc_pass(unsigned gen_limit);
166
166
  #define SCR_CYC_FULL_FLOOR 4096 /* ...but not below this many objects */
167
167
 
168
168
  /* Live count as of the end of the last full pass. */
169
- static size_t scr_cyc_live_after_full = 0;
169
+ static SCR_TL size_t scr_cyc_live_after_full = 0;
170
170
 
171
171
  static size_t scr_cyc_nursery_threshold(void) {
172
- static size_t cached = 0;
172
+ static SCR_TL size_t cached = 0;
173
173
  if (cached == 0) {
174
174
  const char *env = getenv("SCR_CYCLE_THRESHOLD");
175
175
  long v = env ? strtol(env, NULL, 10) : 0;
@@ -186,12 +186,12 @@ static size_t scr_cyc_nursery_threshold(void) {
186
186
  * hysteresis a large mature buffer — which a nursery pass cannot drain —
187
187
  * would re-arm on every single release. Both start at zero, so the first
188
188
  * release runs one trivial pass that arms them properly. */
189
- static size_t scr_cyc_nbuffered = 0;
190
- static size_t scr_cyc_trigger = 0;
189
+ static SCR_TL size_t scr_cyc_nbuffered = 0;
190
+ static SCR_TL size_t scr_cyc_trigger = 0;
191
191
 
192
192
  /* Scheduled nursery passes for which at least one mature root was waiting.
193
193
  * A full pass resets the age; with no mature backlog there is nothing to age. */
194
- static size_t scr_cyc_scheduled_mature_age = 0;
194
+ static SCR_TL size_t scr_cyc_scheduled_mature_age = 0;
195
195
 
196
196
  static size_t scr_cyc_buffered(void) {
197
197
  return scr_roots[SCR_CYC_NURSERY].n + scr_roots[SCR_CYC_MATURE].n;
@@ -404,7 +404,7 @@ static void scr_xg_pin_visit(void *child, void *ctx) {
404
404
  }
405
405
 
406
406
  /* Freed by the cross-generation drain, for the caller's fixpoint. */
407
- static size_t scr_xg_freed = 0;
407
+ static SCR_TL size_t scr_xg_freed = 0;
408
408
 
409
409
  static void scr_xg_release(void *obj);
410
410
  static void scr_xg_child_visit(void *child, void *ctx) {
package/src/scr_error.c CHANGED
@@ -17,7 +17,7 @@ static void scr_error_oom(void) {
17
17
  scr_trap("scriptc: out of memory\n");
18
18
  }
19
19
 
20
- static bool scr_error_traced = false;
20
+ static SCR_TL bool scr_error_traced = false;
21
21
 
22
22
  void scr_error_set_traced(void) { scr_error_traced = true; }
23
23
 
@@ -49,7 +49,7 @@ static void scr_error_gcfree(void *obj) {
49
49
  * must stay linkable WITHOUT the checked-dynamic tree (the runtime C-unit tests link
50
50
  * subsets), so the drop goes through a hook scr_json.c installs before
51
51
  * any cause can exist (scr_domex_new is the only producer). */
52
- static void (*scr_domex_cause_drop)(void *obj) = NULL;
52
+ static SCR_TL void (*scr_domex_cause_drop)(void *obj) = NULL;
53
53
 
54
54
  void scr_domex_install_cause_drop(void (*fn)(void *obj)) {
55
55
  scr_domex_cause_drop = fn;
@@ -90,7 +90,7 @@ static void scr_domex_reld(void *obj) {
90
90
 
91
91
  /* Defaults cover only the instants before main() stamps the program's real
92
92
  * preorder intervals; release is permanent. */
93
- ScrVt scr_error_vts[5] = {
93
+ SCR_TL ScrVt scr_error_vts[5] = {
94
94
  {0, 4, &scr_error_reld}, /* Error */
95
95
  {1, 1, &scr_error_reld}, /* TypeError */
96
96
  {2, 2, &scr_error_reld}, /* RangeError */