@amritk/nish-x86_64-linux 0.17.0 → 0.20.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 +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +231 -20
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +1376 -42
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1412 -70
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/* Nish runtime, the copy-at-join half: how a string or an array of scalars
|
|
2
|
+
* crosses from one thread's arena to another's, and how a `Mutex` keeps the
|
|
3
|
+
* strings stored through its guard.
|
|
4
|
+
*
|
|
5
|
+
* A translation unit of its own rather than more of runtime-parallel.c, for
|
|
6
|
+
* the reason that file's header gives for being one: each file carries its own
|
|
7
|
+
* measured `.text*` ceiling in tests/runtime-budgets.js, and this one would not
|
|
8
|
+
* fit in what runtime-parallel.c had left (RUNTIME_CROSS_TEXT_BUDGET says how
|
|
9
|
+
* much it needs). Apart, a program that divides a range of numbers links none
|
|
10
|
+
* of this, and section GC drops what a program that crosses no string calls.
|
|
11
|
+
*
|
|
12
|
+
* A task's or a chunk's arena is rewound once its job is done
|
|
13
|
+
* (`nish_pool_drain`, and the release the compiler emits after a task or a
|
|
14
|
+
* chunk returns), so a string or an array it answers, sends or stores through
|
|
15
|
+
* a guard cannot stay where it was made. `nish_cross_out` runs on the thread
|
|
16
|
+
* that made the value: one that was bumped in its arena since `mark` (0: at
|
|
17
|
+
* any time) is copied into a block of its own, outside every arena, and
|
|
18
|
+
* answered with its low bit set. Any other value -- a literal, or one the
|
|
19
|
+
* parent made, which outlives the join -- is answered as it is, so a task that
|
|
20
|
+
* answers its parent's array answers that array, as it does under Node.
|
|
21
|
+
* `nish_cross_send` is a channel's: it copies every string, which costs the
|
|
22
|
+
* string's length and nothing that grows with the sender's arena.
|
|
23
|
+
* `nish_cross_in` runs on the thread that takes the value: a tagged one is
|
|
24
|
+
* copied into its arena and its block freed, or, when it is large, its block
|
|
25
|
+
* becomes a chunk of that arena, so every block is either freed by the one
|
|
26
|
+
* `cross_in` its value meets or freed with the arena that adopted it. A map's
|
|
27
|
+
* elements do not go one block each: a chunk packs them into blocks of its
|
|
28
|
+
* own (`nish_cross_keep`), which the caller adopts whole, or copies in and
|
|
29
|
+
* frees when they hold little (`nish_cross_adopt`). Arena blocks,
|
|
30
|
+
* literals and the copies here are all 8-aligned, which is what keeps the low
|
|
31
|
+
* bit free for the tag.
|
|
32
|
+
*
|
|
33
|
+
* `esize` is 0 for a string and the element's size for an array of scalars,
|
|
34
|
+
* which holds no pointer, so one flat copy of its header and its elements is
|
|
35
|
+
* the whole value. A panic ends the process, and every block with it.
|
|
36
|
+
*
|
|
37
|
+
* It compiles in both configurations, as runtime-parallel.c does: a task run
|
|
38
|
+
* on the calling thread at the join is rewound all the same, so the copies are
|
|
39
|
+
* needed whether or not there are threads; only the guard set's lock is
|
|
40
|
+
* `-DNISH_THREADS` alone. */
|
|
41
|
+
#include "nish.h"
|
|
42
|
+
|
|
43
|
+
#include <stddef.h>
|
|
44
|
+
#include <stdlib.h>
|
|
45
|
+
#include <string.h>
|
|
46
|
+
#include <unistd.h>
|
|
47
|
+
|
|
48
|
+
#if defined(NISH_THREADS) && !defined(__wasi__) && !defined(__wasm__)
|
|
49
|
+
#define NISH_CROSS_LOCKED 1
|
|
50
|
+
#include <pthread.h>
|
|
51
|
+
#endif
|
|
52
|
+
|
|
53
|
+
/* runtime.c's `nish_chunk`, which nish.h leaves opaque (`nish_arena.chunks` is
|
|
54
|
+
* a `void *` there). runtime.c asserts the same three facts of its own
|
|
55
|
+
* definition, so a change to either side fails to compile until the other
|
|
56
|
+
* follows. */
|
|
57
|
+
typedef struct nish_xchunk { struct nish_xchunk *next; uint64_t cap; } nish_xchunk;
|
|
58
|
+
_Static_assert(sizeof(nish_xchunk) == 16, "runtime.c's nish_chunk: { next, cap }, the bytes after it");
|
|
59
|
+
_Static_assert(offsetof(nish_xchunk, next) == 0, "runtime.c's nish_chunk: next first");
|
|
60
|
+
_Static_assert(offsetof(nish_xchunk, cap) == 8, "runtime.c's nish_chunk: cap second");
|
|
61
|
+
|
|
62
|
+
/* The end of the program when a copy cannot be allocated: nothing that
|
|
63
|
+
* crosses has a way to go on without the memory. */
|
|
64
|
+
static void nish_cross_oom(void) {
|
|
65
|
+
nish_panic_claim();
|
|
66
|
+
(void)!write(2, "nish: out of memory\n", 20);
|
|
67
|
+
_exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static void *nish_cross_block(uint64_t size) {
|
|
71
|
+
void *b = size <= (uint64_t)SIZE_MAX ? malloc((size_t)size) : 0;
|
|
72
|
+
if (!b) nish_cross_oom();
|
|
73
|
+
return b;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Whether `p` was bumped since `mark`: one walk of the chunks, for a single
|
|
77
|
+
* value. */
|
|
78
|
+
static int nish_since(uint64_t mark, uintptr_t p) {
|
|
79
|
+
for (const nish_xchunk *c = (const nish_xchunk *)nish_arena.chunks; c; c = c->next) {
|
|
80
|
+
uintptr_t base = (uintptr_t)(c + 1), end = base + c->cap;
|
|
81
|
+
int marked = mark >= base && mark <= end;
|
|
82
|
+
if (p >= (marked ? (uintptr_t)mark : base) && p < end) return 1;
|
|
83
|
+
if (marked) return 0;
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* The bytes `v` takes, rounded to 8: a string's header, bytes and NUL, or an
|
|
89
|
+
* array's header and elements. */
|
|
90
|
+
static uint64_t nish_cross_size(const void *v, uint64_t esize) {
|
|
91
|
+
uint64_t n = esize ? sizeof(nish_array) + ((const nish_array *)v)->len * esize
|
|
92
|
+
: 8 + ((const nish_str *)v)->len + 1;
|
|
93
|
+
return (n + 7) & ~(uint64_t)7;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* `v` copied to `at`, flat (an array's elements follow its header). */
|
|
97
|
+
static void nish_cross_put(char *at, const void *v, uint64_t esize) {
|
|
98
|
+
if (esize == 0) {
|
|
99
|
+
memcpy(at, v, (size_t)(8 + ((const nish_str *)v)->len + 1));
|
|
100
|
+
} else {
|
|
101
|
+
const nish_array *a = (const nish_array *)v;
|
|
102
|
+
nish_array *c = (nish_array *)at;
|
|
103
|
+
c->len = c->cap = a->len;
|
|
104
|
+
c->data = (char *)(c + 1);
|
|
105
|
+
if (a->len) memcpy(c->data, a->data, (size_t)(a->len * esize));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* `n` bytes after a chunk header, so the block can become a chunk of the arena
|
|
110
|
+
* that takes it: runtime.c's `nish_free_until` frees it as it frees one
|
|
111
|
+
* `nish_arena_grow` made. */
|
|
112
|
+
static nish_xchunk *nish_cross_chunk(uint64_t n) {
|
|
113
|
+
nish_xchunk *c = (nish_xchunk *)nish_cross_block(sizeof *c + n);
|
|
114
|
+
c->cap = n;
|
|
115
|
+
return c;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Links the block `c` into this thread's arena as its newest chunk, and bumps
|
|
119
|
+
* from it, full to its `cap`. A chunk grown here would
|
|
120
|
+
* sit in the same place, so a release, a keep and a reset treat these as they
|
|
121
|
+
* treat it: whatever rewinds the arena past this point frees them. */
|
|
122
|
+
static void nish_cross_link(nish_xchunk *c) {
|
|
123
|
+
c->next = (nish_xchunk *)nish_arena.chunks;
|
|
124
|
+
nish_arena = (struct nish_arena){(char *)(c + 1), c->cap, c->cap, c};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* `v` in a block of its own, tagged. */
|
|
128
|
+
static void *nish_cross_copy(const void *v, uint64_t esize) {
|
|
129
|
+
nish_xchunk *c = nish_cross_chunk(nish_cross_size(v, esize));
|
|
130
|
+
nish_cross_put((char *)(c + 1), v, esize);
|
|
131
|
+
return (void *)((uintptr_t)(c + 1) | 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
void *nish_cross_out(void *v, uint64_t esize, uint64_t mark) {
|
|
135
|
+
return v && nish_since(mark, (uintptr_t)v) ? nish_cross_copy(v, esize) : v;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
void *nish_cross_send(void *s) { return nish_cross_copy(s, 0); }
|
|
139
|
+
|
|
140
|
+
/* The transfer state of the chunk this thread is running
|
|
141
|
+
* (`nish_cross_begin`): the list its blocks go on, the block being filled and
|
|
142
|
+
* how much of it is, and the state it hides, which `nish_cross_end` restores
|
|
143
|
+
* -- a map nested in a chunk's body runs its own chunks inside it. A map's
|
|
144
|
+
* sequential path begins with no list, which hides the outer state without
|
|
145
|
+
* pushing one, so its elements are not packed into the outer chunk's block. */
|
|
146
|
+
typedef struct nish_xfer {
|
|
147
|
+
void **list;
|
|
148
|
+
nish_xchunk *block;
|
|
149
|
+
uint64_t off;
|
|
150
|
+
struct nish_xfer *prev;
|
|
151
|
+
} nish_xfer;
|
|
152
|
+
_Static_assert(sizeof(nish_xfer) == (sizeof(void *) == 8 ? 32 : 24),
|
|
153
|
+
"src/emit-parallel.ts allocas the state as { i8*, i8*, i64, i8* }");
|
|
154
|
+
static NISH_TLS nish_xfer *nish_xfer_top;
|
|
155
|
+
|
|
156
|
+
void nish_cross_begin(void *state, void **list) {
|
|
157
|
+
nish_xfer *x = (nish_xfer *)state;
|
|
158
|
+
*x = (nish_xfer){list, 0, 0, nish_xfer_top};
|
|
159
|
+
nish_xfer_top = list ? x : 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The block being filled records how much of it is, as its `cap`, so the
|
|
163
|
+
* join reads what each chunk kept and an adopted block is full where it ends. */
|
|
164
|
+
void nish_cross_end(void *state) {
|
|
165
|
+
nish_xfer *x = (nish_xfer *)state;
|
|
166
|
+
if (x->block) x->block->cap = x->off;
|
|
167
|
+
nish_xfer_top = x->prev;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* `nish_arena_release(mark)` for an element's rewind, but keeping the newest
|
|
171
|
+
* chunk when the body grew it past `mark`'s: a mark at the end of a full chunk
|
|
172
|
+
* would otherwise have every element's body malloc a fresh chunk and its
|
|
173
|
+
* release free it again. Only a 64 KB one is kept, emptied, in front of
|
|
174
|
+
* `mark`'s chunk, with those between freed; an older mark still finds its own
|
|
175
|
+
* chunk below it, and what `mark`'s chunk had left is given up once. A mark
|
|
176
|
+
* in no chunk below the newest leaves the arena as it was, as the release
|
|
177
|
+
* does. */
|
|
178
|
+
static void nish_rewind(uint64_t mark) {
|
|
179
|
+
struct nish_arena *a = &nish_arena;
|
|
180
|
+
nish_xchunk *top = a->chunks;
|
|
181
|
+
char *buf = (char *)(top + 1);
|
|
182
|
+
if (mark && top && top->cap == 65536 && mark - (uintptr_t)buf > 65536) {
|
|
183
|
+
a->chunks = top->next;
|
|
184
|
+
nish_arena_release(mark);
|
|
185
|
+
if (a->buf != buf) {
|
|
186
|
+
top->next = a->chunks;
|
|
187
|
+
*a = (struct nish_arena){ buf, 0, 65536, top };
|
|
188
|
+
} else
|
|
189
|
+
a->chunks = top;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
nish_arena_release(mark);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* A map's or a `parallelFrom`'s element, kept as its chunk loop rewinds the
|
|
196
|
+
* arena to `mark`, the bump position before the body was called: everything
|
|
197
|
+
* else the body bumped is dead once it has answered, so a chunk holds one
|
|
198
|
+
* element's temporaries at a time rather than all of its elements'. A value
|
|
199
|
+
* made since `mark` is copied, in a chunk of a divided region, to the end of
|
|
200
|
+
* the chunk's transfer block -- a new one, pushed on the region's list when it
|
|
201
|
+
* does not fit, of 4 KB doubling to 64 KB or of the value's size, so what a
|
|
202
|
+
* chunk leaves unused is bounded by what it keeps -- and is answered there;
|
|
203
|
+
* the caller adopts the blocks at the join (`nish_cross_adopt`). On a map's
|
|
204
|
+
* sequential path, where no state is pushed, it goes out to a block of its own
|
|
205
|
+
* and back in below `mark`. One made before `mark` is answered as it is. */
|
|
206
|
+
void *nish_cross_keep(void *v, uint64_t esize, uint64_t mark) {
|
|
207
|
+
nish_xfer *x = nish_xfer_top;
|
|
208
|
+
if (v && nish_since(mark, (uintptr_t)v)) {
|
|
209
|
+
if (!x) {
|
|
210
|
+
v = nish_cross_copy(v, esize);
|
|
211
|
+
} else {
|
|
212
|
+
uint64_t n = nish_cross_size(v, esize);
|
|
213
|
+
if (!x->block || x->block->cap - x->off < n) {
|
|
214
|
+
uint64_t want = x->block && x->block->cap < 32768 ? x->block->cap * 2 : x->block ? 65536 : 4096;
|
|
215
|
+
x->block = nish_cross_chunk(n > want ? n : want);
|
|
216
|
+
x->block->next = __atomic_exchange_n((nish_xchunk **)x->list, x->block, __ATOMIC_RELAXED);
|
|
217
|
+
x->off = 0;
|
|
218
|
+
}
|
|
219
|
+
char *at = (char *)(x->block + 1) + x->off;
|
|
220
|
+
x->off += n;
|
|
221
|
+
nish_cross_put(at, v, esize);
|
|
222
|
+
v = at;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
nish_rewind(mark);
|
|
226
|
+
return nish_cross_in(v, esize);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* A value of 64 KB or more that does not fit what is left of the current
|
|
230
|
+
* chunk is adopted rather than copied: `nish_arena_grow` would have made a
|
|
231
|
+
* chunk of exactly its size for the copy, and the block is that chunk
|
|
232
|
+
* already. A smaller one is copied, so a run of small values never makes a
|
|
233
|
+
* chunk each, which a release would walk. */
|
|
234
|
+
void *nish_cross_in(void *p, uint64_t esize) {
|
|
235
|
+
if (!((uintptr_t)p & 1)) return p;
|
|
236
|
+
void *v = (void *)((uintptr_t)p & ~(uintptr_t)1);
|
|
237
|
+
nish_xchunk *c = (nish_xchunk *)v - 1;
|
|
238
|
+
uint64_t n = c->cap;
|
|
239
|
+
if (n >= 65536 && n > nish_arena.cap - nish_arena.off) {
|
|
240
|
+
nish_cross_link(c);
|
|
241
|
+
return v;
|
|
242
|
+
}
|
|
243
|
+
char *r = (char *)nish_alloc_struct(n);
|
|
244
|
+
nish_cross_put(r, v, esize);
|
|
245
|
+
free(c);
|
|
246
|
+
return r;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Once every chunk is done, on the thread that called the region, with `dst`
|
|
250
|
+
* the array the region wrote. When the blocks the chunks filled hold 64 KB or
|
|
251
|
+
* more, they become the newest chunks of its arena (`nish_cross_link`), so
|
|
252
|
+
* the elements in them stay where they are, and the cost is one link per
|
|
253
|
+
* block rather than a copy per element. Less is copied, element by element,
|
|
254
|
+
* into the arena and the blocks freed, as `nish_cross_in` copies a small
|
|
255
|
+
* value: an adopted block leaves the arena's current chunk behind, and a
|
|
256
|
+
* region run in a loop would leave one per run. */
|
|
257
|
+
void nish_cross_adopt(void *list, void *dst, uint64_t esize) {
|
|
258
|
+
nish_xchunk *first = (nish_xchunk *)list, *c, *next;
|
|
259
|
+
uint64_t kept = 0;
|
|
260
|
+
for (c = first; c; c = c->next) kept += c->cap;
|
|
261
|
+
if (kept >= 65536) {
|
|
262
|
+
for (c = first; c; c = next) {
|
|
263
|
+
next = c->next;
|
|
264
|
+
nish_cross_link(c);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!first) return;
|
|
269
|
+
nish_array *a = (nish_array *)dst;
|
|
270
|
+
void **e = (void **)a->data;
|
|
271
|
+
for (uint64_t i = 0; i < a->len; i++) {
|
|
272
|
+
for (c = first; c; c = c->next) {
|
|
273
|
+
if ((uintptr_t)e[i] - (uintptr_t)(c + 1) < c->cap) {
|
|
274
|
+
char *r = (char *)nish_alloc_struct(nish_cross_size(e[i], esize));
|
|
275
|
+
nish_cross_put(r, e[i], esize);
|
|
276
|
+
e[i] = r;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (c = first; c; c = next) {
|
|
282
|
+
next = c->next;
|
|
283
|
+
free(c);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* ---- A string a guard stores ----
|
|
288
|
+
*
|
|
289
|
+
* `g.value = s` keeps `s` in memory the `Mutex` owns, since the task that
|
|
290
|
+
* stores it may have made it in its arena: `nish_guard_keep` copies it into a
|
|
291
|
+
* block of its own and answers the copy, and frees `old`, the string the slot
|
|
292
|
+
* held, when it is a copy made here; `g.value = null` into a `string | null`
|
|
293
|
+
* slot frees that copy and keeps nothing. A read that keeps the string past the
|
|
294
|
+
* lock's block copies it into the reader's arena (the compiler calls
|
|
295
|
+
* `nish_str_new`), so nothing outside the lock points at a block, and the
|
|
296
|
+
* next store frees it. The copies are recorded in one set, `nish_kept`, which
|
|
297
|
+
* is how `old` is told apart from the string `new Mutex` was given or a
|
|
298
|
+
* literal: those are never freed. A block still held when its `Mutex` is
|
|
299
|
+
* dropped stays allocated until the process ends -- one per string slot of
|
|
300
|
+
* the `Mutex`, not one per store. The set is one for the process, behind one
|
|
301
|
+
* lock of its own, since two `Mutex`es are locked by two tasks at once; it is
|
|
302
|
+
* taken only by a store, so stores through two different guards wait on each
|
|
303
|
+
* other for the moment it is held. The set holds each block's header, the
|
|
304
|
+
* pointer `malloc` answered, so a leak checker counts a block a dropped `Mutex`
|
|
305
|
+
* left as reachable rather than as possibly lost. */
|
|
306
|
+
static struct {
|
|
307
|
+
#ifdef NISH_CROSS_LOCKED
|
|
308
|
+
pthread_mutex_t mu;
|
|
309
|
+
#endif
|
|
310
|
+
uintptr_t *slots; /* open addressing; 0 empty, 1 a removed entry */
|
|
311
|
+
uint64_t cap, used;
|
|
312
|
+
} nish_kept = {
|
|
313
|
+
#ifdef NISH_CROSS_LOCKED
|
|
314
|
+
PTHREAD_MUTEX_INITIALIZER,
|
|
315
|
+
#endif
|
|
316
|
+
0, 0, 0};
|
|
317
|
+
|
|
318
|
+
static uint64_t nish_kept_at(uintptr_t p) { return (uint64_t)((p >> 3) * 0x9E3779B97F4A7C15ull); }
|
|
319
|
+
|
|
320
|
+
static void nish_kept_put(uintptr_t p) {
|
|
321
|
+
if ((nish_kept.used + 1) * 2 > nish_kept.cap) {
|
|
322
|
+
uintptr_t *old = nish_kept.slots;
|
|
323
|
+
uint64_t n = nish_kept.cap;
|
|
324
|
+
nish_kept.cap = n ? n * 2 : 64;
|
|
325
|
+
nish_kept.slots = (uintptr_t *)calloc((size_t)nish_kept.cap, sizeof(uintptr_t));
|
|
326
|
+
if (!nish_kept.slots) nish_cross_oom();
|
|
327
|
+
nish_kept.used = 0;
|
|
328
|
+
for (uint64_t i = 0; i < n; i++)
|
|
329
|
+
if (old[i] > 1) nish_kept_put(old[i]);
|
|
330
|
+
free(old);
|
|
331
|
+
}
|
|
332
|
+
uint64_t i = nish_kept_at(p) & (nish_kept.cap - 1);
|
|
333
|
+
while (nish_kept.slots[i] > 1) i = (i + 1) & (nish_kept.cap - 1);
|
|
334
|
+
if (nish_kept.slots[i] == 0) nish_kept.used++;
|
|
335
|
+
nish_kept.slots[i] = p;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Whether `p` is in the set, taking it out when it is. */
|
|
339
|
+
static int nish_kept_take(uintptr_t p) {
|
|
340
|
+
if (!nish_kept.cap) return 0;
|
|
341
|
+
uint64_t i = nish_kept_at(p) & (nish_kept.cap - 1);
|
|
342
|
+
for (; nish_kept.slots[i] != 0; i = (i + 1) & (nish_kept.cap - 1)) {
|
|
343
|
+
if (nish_kept.slots[i] == p) {
|
|
344
|
+
nish_kept.slots[i] = 1;
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* `old` is null when the slot is a `string | null` that held none, and `s`
|
|
352
|
+
* is null for a `null` stored into one: the slot's copy is freed and nothing
|
|
353
|
+
* is kept in its place, so the store answers null. */
|
|
354
|
+
void *nish_guard_keep(void *old, const void *s) {
|
|
355
|
+
void *b = s ? (void *)((uintptr_t)nish_cross_copy(s, 0) & ~(uintptr_t)1) : 0;
|
|
356
|
+
#ifdef NISH_CROSS_LOCKED
|
|
357
|
+
pthread_mutex_lock(&nish_kept.mu);
|
|
358
|
+
#endif
|
|
359
|
+
int mine = old && nish_kept_take((uintptr_t)((nish_xchunk *)old - 1));
|
|
360
|
+
if (b) nish_kept_put((uintptr_t)((nish_xchunk *)b - 1));
|
|
361
|
+
#ifdef NISH_CROSS_LOCKED
|
|
362
|
+
pthread_mutex_unlock(&nish_kept.mu);
|
|
363
|
+
#endif
|
|
364
|
+
if (mine) free((nish_xchunk *)old - 1);
|
|
365
|
+
return b;
|
|
366
|
+
}
|
package/runtime/runtime-host.c
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* before it trusts one.
|
|
6
6
|
*
|
|
7
7
|
* A translation unit of its own for the reason runtime-os.c is one: each file
|
|
8
|
-
* carries its own measured `.text*` ceiling in tests/
|
|
9
|
-
* 143 bytes of its ceiling left when these arrived, the
|
|
10
|
-
* that, and a ceiling is not raised to make room
|
|
11
|
-
* the numbers). Section GC still means a program that
|
|
12
|
-
* for none of them, and scripts/build.sh pairs this file
|
|
13
|
-
* the other two halves, so a link line still names one runtime.
|
|
8
|
+
* carries its own measured `.text*` ceiling in tests/runtime-budgets.js.
|
|
9
|
+
* runtime-os.c had 143 bytes of its ceiling left when these arrived, the
|
|
10
|
+
* four measure more than that, and a ceiling is not raised to make room
|
|
11
|
+
* (docs/MASTER_PLAN.md §2 has the numbers). Section GC still means a program that
|
|
12
|
+
* calls none of them pays for none of them, and scripts/build.sh pairs this file
|
|
13
|
+
* with runtime.c like the other two halves, so a link line still names one runtime.
|
|
14
14
|
*
|
|
15
15
|
* Every function here is platform code, and the two platforms differ in three
|
|
16
16
|
* places: `getrandom` on Linux and `getentropy` elsewhere, `st_mtim`
|
|
@@ -57,6 +57,7 @@ double nish_date_now(void) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
static NISH_COLD void nish_entropy_fail(uint64_t asked) {
|
|
60
|
+
nish_panic_claim();
|
|
60
61
|
if (asked > 65536) {
|
|
61
62
|
dprintf(2, "crypto.getRandomValues: %llu bytes asked for, and one call fills at most 65536\n",
|
|
62
63
|
(unsigned long long)asked);
|
package/runtime/runtime-net.c
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* `spawnSync` starts inherits none of them.
|
|
6
6
|
*
|
|
7
7
|
* A translation unit of its own for the reason runtime-host.c is one: each
|
|
8
|
-
* file carries its own measured `.text*` ceiling in tests/
|
|
9
|
-
* are a surface that grows, which would push another unit past its
|
|
10
|
-
* rather than into a new one. Section GC
|
|
8
|
+
* file carries its own measured `.text*` ceiling in tests/runtime-budgets.js,
|
|
9
|
+
* and sockets are a surface that grows, which would push another unit past its
|
|
10
|
+
* ceiling rather than into a new one. Section GC
|
|
11
11
|
* means a program that calls none of these pays for none of them, and
|
|
12
12
|
* scripts/build.sh pairs this file with runtime.c like the other halves, so a
|
|
13
13
|
* link line still names one runtime.
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* receive leaves `meta` at 0, 0, and a `meta[1]` of 0 is also what a real
|
|
40
40
|
* Not-ECT datagram reads as, so a program learns that Darwin has no ECN from
|
|
41
41
|
* the send, never from the receive. The Darwin branch is compiled by CI's Darwin bootstrap
|
|
42
|
-
* rows and run by
|
|
42
|
+
* rows and run by its `runtime-net (macos-latest)` job: tests/runtime-test.c and
|
|
43
|
+
* the `net_` sections of tests/run.js, on a mac. A WASI build has none of this (the checker refuses
|
|
43
44
|
* every `nish:net` call under a wasm target), so there the file is empty.
|
|
44
45
|
*/
|
|
45
46
|
#if defined(__wasi__) || defined(__wasm__)
|
|
@@ -54,6 +55,7 @@ typedef int nish_net_unused;
|
|
|
54
55
|
#include <errno.h>
|
|
55
56
|
#include <fcntl.h>
|
|
56
57
|
#include <netinet/in.h>
|
|
58
|
+
#include <netinet/tcp.h>
|
|
57
59
|
#include <stdint.h>
|
|
58
60
|
#include <string.h>
|
|
59
61
|
#include <sys/socket.h>
|
|
@@ -188,10 +190,11 @@ int32_t nish_net_local_port(int32_t fd) {
|
|
|
188
190
|
IPv4 host is an `AF_INET` socket; any other is `AF_INET6` with
|
|
189
191
|
`IPV6_V6ONLY` off, so `::` hears both families. A stream gets
|
|
190
192
|
`SO_REUSEADDR` and `arg` its backlog; a datagram socket's `arg` is
|
|
191
|
-
`udpBind`'s flags.
|
|
192
|
-
`SO_REUSEPORT
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
`udpBind`'s flags. `opts` is a stream's `tcpListenFlags` word: bit 1 asks
|
|
194
|
+
for `SO_REUSEPORT`, bit 2 for `TCP_NODELAY`. Every option is set before the
|
|
195
|
+
bind, which is where `SO_REUSEPORT` has to be, and where `UDP_GRO` has to be
|
|
196
|
+
for the first datagram to be coalesced. */
|
|
197
|
+
static int32_t nish_net_bound(const nish_str *host, int32_t port, int type, int32_t arg, int32_t opts) {
|
|
195
198
|
unsigned char a[16];
|
|
196
199
|
if ((uint32_t)port > 65535 || !nish_net_parse(host, a)) return -22;
|
|
197
200
|
int v4 = memcmp(a, nish_mapped_prefix, 12) == 0;
|
|
@@ -209,11 +212,11 @@ static int32_t nish_net_bound(const nish_str *host, int32_t port, int type, int3
|
|
|
209
212
|
/* Both families at once on an IPv6 socket; on an IPv4 one this fails with
|
|
210
213
|
`ENOPROTOOPT` and changes nothing, which is cheaper than asking. */
|
|
211
214
|
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof zero);
|
|
212
|
-
int bad = 0;
|
|
215
|
+
int bad = (opts & 1) ? setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof one) : 0;
|
|
213
216
|
if (type == SOCK_STREAM) {
|
|
214
217
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
|
|
218
|
+
if (opts & 2) bad |= setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
|
|
215
219
|
} else {
|
|
216
|
-
if (arg & 1) bad = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof one);
|
|
217
220
|
#if defined(__linux__)
|
|
218
221
|
if (arg & 2) bad |= setsockopt(fd, IPPROTO_UDP, UDP_GRO, &one, sizeof one);
|
|
219
222
|
/* ECN is always read. An IPv6 socket needs both: a datagram from an IPv4
|
|
@@ -236,7 +239,17 @@ static int32_t nish_net_bound(const nish_str *host, int32_t port, int type, int3
|
|
|
236
239
|
so a restarted server rebinds a port whose old connections are still in
|
|
237
240
|
TIME_WAIT. */
|
|
238
241
|
int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog) {
|
|
239
|
-
return nish_net_bound(host, port, SOCK_STREAM, backlog);
|
|
242
|
+
return nish_net_bound(host, port, SOCK_STREAM, backlog, 0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* `tcpListenFlags(host, port, backlog, flags)`: `tcpListen`'s socket, with
|
|
246
|
+
`SO_REUSEPORT` for flag 1, so several listeners of one uid share the port
|
|
247
|
+
(Linux spreads new connections across them; Darwin hands them all to one),
|
|
248
|
+
and `TCP_NODELAY` for flag 2, which the listener carries so that
|
|
249
|
+
`tcpAccept` sets it on each connection it takes. Any other bit is -22. */
|
|
250
|
+
int32_t nish_tcp_listen_flags(const nish_str *host, int32_t port, int32_t backlog, int32_t flags) {
|
|
251
|
+
if ((uint32_t)flags > 3) return -22;
|
|
252
|
+
return nish_net_bound(host, port, SOCK_STREAM, backlog, flags);
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
/* A socket address of `n` bytes into the caller's 18-byte form. An IPv4 peer
|
|
@@ -264,7 +277,18 @@ static void nish_net_put(nish_array *out, const nish_sockaddr *s, socklen_t n) {
|
|
|
264
277
|
/* `tcpAccept(fd, peer)`: the next connection as a descriptor of its own,
|
|
265
278
|
non-blocking and close-on-exec, with the peer's address written into
|
|
266
279
|
`peer`; -11 when none is waiting. A `peer` shorter than 18 bytes is -22
|
|
267
|
-
before anything is accepted, so no connection is taken and lost.
|
|
280
|
+
before anything is accepted, so no connection is taken and lost.
|
|
281
|
+
|
|
282
|
+
A listener opened with `tcpListenFlags` flag 2 holds `TCP_NODELAY`, and the
|
|
283
|
+
connection gets it here. Linux copies the option to an accepted socket,
|
|
284
|
+
but that is not a rule other systems keep, so it is read off the listener
|
|
285
|
+
and set on the connection everywhere. The listener is the record of the
|
|
286
|
+
flag, rather than a table beside the descriptors, so it holds for a
|
|
287
|
+
descriptor however the program came by it, and a closed listener's number
|
|
288
|
+
reused leaves nothing stale behind. A listener that cannot answer (not
|
|
289
|
+
TCP) has no Nagle to turn off; a connection on which the set fails is
|
|
290
|
+
closed and its failure answered, so a flag-2 listener never hands out a
|
|
291
|
+
socket without it. */
|
|
268
292
|
int32_t nish_tcp_accept(int32_t fd, nish_array *peer) {
|
|
269
293
|
if (peer->len < NISH_ADDR_BYTES) return -22;
|
|
270
294
|
nish_sockaddr s;
|
|
@@ -275,6 +299,14 @@ int32_t nish_tcp_accept(int32_t fd, nish_array *peer) {
|
|
|
275
299
|
int c = nish_net_setup(accept(fd, &s.sa, &n));
|
|
276
300
|
#endif
|
|
277
301
|
if (c < 0) return nish_net_fail();
|
|
302
|
+
int nodelay = 0;
|
|
303
|
+
socklen_t size = sizeof nodelay;
|
|
304
|
+
if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, &size) == 0 && nodelay != 0 &&
|
|
305
|
+
setsockopt(c, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof nodelay) != 0) {
|
|
306
|
+
int e = errno;
|
|
307
|
+
close(c);
|
|
308
|
+
return nish_net_err(e);
|
|
309
|
+
}
|
|
278
310
|
nish_net_put(peer, &s, n);
|
|
279
311
|
return c;
|
|
280
312
|
}
|
|
@@ -348,7 +380,7 @@ int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags) {
|
|
|
348
380
|
#if !defined(__linux__)
|
|
349
381
|
if (flags & 2) return -95;
|
|
350
382
|
#endif
|
|
351
|
-
return nish_net_bound(host, port, SOCK_DGRAM, flags);
|
|
383
|
+
return nish_net_bound(host, port, SOCK_DGRAM, flags, flags & 1);
|
|
352
384
|
}
|
|
353
385
|
|
|
354
386
|
/* Room for the two control messages either direction carries: a segment size
|
|
@@ -503,11 +535,15 @@ static int nish_poll_change(int32_t loop, int32_t fd, int filter, int flags, int
|
|
|
503
535
|
|
|
504
536
|
/* Add, modify or remove `fd` in `loop`. epoll does each in one call, with
|
|
505
537
|
the token in `data.u32`. kqueue has a filter per direction rather than a
|
|
506
|
-
set of events, so
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
answers
|
|
510
|
-
a
|
|
538
|
+
set of events, so the answers epoll gives are built from filters: a
|
|
539
|
+
descriptor in the loop has at least one, a disabled read filter standing for
|
|
540
|
+
one added watching nothing; an add first touches both filters with no flags,
|
|
541
|
+
which answers ENOENT for a filter not there, and is EEXIST when either is;
|
|
542
|
+
a modification or a removal deletes both, and is ENOENT when neither was
|
|
543
|
+
there; a modification then adds the ones asked for, each carrying the token
|
|
544
|
+
in `udata`. A touch rewrites the filter's `udata`, and the add touches both
|
|
545
|
+
filters, so the add that answers -17 leaves the descriptor watched for its
|
|
546
|
+
old events, both ways, under the token it gave rather than the old one. */
|
|
511
547
|
static int32_t nish_poll_ctl(int32_t loop, int op, int32_t fd, int32_t events, int32_t token) {
|
|
512
548
|
if ((uint32_t)events > 3) return -22;
|
|
513
549
|
#if defined(__linux__)
|
|
@@ -517,14 +553,23 @@ static int32_t nish_poll_ctl(int32_t loop, int op, int32_t fd, int32_t events, i
|
|
|
517
553
|
e.data.u32 = (uint32_t)token;
|
|
518
554
|
return epoll_ctl(loop, op, fd, &e) != 0 ? nish_net_fail() : 0;
|
|
519
555
|
#else
|
|
520
|
-
int
|
|
521
|
-
if (op
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (
|
|
556
|
+
int r, w;
|
|
557
|
+
if (op == NISH_POLL_ADD) {
|
|
558
|
+
r = nish_poll_change(loop, fd, EVFILT_READ, 0, token);
|
|
559
|
+
w = nish_poll_change(loop, fd, EVFILT_WRITE, 0, token);
|
|
560
|
+
if (r == 0 || w == 0) return -17;
|
|
561
|
+
if (r != ENOENT) return nish_net_err(r);
|
|
562
|
+
if (w != ENOENT) return nish_net_err(w);
|
|
563
|
+
} else {
|
|
564
|
+
r = nish_poll_change(loop, fd, EVFILT_READ, EV_DELETE, 0);
|
|
565
|
+
w = nish_poll_change(loop, fd, EVFILT_WRITE, EV_DELETE, 0);
|
|
566
|
+
if (r != 0 && w != 0) return nish_net_err(r);
|
|
567
|
+
if (op == NISH_POLL_REMOVE) return 0;
|
|
525
568
|
}
|
|
569
|
+
int e = 0;
|
|
526
570
|
if (events & 1) e = nish_poll_change(loop, fd, EVFILT_READ, EV_ADD | EV_ENABLE, token);
|
|
527
571
|
if (e == 0 && (events & 2)) e = nish_poll_change(loop, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, token);
|
|
572
|
+
if (events == 0) e = nish_poll_change(loop, fd, EVFILT_READ, EV_ADD | EV_DISABLE, token);
|
|
528
573
|
return e != 0 ? nish_net_err(e) : 0;
|
|
529
574
|
#endif
|
|
530
575
|
}
|
|
@@ -549,8 +594,7 @@ int32_t nish_poll_remove(int32_t loop, int32_t fd) { return nish_poll_ctl(loop,
|
|
|
549
594
|
`NISH_POLL_CHUNK`. A signal that interrupts the wait answers 0, as a
|
|
550
595
|
timeout does, rather than waiting again past the deadline; the program's
|
|
551
596
|
loop comes round and, if it watches `signalFd()`, finds the signal ready.
|
|
552
|
-
|
|
553
|
-
same token, one per filter. A `ready` shorter than 2 is -22. */
|
|
597
|
+
A `ready` shorter than 2 is -22. */
|
|
554
598
|
int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms) {
|
|
555
599
|
uint64_t room = ready->len / 2;
|
|
556
600
|
if (room == 0) return -22;
|
|
@@ -560,22 +604,37 @@ int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms) {
|
|
|
560
604
|
struct epoll_event e[NISH_POLL_CHUNK];
|
|
561
605
|
/* epoll waits forever for any negative timeout, as the language does. */
|
|
562
606
|
int n = epoll_wait(loop, e, max, timeout_ms);
|
|
563
|
-
#else
|
|
564
|
-
struct kevent e[NISH_POLL_CHUNK];
|
|
565
|
-
struct timespec t = {timeout_ms / 1000, (long)(timeout_ms % 1000) * 1000000};
|
|
566
|
-
int n = kevent(loop, NULL, 0, e, max, timeout_ms < 0 ? NULL : &t);
|
|
567
|
-
#endif
|
|
568
607
|
if (n < 0) return errno == EINTR ? 0 : nish_net_fail();
|
|
569
608
|
for (int i = 0; i < n; i++) {
|
|
570
|
-
#if defined(__linux__)
|
|
571
609
|
uint32_t got = e[i].events;
|
|
572
610
|
out[2 * i] = (int32_t)e[i].data.u32;
|
|
573
611
|
out[2 * i + 1] = ((got & EPOLLIN) ? 1 : 0) | ((got & EPOLLOUT) ? 2 : 0) | ((got & (EPOLLHUP | EPOLLERR)) ? 4 : 0);
|
|
612
|
+
}
|
|
613
|
+
return n;
|
|
574
614
|
#else
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
615
|
+
/* A record per filter, so a descriptor ready both ways is two, folded here
|
|
616
|
+
into one pair as epoll's one event is. Twice the room is asked for, so a
|
|
617
|
+
full `ready` is not cut short by the folding; a descriptor that finds no
|
|
618
|
+
pair free is still ready at the next wait. */
|
|
619
|
+
struct kevent e[NISH_POLL_CHUNK];
|
|
620
|
+
struct timespec t = {timeout_ms / 1000, (long)(timeout_ms % 1000) * 1000000};
|
|
621
|
+
int got = kevent(loop, NULL, 0, e, 2 * max < NISH_POLL_CHUNK ? 2 * max : NISH_POLL_CHUNK, timeout_ms < 0 ? NULL : &t);
|
|
622
|
+
if (got < 0) return errno == EINTR ? 0 : nish_net_fail();
|
|
623
|
+
int n = 0;
|
|
624
|
+
for (int i = 0; i < got; i++) {
|
|
625
|
+
int32_t bits = (e[i].filter == EVFILT_READ ? 1 : 2) | ((e[i].flags & (EV_EOF | EV_ERROR)) ? 4 : 0);
|
|
626
|
+
int k = 0;
|
|
627
|
+
while (k < n && e[k].ident != e[i].ident) k++;
|
|
628
|
+
if (k == n) {
|
|
629
|
+
if (n == max) continue;
|
|
630
|
+
e[n] = e[i];
|
|
631
|
+
out[2 * n] = (int32_t)(intptr_t)e[i].udata;
|
|
632
|
+
out[2 * n + 1] = 0;
|
|
633
|
+
n++;
|
|
634
|
+
}
|
|
635
|
+
out[2 * k + 1] |= bits;
|
|
578
636
|
}
|
|
579
637
|
return n;
|
|
638
|
+
#endif
|
|
580
639
|
}
|
|
581
640
|
#endif
|