@amritk/nish-aarch64-darwin 0.35.0 → 0.37.0

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.
Files changed (128) hide show
  1. package/INSTALL.md +73 -59
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +50 -4
  4. package/package.json +1 -1
  5. package/runtime/async.d.mts +2 -0
  6. package/runtime/async.mjs +7 -0
  7. package/runtime/nish.d.ts +4849 -430
  8. package/runtime/nish.h +25 -0
  9. package/runtime/page.d.ts +53 -0
  10. package/runtime/page.mjs +146 -0
  11. package/runtime/runtime-async.c +557 -0
  12. package/runtime/runtime-wasm.c +63 -7
  13. package/runtime/vite.d.ts +30 -0
  14. package/runtime/vite.mjs +150 -0
  15. package/scripts/build.sh +252 -15
  16. package/std/README.md +107 -82
  17. package/std/base64.ts +34 -5
  18. package/std/bytes.ts +279 -59
  19. package/std/cli-args.ts +296 -70
  20. package/std/cli-flags.ts +184 -19
  21. package/std/cli.ts +475 -54
  22. package/std/collections.ts +121 -23
  23. package/std/compress/checksum.ts +44 -14
  24. package/std/compress/deflate.ts +224 -111
  25. package/std/compress/gzip.ts +217 -53
  26. package/std/compress/zlib.ts +63 -7
  27. package/std/crypto/aes.ts +128 -52
  28. package/std/crypto/base64url.ts +29 -5
  29. package/std/crypto/bcrypt-batch.ts +101 -34
  30. package/std/crypto/bcrypt.ts +93 -27
  31. package/std/crypto/chacha20poly1305.ts +69 -14
  32. package/std/crypto/ct.ts +32 -13
  33. package/std/crypto/ed25519.ts +66 -16
  34. package/std/crypto/hkdf.ts +198 -13
  35. package/std/crypto/hmac.ts +242 -37
  36. package/std/crypto/jwt.ts +204 -72
  37. package/std/crypto/p256.ts +222 -72
  38. package/std/crypto/p384.ts +51 -23
  39. package/std/crypto/pki-roots.ts +32 -2
  40. package/std/crypto/pki.ts +99 -30
  41. package/std/crypto/rsa.ts +56 -22
  42. package/std/crypto/sha1.ts +3 -0
  43. package/std/crypto/sha256.ts +24 -1
  44. package/std/crypto/sha512.ts +99 -14
  45. package/std/crypto/x25519.ts +163 -24
  46. package/std/crypto/x509-random.ts +10 -0
  47. package/std/crypto/x509.ts +133 -66
  48. package/std/host/ring.ts +124 -0
  49. package/std/json-equal.ts +31 -11
  50. package/std/json-format.ts +112 -13
  51. package/std/json-number.ts +76 -26
  52. package/std/json-pointer.ts +23 -1
  53. package/std/json-tokenizer.ts +126 -51
  54. package/std/json-typed.ts +757 -96
  55. package/std/json-value.ts +438 -157
  56. package/std/json.ts +170 -38
  57. package/std/map.ts +25 -7
  58. package/std/math.ts +36 -4
  59. package/std/net/buffer-pool.ts +42 -11
  60. package/std/net/dns-config.ts +109 -23
  61. package/std/net/dns-message.ts +62 -7
  62. package/std/net/dns-resolver.ts +247 -53
  63. package/std/net/dns.ts +203 -35
  64. package/std/net/fetch.ts +1267 -290
  65. package/std/net/handler.ts +296 -80
  66. package/std/net/hpack.ts +290 -77
  67. package/std/net/http-compress.ts +152 -36
  68. package/std/net/http-fields.ts +85 -9
  69. package/std/net/http1-client-codec.ts +129 -44
  70. package/std/net/http1-codec.ts +279 -114
  71. package/std/net/http1-server.ts +409 -139
  72. package/std/net/http2-frame.ts +289 -51
  73. package/std/net/http2-tls.ts +152 -49
  74. package/std/net/http2.ts +314 -160
  75. package/std/net/http3-frame.ts +165 -27
  76. package/std/net/http3-server.ts +282 -131
  77. package/std/net/http3.ts +377 -173
  78. package/std/net/outbound.ts +37 -3
  79. package/std/net/postgres-carrier.ts +144 -8
  80. package/std/net/postgres.ts +898 -260
  81. package/std/net/prefork.ts +202 -65
  82. package/std/net/qpack.ts +120 -32
  83. package/std/net/quic-conn-ack.ts +60 -12
  84. package/std/net/quic-conn-cid.ts +135 -27
  85. package/std/net/quic-conn-params.ts +109 -22
  86. package/std/net/quic-datagram.ts +50 -3
  87. package/std/net/quic-frame.ts +333 -23
  88. package/std/net/quic-listener.ts +161 -75
  89. package/std/net/quic-packet.ts +373 -73
  90. package/std/net/quic-recovery.ts +287 -65
  91. package/std/net/quic-stream.ts +436 -146
  92. package/std/net/quic.ts +693 -323
  93. package/std/net/router.ts +87 -40
  94. package/std/net/tls/client.ts +167 -51
  95. package/std/net/tls/codec.ts +494 -68
  96. package/std/net/tls/record-client.ts +140 -73
  97. package/std/net/tls/record-server.ts +233 -105
  98. package/std/net/tls/record.ts +142 -36
  99. package/std/net/tls/schedule.ts +235 -26
  100. package/std/net/tls/ticket.ts +161 -40
  101. package/std/net/tls/verify.ts +57 -10
  102. package/std/net/tls-tcp.ts +308 -95
  103. package/std/net/tls.ts +276 -102
  104. package/std/net/upstream.ts +81 -45
  105. package/std/net/url.ts +109 -22
  106. package/std/net/websocket.ts +140 -26
  107. package/std/net/webtransport.ts +243 -106
  108. package/std/pair.ts +10 -2
  109. package/std/random.ts +42 -15
  110. package/std/regex.ts +422 -257
  111. package/std/region.ts +70 -15
  112. package/std/secret.ts +52 -11
  113. package/std/sort.ts +17 -2
  114. package/std/sqlite.ts +256 -35
  115. package/std/sync/ring.ts +229 -56
  116. package/std/sync/shm.ts +203 -23
  117. package/std/term-io.ts +154 -78
  118. package/std/term-pixels.ts +144 -19
  119. package/std/term-probe.ts +77 -35
  120. package/std/term-rate.ts +199 -118
  121. package/std/term.ts +400 -175
  122. package/std/testing.ts +130 -32
  123. package/std/text.ts +84 -17
  124. package/std/threads.ts +248 -56
  125. package/std/yaml-json.ts +111 -97
  126. package/std/yaml-lines.ts +47 -15
  127. package/std/yaml-write.ts +45 -30
  128. package/std/yaml.ts +764 -384
