@scriptc/runtime 0.0.24 → 0.0.26

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.24",
3
+ "version": "0.0.26",
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_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_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_events.c CHANGED
@@ -30,6 +30,9 @@
30
30
  #include <io.h>
31
31
  #include <unistd.h> /* mingw-w64 ships one: read */
32
32
  #include <windows.h>
33
+ #elif defined(__wasi__)
34
+ #include <poll.h>
35
+ #include <unistd.h>
33
36
  #else
34
37
  #include <poll.h>
35
38
  #include <unistd.h>
@@ -40,6 +43,21 @@ static void scr_events_oom(void) {
40
43
  abort();
41
44
  }
42
45
 
46
+ #ifdef __wasi__
47
+ /* The native child unit normally owns these generic Error-listener adapters.
48
+ * WASI cannot spawn children and therefore does not link that unit, while
49
+ * stdin's error event intentionally shares the same adapter ABI. */
50
+ void scr_child_err_thunk0(ScrClosure *cb, ScrStr *msg) {
51
+ (void)msg;
52
+ ((void (*)(ScrClosure *))cb->fn)(cb);
53
+ }
54
+
55
+ void scr_child_err_thunk_error(ScrClosure *cb, ScrStr *msg) {
56
+ ScrError *error = scr_error_new(0 /* Error */, msg);
57
+ ((void (*)(ScrClosure *, ScrError *))cb->fn)(cb, error);
58
+ }
59
+ #endif
60
+
43
61
  /* ── process signal events (process.on("SIGINT" | "SIGTERM")) ─────────
44
62
  * Classic self-pipe integration: a watched signal's sigaction handler
45
63
  * (installed WITHOUT SA_RESTART) sets a per-signal flag and writes one
@@ -62,7 +80,7 @@ typedef struct {
62
80
 
63
81
  typedef struct ScrSigReg {
64
82
  int sig;
65
- #ifdef _WIN32
83
+ #if defined(_WIN32) || defined(__wasi__)
66
84
  void (*prev)(int); /* restored when the last listener leaves */
67
85
  #else
68
86
  struct sigaction prev; /* restored when the last listener leaves */
@@ -116,7 +134,7 @@ static void scr_sig_cleanup(void) {
116
134
  }
117
135
 
