@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.
- package/INSTALL.md +73 -59
- package/bin/nish +0 -0
- package/docs/AI.md +50 -4
- package/package.json +1 -1
- package/runtime/async.d.mts +2 -0
- package/runtime/async.mjs +7 -0
- package/runtime/nish.d.ts +4849 -430
- package/runtime/nish.h +25 -0
- package/runtime/page.d.ts +53 -0
- package/runtime/page.mjs +146 -0
- package/runtime/runtime-async.c +557 -0
- package/runtime/runtime-wasm.c +63 -7
- package/runtime/vite.d.ts +30 -0
- package/runtime/vite.mjs +150 -0
- package/scripts/build.sh +252 -15
- package/std/README.md +107 -82
- package/std/base64.ts +34 -5
- package/std/bytes.ts +279 -59
- package/std/cli-args.ts +296 -70
- package/std/cli-flags.ts +184 -19
- package/std/cli.ts +475 -54
- package/std/collections.ts +121 -23
- package/std/compress/checksum.ts +44 -14
- package/std/compress/deflate.ts +224 -111
- package/std/compress/gzip.ts +217 -53
- package/std/compress/zlib.ts +63 -7
- package/std/crypto/aes.ts +128 -52
- package/std/crypto/base64url.ts +29 -5
- package/std/crypto/bcrypt-batch.ts +101 -34
- package/std/crypto/bcrypt.ts +93 -27
- package/std/crypto/chacha20poly1305.ts +69 -14
- package/std/crypto/ct.ts +32 -13
- package/std/crypto/ed25519.ts +66 -16
- package/std/crypto/hkdf.ts +198 -13
- package/std/crypto/hmac.ts +242 -37
- package/std/crypto/jwt.ts +204 -72
- package/std/crypto/p256.ts +222 -72
- package/std/crypto/p384.ts +51 -23
- package/std/crypto/pki-roots.ts +32 -2
- package/std/crypto/pki.ts +99 -30
- package/std/crypto/rsa.ts +56 -22
- package/std/crypto/sha1.ts +3 -0
- package/std/crypto/sha256.ts +24 -1
- package/std/crypto/sha512.ts +99 -14
- package/std/crypto/x25519.ts +163 -24
- package/std/crypto/x509-random.ts +10 -0
- package/std/crypto/x509.ts +133 -66
- package/std/host/ring.ts +124 -0
- package/std/json-equal.ts +31 -11
- package/std/json-format.ts +112 -13
- package/std/json-number.ts +76 -26
- package/std/json-pointer.ts +23 -1
- package/std/json-tokenizer.ts +126 -51
- package/std/json-typed.ts +757 -96
- package/std/json-value.ts +438 -157
- package/std/json.ts +170 -38
- package/std/map.ts +25 -7
- package/std/math.ts +36 -4
- package/std/net/buffer-pool.ts +42 -11
- package/std/net/dns-config.ts +109 -23
- package/std/net/dns-message.ts +62 -7
- package/std/net/dns-resolver.ts +247 -53
- package/std/net/dns.ts +203 -35
- package/std/net/fetch.ts +1267 -290
- package/std/net/handler.ts +296 -80
- package/std/net/hpack.ts +290 -77
- package/std/net/http-compress.ts +152 -36
- package/std/net/http-fields.ts +85 -9
- package/std/net/http1-client-codec.ts +129 -44
- package/std/net/http1-codec.ts +279 -114
- package/std/net/http1-server.ts +409 -139
- package/std/net/http2-frame.ts +289 -51
- package/std/net/http2-tls.ts +152 -49
- package/std/net/http2.ts +314 -160
- package/std/net/http3-frame.ts +165 -27
- package/std/net/http3-server.ts +282 -131
- package/std/net/http3.ts +377 -173
- package/std/net/outbound.ts +37 -3
- package/std/net/postgres-carrier.ts +144 -8
- package/std/net/postgres.ts +898 -260
- package/std/net/prefork.ts +202 -65
- package/std/net/qpack.ts +120 -32
- package/std/net/quic-conn-ack.ts +60 -12
- package/std/net/quic-conn-cid.ts +135 -27
- package/std/net/quic-conn-params.ts +109 -22
- package/std/net/quic-datagram.ts +50 -3
- package/std/net/quic-frame.ts +333 -23
- package/std/net/quic-listener.ts +161 -75
- package/std/net/quic-packet.ts +373 -73
- package/std/net/quic-recovery.ts +287 -65
- package/std/net/quic-stream.ts +436 -146
- package/std/net/quic.ts +693 -323
- package/std/net/router.ts +87 -40
- package/std/net/tls/client.ts +167 -51
- package/std/net/tls/codec.ts +494 -68
- package/std/net/tls/record-client.ts +140 -73
- package/std/net/tls/record-server.ts +233 -105
- package/std/net/tls/record.ts +142 -36
- package/std/net/tls/schedule.ts +235 -26
- package/std/net/tls/ticket.ts +161 -40
- package/std/net/tls/verify.ts +57 -10
- package/std/net/tls-tcp.ts +308 -95
- package/std/net/tls.ts +276 -102
- package/std/net/upstream.ts +81 -45
- package/std/net/url.ts +109 -22
- package/std/net/websocket.ts +140 -26
- package/std/net/webtransport.ts +243 -106
- package/std/pair.ts +10 -2
- package/std/random.ts +42 -15
- package/std/regex.ts +422 -257
- package/std/region.ts +70 -15
- package/std/secret.ts +52 -11
- package/std/sort.ts +17 -2
- package/std/sqlite.ts +256 -35
- package/std/sync/ring.ts +229 -56
- package/std/sync/shm.ts +203 -23
- package/std/term-io.ts +154 -78
- package/std/term-pixels.ts +144 -19
- package/std/term-probe.ts +77 -35
- package/std/term-rate.ts +199 -118
- package/std/term.ts +400 -175
- package/std/testing.ts +130 -32
- package/std/text.ts +84 -17
- package/std/threads.ts +248 -56
- package/std/yaml-json.ts +111 -97
- package/std/yaml-lines.ts +47 -15
- package/std/yaml-write.ts +45 -30
- 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
|
package/runtime/runtime-wasm.c
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|