@@ -0,0 +1,557 @@
1
+ /* Nish runtime, the async half: the executor that resumes `async` tasks when
2
+ * the kernel says a file descriptor is ready or a timer is due
3
+ * (docs/wp49-async-server.md, "Stage 5, part 1"; security record
4
+ * docs/security/async.md).
5
+ *
6
+ * A translation unit of its own, paired with runtime.c in scripts/build.sh's
7
+ * `halves` and src/run-cache.ts's `runtimeUnits()` like the others, and linked
8
+ * only into a program whose IR names `@nish_async_`: one with no `async`
9
+ * function links none of it. The wasm profiles refuse `async` (NL2652), so
10
+ * under wasm this file is empty.
11
+ *
12
+ * **A task is a node.** The compiler sizes each spawned function's frame and
13
+ * hands the size to `nish_async_spawn`, which answers a node: this header,
14
+ * then the frame at `NISH_ASYNC_HEADER` bytes. Nodes come from one slab per
15
+ * task kind: a free list, refilled by chunks of nodes whose count doubles, so
16
+ * a program's slab grows to the most tasks it ever had in flight and is then
17
+ * reused; a node is never given back to `malloc`. The compiled driver
18
+ * (`@nish.async.run`, emitted by src/emit-async.ts) resumes a node through a
19
+ * switch over the kinds, not a function pointer, so nothing here calls into
20
+ * the program.
21
+ *
22
+ * **Every node has a permanent index and a generation.** The index is its
23
+ * place in the task table, given once; the generation moves on each time the
24
+ * node is reused. A wait records the (index, generation) of the task that
25
+ * made it, so a completion that arrives after its task finished -- an orphan
26
+ * wait, or a node already reused by another task -- is dropped and counted
27
+ * (`nish_async_stat(1)`) rather than resuming the wrong task. Index 0 is
28
+ * `main`'s, a static node with kind -1 that is never reused.
29
+ *
30
+ * **A wait is a token.** `nish_async_wait_fd` and `nish_async_sleep` answer
31
+ * `(generation << 32) | (slot + 1)` for a slot of the wait table, and the
32
+ * caller polls it with `nish_async_poll` (-1 pending, else the answer, which
33
+ * frees the slot). A wait that can be answered at once -- a bad descriptor,
34
+ * a regular file, a negative sleep -- is made already fired, so the poll in
35
+ * the same resume reads it and the task never suspends.
36
+ *
37
+ * **The loop.** `nish_async_next` answers the next task to resume: the head
38
+ * of the ready queue, or, when the queue is empty and something waits, the
39
+ * first task a blocking kernel wait wakes. Each wait reports the kernel's
40
+ * ready descriptors in the order the kernel listed them, then the timers that
41
+ * expired, earliest deadline first (a binary heap, ties broken by the order
42
+ * they were made). It answers null when nothing is ready and nothing waits,
43
+ * which ends the loop.
44
+ *
45
+ * **A descriptor wait owns a duplicate.** The descriptor is duplicated
46
+ * (`F_DUPFD_CLOEXEC`) and the duplicate registered one-shot, so two tasks can
47
+ * wait on one descriptor, and a program that closes the descriptor while a
48
+ * task waits on it cannot have the registration answer for whatever the
49
+ * number is reused for. The duplicate is closed when the wait fires.
50
+ *
51
+ * **Regions.** Each spawned task owns a region (runtime-region.c), entered
52
+ * whenever the task runs and freed when it finishes, so whatever a task
53
+ * allocates is given back when it ends. The driver enters and leaves around
54
+ * each resume (`nish_async_enter`, `nish_async_leave`).
55
+ *
56
+ * The executor runs on the thread that runs `main`; its state is not shared
57
+ * with the threads of `nish/threads`. */
58
+
59
+ #if defined(__wasi__) || defined(__wasm__)
60
+ typedef int nish_async_unused_in_wasm;
61
+ #else
62
+ #if defined(__linux__)
63
+ /* glibc declares `clock_gettime`, `nanosleep` and `F_DUPFD_CLOEXEC` under a feature macro. */
64
+ #define _GNU_SOURCE
65
+ #else
66
+ #define _DARWIN_C_SOURCE
67
+ #endif
68
+
69
+ #include <errno.h>
70
+ #include <fcntl.h>
71
+ #include <stddef.h>
72
+ #include <stdint.h>
73
+ #include <stdlib.h>
74
+ #include <string.h>
75
+ #include <time.h>
76
+ #include <unistd.h>
77
+ #if defined(__linux__)
78
+ #include <sys/epoll.h>
79
+ #else
80
+ #include <sys/event.h>
81
+ #include <sys/stat.h>
82
+ #include <sys/time.h>
83
+ #include <sys/types.h>
84
+ #endif
85
+
86
+ #include "nish.h"
87
+
88
+ #define NISH_COLD __attribute__((noreturn, cold, noinline))
89
+
90
+ /* The bytes ahead of a frame in a node; src/emit-async.ts's NODE_HEADER. */
91
+ #define NISH_ASYNC_HEADER 32
92
+
93
+ typedef struct nish_task {
94
+ struct nish_task *next; /* the ready queue's link, or the free list's */
95
+ int32_t kind; /* the spawned function's kind; -1 for `main` */
96
+ uint32_t gen; /* moves on each time the node is reused */
97
+ uint64_t region; /* the task's region; 0 for `main` and a free node */
98
+ uint32_t index; /* the node's place in the task table, for ever */
99
+ uint32_t queued; /* on the ready queue, so it is queued once */
100
+ } nish_task;
101
+
102
+ _Static_assert(sizeof(nish_task) <= NISH_ASYNC_HEADER, "a node's header holds a task");
103
+ _Static_assert(offsetof(nish_task, kind) == 8, "the driver reads the kind at byte 8");
104
+
105
+ typedef struct nish_slab {
106
+ nish_task *free;
107
+ uint64_t stride; /* header and frame, rounded to 16 */
108
+ uint64_t chunk; /* nodes in the next chunk */
109
+ } nish_slab;
110
+
111
+ enum { NISH_WAIT_FREE, NISH_WAIT_PENDING, NISH_WAIT_FIRED };
112
+
113
+ typedef struct nish_wait {
114
+ uint32_t gen; /* moves on each time the slot is freed */
115
+ uint32_t task; /* the waiting task's index ... */
116
+ uint32_t task_gen; /* ... and its generation when it waited */
117
+ int32_t state;
118
+ int32_t answer;
119
+ int32_t fd; /* the registered duplicate, or -1 for a timer */
120
+ int64_t deadline; /* a timer's, in CLOCK_MONOTONIC nanoseconds */
121
+ uint64_t seq; /* a timer's place among equal deadlines */
122
+ } nish_wait;
123
+
124
+ static nish_task nish_async_main_task = {NULL, -1, 1, 0, 0, 0};
125
+ static nish_task *nish_async_current = &nish_async_main_task;
126
+
127
+ static nish_task **nish_async_tasks;
128
+ static uint32_t nish_async_task_count;
129
+ static uint32_t nish_async_task_cap;
130
+
131
+ static nish_slab *nish_async_slabs;
132
+ static uint32_t nish_async_slab_count;
133
+
134
+ static nish_task *nish_async_head;
135
+ static nish_task *nish_async_tail;
136
+
137
+ static nish_wait *nish_async_waits;
138
+ static uint32_t nish_async_wait_count;
139
+ static uint32_t nish_async_wait_cap;
140
+ static uint32_t *nish_async_wait_free;
141
+ static uint32_t nish_async_wait_free_len;
142
+
143
+ static uint32_t *nish_async_heap;
144
+ static uint32_t nish_async_heap_len;
145
+ static uint32_t nish_async_heap_cap;
146
+ static uint64_t nish_async_seq;
147
+
148
+ static int nish_async_poller = -1;
149
+ static uint32_t nish_async_fd_waits;
150
+
151
+ static int32_t nish_async_allocated;
152
+ static int32_t nish_async_late;
153
+ static int32_t nish_async_live; /* spawned tasks not yet done, each holding a region */
154
+
155
+ enum { NISH_ASYNC_OOM, NISH_ASYNC_POLLER, NISH_ASYNC_TOO_MANY, NISH_ASYNC_WAIT };
156
+ #define NISH_ASYNC_MSG(text) { sizeof text - 1, text }
157
+ static const struct { uint64_t len; char data[64]; } nish_async_messages[] = {
158
+ NISH_ASYNC_MSG("nish: out of memory"),
159
+ NISH_ASYNC_MSG("nish: the async event loop could not be made"),
160
+ NISH_ASYNC_MSG("nish: more than 4294967294 async tasks or waits"),
161
+ NISH_ASYNC_MSG("nish: the async event loop could not wait"),
162
+ };
163
+
164
+ static NISH_COLD void nish_async_die(int why) {
165
+ nish_panic((const nish_str *)&nish_async_messages[why]);
166
+ }
167
+
168
+ /* Grow `*items` (of `size`-byte entries, `*cap` of them) to hold one more. */
169
+ static void *nish_async_grow(void *items, uint32_t *cap, size_t size) {
170
+ if (*cap >= UINT32_MAX / 2) nish_async_die(NISH_ASYNC_TOO_MANY);
171
+ uint32_t want = *cap ? *cap * 2 : 16;
172
+ void *grown = realloc(items, (size_t)want * size);
173
+ if (!grown) nish_async_die(NISH_ASYNC_OOM);
174
+ *cap = want;
175
+ return grown;
176
+ }
177
+
178
+ static void nish_async_table_add(nish_task *t) {
179
+ if (!nish_async_tasks) {
180
+ nish_async_tasks = nish_async_grow(NULL, &nish_async_task_cap, sizeof *nish_async_tasks);
181
+ nish_async_tasks[0] = &nish_async_main_task;
182
+ nish_async_task_count = 1;
183
+ }
184
+ if (nish_async_task_count == nish_async_task_cap)
185
+ nish_async_tasks = nish_async_grow(nish_async_tasks, &nish_async_task_cap, sizeof *nish_async_tasks);
186
+ t->index = nish_async_task_count;
187
+ nish_async_tasks[nish_async_task_count++] = t;
188
+ }
189
+
190
+ static int64_t nish_async_now(void) {
191
+ struct timespec ts;
192
+ clock_gettime(CLOCK_MONOTONIC, &ts);
193
+ return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
194
+ }
195
+
196
+ static void nish_async_enqueue(nish_task *t) {
197
+ if (t->queued) return;
198
+ t->queued = 1;
199
+ t->next = NULL;
200
+ if (nish_async_tail) nish_async_tail->next = t;
201
+ else nish_async_head = t;
202
+ nish_async_tail = t;
203
+ }
204
+
205
+ /* ---- Tasks --------------------------------------------------------------- */
206
+
207
+ void *nish_async_spawn(int32_t kind, uint64_t bytes) {
208
+ if (kind < 0) nish_async_die(NISH_ASYNC_TOO_MANY);
209
+ if ((uint32_t)kind >= nish_async_slab_count) {
210
+ uint32_t want = (uint32_t)kind + 1;
211
+ nish_slab *grown = realloc(nish_async_slabs, (size_t)want * sizeof *grown);
212
+ if (!grown) nish_async_die(NISH_ASYNC_OOM);
213
+ memset(grown + nish_async_slab_count, 0, (size_t)(want - nish_async_slab_count) * sizeof *grown);
214
+ nish_async_slabs = grown;
215
+ nish_async_slab_count = want;
216
+ }
217
+ nish_slab *slab = &nish_async_slabs[kind];
218
+ if (!slab->free) {
219
+ if (!slab->stride) {
220
+ slab->stride = (NISH_ASYNC_HEADER + bytes + 15) & ~(uint64_t)15;
221
+ slab->chunk = 4;
222
+ }
223
+ uint64_t count = slab->chunk;
224
+ char *chunk = malloc(count * slab->stride);
225
+ if (!chunk) nish_async_die(NISH_ASYNC_OOM);
226
+ slab->chunk = count * 2;
227
+ for (uint64_t i = count; i-- > 0;) {
228
+ nish_task *t = (nish_task *)(chunk + i * slab->stride);
229
+ t->gen = 1;
230
+ t->region = 0;
231
+ t->queued = 0;
232
+ nish_async_table_add(t);
233
+ t->next = slab->free;
234
+ slab->free = t;
235
+ nish_async_allocated++;
236
+ }
237
+ }
238
+ nish_task *t = slab->free;
239
+ slab->free = t->next;
240
+ t->next = NULL;
241
+ t->kind = kind;
242
+ t->queued = 0;
243
+ t->region = nish_region_new();
244
+ nish_async_live++;
245
+ return t;
246
+ }
247
+
248
+ /* Make `node` the running task and enter its region; answers the task that was running. */
249
+ void *nish_async_enter(void *node) {
250
+ nish_task *t = node;
251
+ nish_task *prev = nish_async_current;
252
+ nish_async_current = t;
253
+ if (t->region) nish_region_enter(t->region);
254
+ return prev;
255
+ }
256
+
257
+ /* Leave `node`'s region and make `prev`, what `nish_async_enter` answered, the running task again. */
258
+ void nish_async_leave(void *node, void *prev) {
259
+ nish_task *t = node;
260
+ if (t->region) nish_region_leave(t->region, NULL);
261
+ nish_async_current = prev;
262
+ }
263
+
264
+ /* A task finished: free its region and put its node back, a generation on. */
265
+ void nish_async_done(void *node) {
266
+ nish_task *t = node;
267
+ if (t->kind < 0) return;
268
+ nish_region_free(t->region);
269
+ t->region = 0;
270
+ nish_async_live--;
271
+ t->gen = t->gen == UINT32_MAX ? 1 : t->gen + 1;
272
+ nish_slab *slab = &nish_async_slabs[t->kind];
273
+ t->next = slab->free;
274
+ slab->free = t;
275
+ }
276
+
277
+ /* ---- Waits --------------------------------------------------------------- */
278
+
279
+ static uint64_t nish_async_token(uint32_t slot) {
280
+ return ((uint64_t)nish_async_waits[slot].gen << 32) | ((uint64_t)slot + 1);
281
+ }
282
+
283
+ /* The slot `token` names, or -1 for one that is not a live wait. */
284
+ static int64_t nish_async_slot_of(uint64_t token) {
285
+ uint64_t low = token & 0xffffffffu;
286
+ if (low == 0 || low > nish_async_wait_count) return -1;
287
+ uint32_t slot = (uint32_t)(low - 1);
288
+ nish_wait *w = &nish_async_waits[slot];
289
+ if (w->gen != (uint32_t)(token >> 32) || w->state == NISH_WAIT_FREE) return -1;
290
+ return slot;
291
+ }
292
+
293
+ static uint32_t nish_async_wait_new(void) {
294
+ uint32_t slot;
295
+ if (nish_async_wait_free_len) {
296
+ slot = nish_async_wait_free[--nish_async_wait_free_len];
297
+ } else {
298
+ if (nish_async_wait_count == nish_async_wait_cap) {
299
+ uint32_t cap = nish_async_wait_cap;
300
+ nish_async_waits = nish_async_grow(nish_async_waits, &nish_async_wait_cap, sizeof *nish_async_waits);
301
+ nish_async_wait_free = nish_async_grow(nish_async_wait_free, &cap, sizeof *nish_async_wait_free);
302
+ }
303
+ slot = nish_async_wait_count++;
304
+ nish_async_waits[slot].gen = 1;
305
+ }
306
+ nish_wait *w = &nish_async_waits[slot];
307
+ w->task = nish_async_current->index;
308
+ w->task_gen = nish_async_current->gen;
309
+ w->state = NISH_WAIT_PENDING;
310
+ w->answer = 0;
311
+ w->fd = -1;
312
+ w->deadline = 0;
313
+ w->seq = 0;
314
+ return slot;
315
+ }
316
+
317
+ static void nish_async_wait_free_slot(uint32_t slot) {
318
+ nish_wait *w = &nish_async_waits[slot];
319
+ w->state = NISH_WAIT_FREE;
320
+ w->gen = w->gen == UINT32_MAX ? 1 : w->gen + 1;
321
+ nish_async_wait_free[nish_async_wait_free_len++] = slot;
322
+ }
323
+
324
+ /* A wait answered before anyone polled it: the poll in the same resume reads it. */
325
+ static uint64_t nish_async_fired_now(uint32_t slot, int32_t answer) {
326
+ nish_async_waits[slot].state = NISH_WAIT_FIRED;
327
+ nish_async_waits[slot].answer = answer;
328
+ return nish_async_token(slot);
329
+ }
330
+
331
+ /* The wait in `slot` completed with `answer`: queue its task, or drop it when
332
+ the task it was made for has finished. */
333
+ static void nish_async_fire(uint32_t slot, int32_t answer) {
334
+ nish_wait *w = &nish_async_waits[slot];
335
+ if (w->state != NISH_WAIT_PENDING) return;
336
+ if (w->fd >= 0) {
337
+ #if defined(__linux__)
338
+ epoll_ctl(nish_async_poller, EPOLL_CTL_DEL, w->fd, NULL);
339
+ #endif
340
+ close(w->fd);
341
+ w->fd = -1;
342
+ nish_async_fd_waits--;
343
+ }
344
+ w->state = NISH_WAIT_FIRED;
345
+ w->answer = answer;
346
+ nish_task *t = w->task == 0 ? &nish_async_main_task
347
+ : w->task < nish_async_task_count ? nish_async_tasks[w->task] : NULL;
348
+ if (!t || t->gen != w->task_gen) {
349
+ nish_async_late++;
350
+ nish_async_wait_free_slot(slot);
351
+ return;
352
+ }
353
+ nish_async_enqueue(t);
354
+ }
355
+
356
+ static int nish_async_timer_before(uint32_t a, uint32_t b) {
357
+ nish_wait *x = &nish_async_waits[a];
358
+ nish_wait *y = &nish_async_waits[b];
359
+ return x->deadline < y->deadline || (x->deadline == y->deadline && x->seq < y->seq);
360
+ }
361
+
362
+ static void nish_async_heap_push(uint32_t slot) {
363
+ if (nish_async_heap_len == nish_async_heap_cap)
364
+ nish_async_heap = nish_async_grow(nish_async_heap, &nish_async_heap_cap, sizeof *nish_async_heap);
365
+ uint32_t at = nish_async_heap_len++;
366
+ while (at > 0) {
367
+ uint32_t up = (at - 1) / 2;
368
+ if (!nish_async_timer_before(slot, nish_async_heap[up])) break;
369
+ nish_async_heap[at] = nish_async_heap[up];
370
+ at = up;
371
+ }
372
+ nish_async_heap[at] = slot;
373
+ }
374
+
375
+ static uint32_t nish_async_heap_pop(void) {
376
+ uint32_t top = nish_async_heap[0];
377
+ uint32_t last = nish_async_heap[--nish_async_heap_len];
378
+ uint32_t at = 0;
379
+ for (;;) {
380
+ uint32_t kid = at * 2 + 1;
381
+ if (kid >= nish_async_heap_len) break;
382
+ if (kid + 1 < nish_async_heap_len && nish_async_timer_before(nish_async_heap[kid + 1], nish_async_heap[kid]))
383
+ kid++;
384
+ if (!nish_async_timer_before(nish_async_heap[kid], last)) break;
385
+ nish_async_heap[at] = nish_async_heap[kid];
386
+ at = kid;
387
+ }
388
+ if (nish_async_heap_len) nish_async_heap[at] = last;
389
+ return top;
390
+ }
391
+
392
+ /* A wait for `fd` to be readable (`dir` 0) or writable (`dir` 1). */
393
+ uint64_t nish_async_wait_fd(int32_t fd, int32_t dir) {
394
+ uint32_t slot = nish_async_wait_new();
395
+ if (fd < 0) return nish_async_fired_now(slot, 0);
396
+ if (nish_async_poller < 0) {
397
+ #if defined(__linux__)
398
+ nish_async_poller = epoll_create1(EPOLL_CLOEXEC);
399
+ #else
400
+ nish_async_poller = kqueue();
401
+ if (nish_async_poller >= 0) fcntl(nish_async_poller, F_SETFD, FD_CLOEXEC);
402
+ #endif
403
+ if (nish_async_poller < 0) nish_async_die(NISH_ASYNC_POLLER);
404
+ }
405
+ int dup = fcntl(fd, F_DUPFD_CLOEXEC, 0);
406
+ if (dup < 0) return nish_async_fired_now(slot, 0);
407
+ #if !defined(__linux__)
408
+ /* kqueue accepts a regular file and reports it readable only while it has
409
+ bytes left, so one at its end would park the task for ever; epoll refuses
410
+ it below. A regular file never blocks: it is ready, on both kernels. */
411
+ struct stat st;
412
+ if (fstat(dup, &st) == 0 && S_ISREG(st.st_mode)) {
413
+ close(dup);
414
+ return nish_async_fired_now(slot, 1);
415
+ }
416
+ #endif
417
+ uint64_t token = nish_async_token(slot);
418
+ #if defined(__linux__)
419
+ struct epoll_event ev;
420
+ memset(&ev, 0, sizeof ev);
421
+ ev.events = (dir ? EPOLLOUT : EPOLLIN) | EPOLLONESHOT;
422
+ ev.data.u64 = token;
423
+ int rc = epoll_ctl(nish_async_poller, EPOLL_CTL_ADD, dup, &ev);
424
+ #else
425
+ struct kevent ev;
426
+ EV_SET(&ev, (uintptr_t)dup, dir ? EVFILT_WRITE : EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0,
427
+ (void *)(uintptr_t)token);
428
+ int rc = kevent(nish_async_poller, &ev, 1, NULL, 0, NULL);
429
+ #endif
430
+ if (rc < 0) {
431
+ /* epoll refuses a regular file (EPERM), which never blocks: it is ready. */
432
+ int ready = errno == EPERM;
433
+ close(dup);
434
+ return nish_async_fired_now(slot, ready);
435
+ }
436
+ nish_async_waits[slot].fd = dup;
437
+ nish_async_fd_waits++;
438
+ return token;
439
+ }
440
+
441
+ /* A wait for `ms` milliseconds; a negative `ms` answers false at once. */
442
+ uint64_t nish_async_sleep(int32_t ms) {
443
+ uint32_t slot = nish_async_wait_new();
444
+ if (ms < 0) return nish_async_fired_now(slot, 0);
445
+ nish_async_waits[slot].deadline = nish_async_now() + (int64_t)ms * 1000000;
446
+ nish_async_waits[slot].seq = nish_async_seq++;
447
+ nish_async_heap_push(slot);
448
+ return nish_async_token(slot);
449
+ }
450
+
451
+ /* -1 while the wait is pending; else its answer (1 ready, 0 failed), and the token is spent. */
452
+ int32_t nish_async_poll(uint64_t token) {
453
+ int64_t slot = nish_async_slot_of(token);
454
+ if (slot < 0) return 0;
455
+ nish_wait *w = &nish_async_waits[slot];
456
+ if (w->state == NISH_WAIT_PENDING) return -1;
457
+ int32_t answer = w->answer;
458
+ nish_async_wait_free_slot((uint32_t)slot);
459
+ return answer;
460
+ }
461
+
462
+ /* ---- The loop ------------------------------------------------------------ */
463
+
464
+ /* Block until a descriptor is ready or the first timer is due, then queue
465
+ what woke: the descriptors in the kernel's order, then the expired timers
466
+ by deadline. */
467
+ static void nish_async_block(void) {
468
+ int64_t wait_ns = -1;
469
+ if (nish_async_heap_len) {
470
+ wait_ns = nish_async_waits[nish_async_heap[0]].deadline - nish_async_now();
471
+ if (wait_ns < 0) wait_ns = 0;
472
+ }
473
+ if (nish_async_fd_waits) {
474
+ #if defined(__linux__)
475
+ struct epoll_event evs[64];
476
+ int timeout = -1;
477
+ if (wait_ns >= 0) {
478
+ int64_t ms = (wait_ns + 999999) / 1000000;
479
+ timeout = ms > 0x7fffffff ? 0x7fffffff : (int)ms;
480
+ }
481
+ int n = epoll_wait(nish_async_poller, evs, 64, timeout);
482
+ /* A signal ends the wait early and the caller waits again; any other
483
+ failure would recur on every call and spin, so it ends the program. */
484
+ if (n < 0 && errno != EINTR) nish_async_die(NISH_ASYNC_WAIT);
485
+ for (int i = 0; i < n; i++) {
486
+ int64_t slot = nish_async_slot_of(evs[i].data.u64);
487
+ if (slot < 0) {
488
+ nish_async_late++;
489
+ continue;
490
+ }
491
+ nish_async_fire((uint32_t)slot, 1);
492
+ }
493
+ #else
494
+ struct kevent evs[64];
495
+ struct timespec ts;
496
+ struct timespec *tp = NULL;
497
+ if (wait_ns >= 0) {
498
+ ts.tv_sec = wait_ns / 1000000000;
499
+ ts.tv_nsec = wait_ns % 1000000000;
500
+ tp = &ts;
501
+ }
502
+ int n = kevent(nish_async_poller, NULL, 0, evs, 64, tp);
503
+ if (n < 0 && errno != EINTR) nish_async_die(NISH_ASYNC_WAIT);
504
+ for (int i = 0; i < n; i++) {
505
+ int64_t slot = nish_async_slot_of((uint64_t)(uintptr_t)evs[i].udata);
506
+ if (slot < 0) {
507
+ nish_async_late++;
508
+ continue;
509
+ }
510
+ nish_async_fire((uint32_t)slot, 1);
511
+ }
512
+ #endif
513
+ } else if (wait_ns > 0) {
514
+ struct timespec ts;
515
+ ts.tv_sec = wait_ns / 1000000000;
516
+ ts.tv_nsec = wait_ns % 1000000000;
517
+ while (nanosleep(&ts, &ts) < 0 && errno == EINTR) {
518
+ }
519
+ }
520
+ int64_t now = nish_async_now();
521
+ while (nish_async_heap_len && nish_async_waits[nish_async_heap[0]].deadline <= now)
522
+ nish_async_fire(nish_async_heap_pop(), 1);
523
+ }
524
+
525
+ /* The next task to resume, waiting on the kernel for one when none is ready;
526
+ null when nothing is ready and nothing waits. */
527
+ void *nish_async_next(void) {
528
+ while (!nish_async_head) {
529
+ if (!nish_async_fd_waits && !nish_async_heap_len) return NULL;
530
+ nish_async_block();
531
+ }
532
+ nish_task *t = nish_async_head;
533
+ nish_async_head = t->next;
534
+ if (!nish_async_head) nish_async_tail = NULL;
535
+ t->next = NULL;
536
+ t->queued = 0;
537
+ return t;
538
+ }
539
+
540
+ /* A spawned task's string argument, copied into the arena that is current --
541
+ * the task's region, which the spawn has just entered -- so the task never
542
+ * reads memory its spawner's arena owns after the spawner has returned. */
543
+ void *nish_async_copy_str(const void *s) {
544
+ const nish_str *from = s;
545
+ return nish_str_new(from->data, from->len);
546
+ }
547
+
548
+ /* 0: nodes the slabs ever allocated; 1: completions dropped because their task
549
+ had finished; 2: spawned tasks not yet finished, each of which holds a region. */
550
+ int32_t nish_async_stat(int32_t which) {
551
+ if (which == 0) return nish_async_allocated;
552
+ if (which == 1) return nish_async_late;
553
+ if (which == 2) return nish_async_live;
554
+ return -1;
555
+ }
556
+
557
+ #endif
@@ -9,7 +9,8 @@
9
9
  * (`%struct.nish_arena = type { i8*, i64, i64, i8* }`), not size_t: wasm32