118
136
  static void scr_wake_pipe_init(void) {
119
- #ifdef _WIN32
137
+ #if defined(_WIN32) || defined(__wasi__)
120
138
  /* No self-pipe: the win32 loop never polls fds (scr_async.c's capped
121
139
  * nanosleep takes the idle sleep), so the handler's flag is the whole
122
140
  * wake path and the fds stay -1 — every pipe use below is guarded. */
@@ -141,6 +159,15 @@ static void scr_wake_pipe_drain(void) {
141
159
  }
142
160
 
143
161
  void scr_signal_on(double signum, ScrClosure *cb /*moves*/, bool once) {
162
+ #ifdef __wasi__
163
+ (void)signum;
164
+ (void)once;
165
+ scr_closure_release(cb);
166
+ /* The compiler rejects this capability before linking. This guard keeps
167
+ * the optional events unit honest if hand-authored IR reaches it. */
168
+ scr_trap("scriptc: internal error: OS signal surface reached on WASI\n");
169
+ return;
170
+ #else
144
171
  int sig = (int)signum;
145
172
  if (sig <= 0 || sig >= SCR_SIG_MAX) {
146
173
  scr_closure_release(cb); /* frontend only emits classic signals */
@@ -176,11 +203,15 @@ void scr_signal_on(double signum, ScrClosure *cb /*moves*/, bool once) {
176
203
  reg->ls[reg->n].cb = cb;
177
204
  reg->ls[reg->n].once = once;
178
205
  reg->n++;
206
+ #endif
179
207
  }
180
208
 
181
209
  static void scr_sig_reg_drop(ScrSigReg *reg) {
182
210
  #ifdef _WIN32
183
211
  signal(reg->sig, reg->prev); /* default disposition back */
212
+ #elif defined(__wasi__)
213
+ /* No signal disposition exists in WASI Preview 1. The compiler refuses
214
+ * signal registrations, so this arm is only the cleanup safety net. */
184
215
  #else
185
216
  sigaction(reg->sig, &reg->prev, NULL); /* default disposition back */
186
217
  #endif
@@ -193,6 +224,12 @@ static void scr_sig_reg_drop(ScrSigReg *reg) {
193
224
  }
194
225
 
195
226
  void scr_signal_off(double signum, ScrClosure *cb /*borrowed*/) {
227
+ #ifdef __wasi__
228
+ (void)signum;
229
+ (void)cb;
230
+ scr_trap("scriptc: internal error: OS signal surface reached on WASI\n");
231
+ return;
232
+ #else
196
233
  int sig = (int)signum;
197
234
  ScrSigReg *reg = scr_sig_regs;
198
235
  while (reg && reg->sig != sig) reg = reg->next;
@@ -206,6 +243,7 @@ void scr_signal_off(double signum, ScrClosure *cb /*borrowed*/) {
206
243
  return;
207
244
  }
208
245
  }
246
+ #endif
209
247
  }
210
248
 
211
249
  /* One dispatch pass: every flagged signal fires its listener SNAPSHOT
@@ -486,6 +524,10 @@ static void scr_stdin_service(void) {
486
524
  if (WaitForSingleObject(h, 0) != WAIT_OBJECT_0) return;
487
525
  }
488
526
  }
527
+ #elif defined(__wasi__)
528
+ /* install() puts fd 0 in nonblocking mode, so a direct read is both the
529
+ * readiness probe and the operation. This also observes a closed pipe on
530
+ * WASI hosts whose poll_oneoff adapter does not report POLLHUP. */
489
531
  #else
490
532
  struct pollfd pfd = {0 /* stdin */, POLLIN, 0};
491
533
  int rc = poll(&pfd, 1, 0);
@@ -705,6 +747,10 @@ void scr_events_install(void) {
705
747
  static bool installed = false;
706
748
  if (installed) return;
707
749
  installed = true;
750
+ #ifdef __wasi__
751
+ int stdin_flags = fcntl(0, F_GETFL, 0);
752
+ if (stdin_flags >= 0) (void)fcntl(0, F_SETFL, stdin_flags | O_NONBLOCK);
753
+ #endif
708
754
  atexit(scr_events_cleanup_atexit);
709
755
  atexit(scr_exit_atexit);
710
756
  scr_loop_set_events(&scr_events_pending, &scr_events_watching, &scr_events_dispatch,
@@ -390,10 +390,10 @@ ScrEmitter *scr_emitter_on_via(ScrEmitter *em, ScrStr *name, ScrClosure *orig /*
390
390
 
391
391
  /* ── fixed-arity invoke shims (scr_runtime.h's contract) ──────────────
392
392
  * Read k pointer-size slots and call the fixed-signature adapter behind
393
- * cb->fn. The LLVM lane's emit sites pass every tuple argument pointer-
394
- * classed (f64 as its i64 bit pattern, bool zero-extended) and the
395
- * runtime's own emits are pointer-only, so va_arg(ap, void *) reads every
396
- * tuple a fixed-registered listener can observe. */
393
+ * cb->fn. The LLVM lane's emit sites pass scalars by pointer to typed stack
394
+ * slots and references directly; the runtime's own emits are pointer-only,
395
+ * so va_arg(ap, void *) reads every tuple a fixed-registered listener can
396
+ * observe on 32- and 64-bit targets. */
397
397
  void scr_ee_inv_fixed0(ScrClosure *cb, va_list ap) {
398
398
  (void)ap;
399
399
  ((void (*)(ScrClosure *))cb->fn)(cb);
package/src/scr_http.c CHANGED
@@ -2009,6 +2009,7 @@ void scr_http2_stream_undef_call(ScrStr *member) {
2009
2009
  ScrNetServer *scr_http_create_server(ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn) {
2010
2010
  scr_http_install();
2011
2011
  ScrNetServer *s = scr_net_create_server(NULL, NULL);
2012
+ scr_net_server_enable_http_timeout_surface(s);
2012
2013
  ScrHttpSrvCtx *ctx = calloc(1, sizeof *ctx);
2013
2014
  if (!ctx) scr_http_oom();
2014
2015
  ctx->proto = SCR_NET_PROTO_HTTP1;
package/src/scr_island.c CHANGED
@@ -118,7 +118,7 @@ static void *isl_realloc_fn(void *opaque, void *ptr, size_t size) {
118
118
  if (!ptr && p) isl_live_allocs++;
119
119
  return p;
120
120
  }
121
- static size_t isl_usable_size(const void *ptr) { return isl_malloc_size(ptr); }
121
+ static size_t isl_usable_size(const void *ptr) { return isl_malloc_size((void *)ptr); }
122
122
 
123
123
  static const JSMallocFunctions isl_mf = {
124
124
  isl_calloc, isl_malloc, isl_free, isl_realloc_fn, isl_usable_size,
@@ -2107,7 +2107,7 @@ ScrJsval *scr_jsval_from_promise(ScrPromise *p, int payload) {
2107
2107
  w->payload = payload;
2108
2108
  w->next = isl_prom_wraps;
2109
2109
  isl_prom_wraps = w;
2110
- ScrPromise *waiter = scr_async_spawn(isl_prom_wrap_entry, w);
2110
+ ScrPromise *waiter = scr_async_spawn_after(w->p, isl_prom_wrap_entry, w);
2111
2111
  scr_promise_release(waiter); /* the waiter never rejects; nobody awaits it */
2112
2112
  return isl_cell_new(prom);
2113
2113
  }
@@ -3352,7 +3352,7 @@ static JSValue isl_host_ids(JSContext *ctx, JSValueConst this_val, int argc,
3352
3352
  (void)argc;
3353
3353
  (void)argv;
3354
3354
  JSValue arr = JS_NewArray(ctx);
3355
- #ifdef _WIN32
3355
+ #if defined(_WIN32) || defined(__wasi__)
3356
3356
  JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, -1));
3357
3357
  JS_SetPropertyUint32(ctx, arr, 1, JS_NewInt32(ctx, -1));
3358
3358
  #else
@@ -3370,7 +3370,7 @@ static JSValue isl_host_umask(JSContext *ctx, JSValueConst this_val, int argc,
3370
3370
  (void)this_val;
3371
3371
  (void)argc;
3372
3372
  (void)argv;
3373
- #ifdef _WIN32
3373
+ #if defined(_WIN32) || defined(__wasi__)
3374
3374
  return JS_NewInt32(ctx, 0);
3375
3375
  #else
3376
3376
  mode_t m = umask(0);
@@ -3495,11 +3495,15 @@ static JSValue isl_host_hostname(JSContext *ctx, JSValueConst this_val, int argc
3495
3495
  * counted like the socket units' own WSAStartup calls (ws2_32 is on
3496
3496
  * every win32 link line). */
3497
3497
  char buf[256];
3498
+ #if defined(__wasi__)
3499
+ buf[0] = '\0';
3500
+ #else
3498
3501
  #ifdef _WIN32
3499
3502
  WSADATA wsa;
3500
3503
  if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return JS_NewString(ctx, "");
3501
3504
  #endif
3502
3505
  if (gethostname(buf, sizeof buf) != 0) buf[0] = '\0';
3506
+ #endif
3503
3507
  buf[sizeof buf - 1] = '\0';
3504
3508
  return JS_NewString(ctx, buf);
3505
3509
  }