10
10
  * pointers are 4 bytes, and the inlined fast path in every compiled function
11
11
  * bumps the 64-bit `off` field directly. Panics are `unreachable` (a JS
12
- * RuntimeError); there is no stderr to write a message to. Strings and I/O
12
+ * RuntimeError); there is no stderr to write a message to, so under
13
+ * wasm-host the page is told first (NISH_DIE). Strings and I/O
13
14
  * need a WASI runtime and are not provided here, so `--emit-dts` lists
14
15
  * string functions as not exported. */
15
16
  #include <stddef.h>
@@ -37,6 +38,35 @@ _Static_assert(offsetof(struct nish_arena, off) == 8, "the inlined allocator bum
37
38
  _Static_assert(offsetof(struct nish_arena, cap) == 16, "the inlined allocator reads field 2");
38
39
  _Static_assert(offsetof(struct nish_arena, chunks) == 24, "arena layout is ABI");
39
40
 
41
+ /* WP50 stage 3: a module for a page (`scripts/build.sh --profile wasm-host`,
42
+ * which compiles this file with -DNISH_WASM_HOST) reports each panic to the
43
+ * page before it traps, through the import `nish.panic`. `kind` is one of the
44
+ * NISH_PANIC_* below; `site` numbers the call in `<out>.sites.json`, or is -1
45
+ * for a panic the compiler did not tag (one this runtime raises itself);
46
+ * `msg`/`len` are a message's bytes in linear memory (kind panic only) and
47
+ * `a`, `b` its numbers: an index and a length, a slice's start and end (its
48
+ * length in `len`), an arithmetic operator, an exit code. The page's loader
49
+ * formats the message, marks the module dead and throws, so the import does
50
+ * not return; the trap after it is for a page that returns anyway. Without
51
+ * NISH_WASM_HOST (`--profile wasm`) each panic is the bare trap it was. */
52
+ #define NISH_PANIC_MESSAGE 0
53
+ #define NISH_PANIC_INDEX 1
54
+ #define NISH_PANIC_SLICE 2
55
+ #define NISH_PANIC_ARITH 3
56
+ #define NISH_PANIC_MEMORY 4
57
+ #define NISH_PANIC_EXIT 5
58
+ #ifdef NISH_WASM_HOST
59
+ __attribute__((import_module("nish"), import_name("panic"))) void nish_host_panic(
60
+ int32_t kind, int32_t site, const void *msg, uint32_t len, double a, double b);
61
+ #define NISH_DIE(kind, site, msg, len, a, b) \
62
+ do { \
63
+ nish_host_panic((kind), (site), (msg), (len), (a), (b)); \
64
+ __builtin_trap(); \
65
+ } while (0)
66
+ #else
67
+ #define NISH_DIE(kind, site, msg, len, a, b) __builtin_trap()
68
+ #endif
69
+
40
70
  extern unsigned char __heap_base;
41
71
  #define NISH_PAGE ((uint64_t)65536)
42
72
 
@@ -55,10 +85,12 @@ void *nish_arena_grow(uint64_t size) {
55
85
  nish_arena.cap = (uint64_t)__builtin_wasm_memory_size(0) * NISH_PAGE - base;
56
86
  }
57
87
  uint64_t need = nish_arena.off + size;
58
- if (need < size || need > (uintptr_t)-1 - (uintptr_t)nish_arena.buf) __builtin_trap();
88
+ /* Under wasm-host both are "out of memory": the address space, or a
89
+ `--memory` the module was built with, is spent. */
90
+ if (need < size || need > (uintptr_t)-1 - (uintptr_t)nish_arena.buf) NISH_DIE(NISH_PANIC_MEMORY, -1, 0, 0, 0, 0);
59
91
  if (need > nish_arena.cap) {
60
92
  uint64_t pages = (need - nish_arena.cap + NISH_PAGE - 1) / NISH_PAGE;
61
- if (__builtin_wasm_memory_grow(0, (uintptr_t)pages) == (uintptr_t)-1) __builtin_trap();
93
+ if (__builtin_wasm_memory_grow(0, (uintptr_t)pages) == (uintptr_t)-1) NISH_DIE(NISH_PANIC_MEMORY, -1, 0, 0, 0, 0);
62
94
  nish_arena.cap += pages * NISH_PAGE;
63
95
  }
64
96
  void *p = nish_arena.buf + nish_arena.off;
@@ -69,7 +101,7 @@ void *nish_arena_grow(uint64_t size) {
69
101
  /* `size <= cap - off` rather than `off + size <= cap`, which a size near 2^64
70
102
  wraps into a yes; a size that rounding wraps to 0 is trapped the same way. */
71
103
  void *nish_alloc_struct(uint64_t size) {
72
- if (size > (uint64_t)-8) __builtin_trap();
104
+ if (size > (uint64_t)-8) NISH_DIE(NISH_PANIC_MEMORY, -1, 0, 0, 0, 0);
73
105
  size = (size + 7) & ~(uint64_t)7;
74
106
  if (nish_arena.buf && size <= nish_arena.cap - nish_arena.off) {
75
107
  void *p = nish_arena.buf + nish_arena.off;
@@ -96,7 +128,7 @@ typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array
96
128
  nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
97
129
  /* With `len` in range, `len * elem_size` cannot wrap for any element
98
130
  smaller than 8 GiB, and a product past 4 GiB traps in the grow. */
99
- if (len > NISH_ARRAY_MAX) __builtin_trap();
131
+ if (len > NISH_ARRAY_MAX) NISH_DIE(NISH_PANIC_MEMORY, -1, 0, 0, 0, 0);
100
132
  nish_array *a = (nish_array *)nish_alloc_struct(sizeof *a);
101
133
  a->len = a->cap = len;
102
134
  a->data = (char *)nish_alloc_struct(len * elem_size);
@@ -106,7 +138,7 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
106
138
  void nish_array_grow(nish_array *a, uint64_t elem_size) {
107
139
  uint64_t cap = a->cap ? a->cap * 2 : 4;
108
140
  if (cap > NISH_ARRAY_MAX) {
109
- if (a->len >= NISH_ARRAY_MAX) __builtin_trap();
141
+ if (a->len >= NISH_ARRAY_MAX) NISH_DIE(NISH_PANIC_MEMORY, -1, 0, 0, 0, 0);
110
142
  cap = NISH_ARRAY_MAX;
111
143
  }
112
144
  char *data = (char *)nish_alloc_struct(cap * elem_size);
@@ -115,7 +147,30 @@ void nish_array_grow(nish_array *a, uint64_t elem_size) {
115
147
  a->cap = cap;
116
148
  }
117
149
 
118
- /* ---- Panics: `unreachable`, which the host sees as a RuntimeError */
150
+ /* ---- Panics: `unreachable`, which the host sees as a RuntimeError; under
151
+ * wasm-host the page hears of each first (NISH_DIE above). */
152
+ #ifdef NISH_WASM_HOST
153
+ /* The `_at` forms take the site number build.sh rewrote a tagged call to. */
154
+ void nish_panic_index_at(int32_t site, uint64_t idx, uint64_t len) {
155
+ NISH_DIE(NISH_PANIC_INDEX, site, 0, 0, (double)idx, (double)len);
156
+ }
157
+ void nish_panic_slice_at(int32_t site, int64_t s, int64_t e, int64_t len) {
158
+ NISH_DIE(NISH_PANIC_SLICE, site, 0, (uint32_t)len, (double)s, (double)e);
159
+ }
160
+ void nish_panic_overflow_at(int32_t site, uint32_t op) { NISH_DIE(NISH_PANIC_ARITH, site, 0, 0, (double)op, 0); }
161
+ void nish_panic_div_at(int32_t site, _Bool by_zero) { nish_panic_overflow_at(site, 4 + by_zero); }
162
+ /* A string is `{ i64 len, bytes[len] }` (runtime.c's nish_str). */
163
+ void nish_panic_at(int32_t site, const void *s) {
164
+ const uint64_t *len = (const uint64_t *)s;
165
+ NISH_DIE(NISH_PANIC_MESSAGE, site, (const char *)s + 8, (uint32_t)*len, 0, 0);
166
+ }
167
+ void nish_panic_index(uint64_t idx, uint64_t len) { nish_panic_index_at(-1, idx, len); }
168
+ void nish_panic_slice(int64_t s, int64_t e, int64_t len) { nish_panic_slice_at(-1, s, e, len); }
169
+ void nish_panic_div(_Bool by_zero) { nish_panic_div_at(-1, by_zero); }
170
+ void nish_panic_overflow(uint32_t op) { nish_panic_overflow_at(-1, op); }
171
+ void nish_exit(int32_t code) { NISH_DIE(NISH_PANIC_EXIT, -1, 0, 0, (double)code, 0); }
172
+ void nish_panic(const void *s) { nish_panic_at(-1, s); }
173
+ #else
119
174
  void nish_panic_index(uint64_t idx, uint64_t len) { (void)idx; (void)len; __builtin_trap(); }
120
175
  void nish_panic_slice(int64_t s, int64_t e, int64_t len) { (void)s; (void)e; (void)len; __builtin_trap(); }
121
176
  void nish_panic_div(_Bool by_zero) { (void)by_zero; __builtin_trap(); }
@@ -126,6 +181,7 @@ void nish_exit(int32_t code) { (void)code; __builtin_trap(); }
126
181
  * parameter checks it on entry (WP31 §9), so this is what lets such a module
127
182
  * link; the loader throws a RangeError before the call reaches it. */
128
183
  void nish_panic(const void *s) { (void)s; __builtin_trap(); }
184
+ #endif
129
185
  /* Output has nowhere to go either. */
130
186
  void nish_write(const void *s, int32_t fd, _Bool newline) { (void)s; (void)fd; (void)newline; }
131
187