@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- 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 +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- 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 +501 -62
- 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 +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- 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 +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/runtime/nish.h
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
* `runtime/runtime-os.c` holds everything that wraps a system call: the file
|
|
9
9
|
* functions, the directory and subprocess calls, `nish_getenv`, the monotonic
|
|
10
10
|
* clock, `nish_platform` / `nish_arch` — and runtime-parallel.c, runtime-host.c,
|
|
11
|
-
* runtime-net.c
|
|
12
|
-
* time)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* runtime-net.c, runtime-simd.c (the byte searches that read a vector at a
|
|
12
|
+
* time), runtime-aes.c (AES-GCM on the CPU's AES instructions) and
|
|
13
|
+
* runtime-term.c (stdin and the terminal, `nish:term`) split off
|
|
14
|
+
* the same way since. They are separate so that each carries its own measured
|
|
15
|
+
* code-size ceiling (docs/wp7-runtime.md, "Runtime additions and budget"); a
|
|
16
|
+
* host links all of them, which `scripts/build.sh` does for you when handed
|
|
17
|
+
* runtime.c, runtime-aes.c included: the compiler calls its kernels from
|
|
18
|
+
* std/crypto/aes.ts, so `scripts/build.sh` pairs it and `src/run-cache.ts`
|
|
19
|
+
* caches it like the others. `tests/run.js` checks that every runtime function
|
|
16
20
|
* declared in runtime.ts has a prototype in this file.
|
|
17
21
|
*
|
|
18
22
|
* Headers generated by `nish --emit-header` include this one, so keep
|
|
@@ -151,10 +155,45 @@ int64_t nish_str_index_of_from(const nish_str *s, const nish_str *sub, int64_t f
|
|
|
151
155
|
set outside 1..16 bytes is answered by the scalar path rather than refused,
|
|
152
156
|
but only 1..16 is the contract. Variable-time: never call it on a secret. */
|
|
153
157
|
int64_t nish_str_index_of_any(const nish_str *s, const nish_str *set, int64_t from);
|
|
158
|
+
/* WP38 S6a, the byte-class runs of `nish/bytes`: the first index i in
|
|
159
|
+
[from, to) whose byte p[i] is outside the class, or `to`. `p` is an array's
|
|
160
|
+
data and the caller has clamped 0 <= from <= to <= its length, so no byte at
|
|
161
|
+
or past `to` is read. `skip_blank` steps over JSON's four blanks (space, tab,
|
|
162
|
+
newline, carriage return) and `skip_digits` over 0-9. `skip_json_text` steps
|
|
163
|
+
over printable ASCII other than `"` and `\`, and over every well-formed
|
|
164
|
+
UTF-8 sequence (RFC 3629) that ends at or before `to`; a malformed or cut-off
|
|
165
|
+
sequence stops the run at its lead byte. `skip_json_plain` steps over every
|
|
166
|
+
byte from 0x20 up other than `"` and `\` -- what a JSON writer copies without
|
|
167
|
+
an escape -- with no UTF-8 check. They live in runtime/runtime-bytes.c and
|
|
168
|
+
choose their path as `nish_str_index_of_any` does. Variable-time: never call
|
|
169
|
+
them on a secret. */
|
|
170
|
+
int64_t nish_bytes_skip_blank(const uint8_t *p, int64_t from, int64_t to);
|
|
171
|
+
int64_t nish_bytes_skip_digits(const uint8_t *p, int64_t from, int64_t to);
|
|
172
|
+
int64_t nish_bytes_skip_json_text(const uint8_t *p, int64_t from, int64_t to);
|
|
173
|
+
int64_t nish_bytes_skip_json_plain(const uint8_t *p, int64_t from, int64_t to);
|
|
154
174
|
/* `console.log(s)`: one write(2) of the bytes plus a newline to stdout. */
|
|
155
175
|
void nish_print(const nish_str *s);
|
|
156
176
|
/* `console.error` and the newline-free `write` / `writeError`: fd 1 or 2. */
|
|
157
177
|
void nish_write(const nish_str *s, int32_t fd, bool newline);
|
|
178
|
+
/* `panic(message)`, `expect` and a failed range entry: the message and a
|
|
179
|
+
newline on stderr, then `exit(1)`. Only the first panic of a process prints;
|
|
180
|
+
a thread that panics after it parks until that exit (`nish_panic_claim`). */
|
|
181
|
+
__attribute__((noreturn)) void nish_panic(const nish_str *s);
|
|
182
|
+
/* Called by every panic path before it prints: answers for the first caller
|
|
183
|
+
and parks every later one forever. Without threads no second caller can
|
|
184
|
+
exist, so it is empty there. */
|
|
185
|
+
#ifdef NISH_THREADS
|
|
186
|
+
void nish_panic_claim(void);
|
|
187
|
+
#else
|
|
188
|
+
#define nish_panic_claim() ((void)0)
|
|
189
|
+
#endif
|
|
190
|
+
/* How a panic that skips atexit ends: `_exit(1)`, after `nish_fatal_hook`
|
|
191
|
+
when it is set (runtime.c). The hook is the terminal's restore while
|
|
192
|
+
`setRawMode(true)` holds or `setTerminalRestore` bytes wait
|
|
193
|
+
(runtime-term.c), and null otherwise. Under `-DNISH_FUZZ_ABORT`
|
|
194
|
+
(scripts/fuzz.sh) it and `nish_panic` call `abort()`. */
|
|
195
|
+
__attribute__((noreturn)) void nish_fatal_exit(void);
|
|
196
|
+
extern void (*nish_fatal_hook)(void);
|
|
158
197
|
/* Number to string, as `${n}` does: decimal for integers, shortest round-trip (JS Number#toString) for f64. */
|
|
159
198
|
nish_str *nish_str_from_i32(int32_t v);
|
|
160
199
|
nish_str *nish_str_from_f64(double v);
|
|
@@ -164,7 +203,8 @@ nish_str *nish_str_from_i64(int64_t v);
|
|
|
164
203
|
nish_str *nish_str_from_u64(uint64_t v);
|
|
165
204
|
|
|
166
205
|
/* Process and file I/O (WP7). `nish_exit` never returns; the file functions
|
|
167
|
-
* print a message to stderr and
|
|
206
|
+
* print a message to stderr and `_exit(1)` on a fatal error, the first panic
|
|
207
|
+
* of a process only (`nish_panic_claim`).
|
|
168
208
|
*
|
|
169
209
|
* Every function here that takes a path, a name or an argument vector refuses
|
|
170
210
|
* one holding a NUL, answering as it answers a path that does not exist: the
|
|
@@ -228,17 +268,73 @@ typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array
|
|
|
228
268
|
* `elem_size` is `sizeof` one element, so for a record element type it is
|
|
229
269
|
* `sizeof(struct Point)` and the block is a C array of them. */
|
|
230
270
|
nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
271
|
+
/* `nish/regex`'s match-start search: the first i >= from with i + depth <=
|
|
272
|
+
len where every s[i + j], j < depth, is in position j's byte set, or -1.
|
|
273
|
+
Position j's set is written twice: as the 32 bytes of masks from 32 * j,
|
|
274
|
+
where byte b is in it when bit (b >> 4) & 7 of byte (b & 15) | (b >> 7) << 4
|
|
275
|
+
is set, and as the 256 bytes from 32 * depth + 256 * j, where it is in it
|
|
276
|
+
when byte b of them is not 0; the two must agree. The vector paths look for
|
|
277
|
+
the width positions from off first, in the first form, and test the others
|
|
278
|
+
in the second; the answer does not depend on them. from is already in
|
|
279
|
+
[0, len], depth is 1..8, width is 1..4, off + width <= depth and masks
|
|
280
|
+
holds at least 288 * depth bytes. It lives
|
|
281
|
+
in runtime/runtime-simd.c and picks its path as nish_str_index_of_any does.
|
|
282
|
+
Variable-time: never call it on a secret. */
|
|
283
|
+
int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t depth, int64_t off, int64_t width,
|
|
284
|
+
int64_t from);
|
|
231
285
|
/* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
|
|
232
286
|
* `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
|
|
233
287
|
* allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
|
|
234
288
|
* make one, because under --number-mode i32 its `length` would read back
|
|
235
|
-
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2).
|
|
289
|
+
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2).
|
|
290
|
+
* A host that hands an array in keeps the same bound: the N-API shim refuses a
|
|
291
|
+
* longer typed array, and a C host must not pass a `len` past it
|
|
292
|
+
* (docs/security/bytes.md, BYTES-1). */
|
|
236
293
|
/* The cold paths compiled code calls: `push` when len == cap, a failed bounds
|
|
237
294
|
* check. `nish_array_grow` moves `cap * elem_size` bytes into a fresh arena
|
|
238
295
|
* block and writes `data`/`cap`, so for a record element type it relocates the
|
|
239
296
|
* elements themselves — which is why the checker refuses to let a program hold
|
|
240
297
|
* a pointer to one across a `push` (WP15 section 2a). */
|
|
241
298
|
void nish_array_grow(nish_array *a, uint64_t elem_size);
|
|
299
|
+
/* ---- AES-GCM on the CPU's AES instructions (WP38 S5) ----------------------
|
|
300
|
+
* runtime/runtime-aes.c: AES-NI and PCLMULQDQ on x86-64, the ARMv8 AES and
|
|
301
|
+
* PMULL instructions on AArch64. The compiler calls each kernel in place of one
|
|
302
|
+
* private function of std/crypto/aes.ts, behind `nish_aes_hw`, and calls the
|
|
303
|
+
* bitsliced Nish function itself when that answers 0. The caller has checked
|
|
304
|
+
* every precondition: a kernel checks no bound, and reads and writes only
|
|
305
|
+
* what is listed. Every kernel is constant time in its key, its data and H.
|
|
306
|
+
*
|
|
307
|
+
* `rk` is a `u64[]` of `8 * (rounds + 1)` words, the length the bitsliced
|
|
308
|
+
* schedule has. On this path it holds, in bytes: the FIPS 197 schedule in
|
|
309
|
+
* [0, 16 * (rounds + 1)), round key r at 16 * r; H = E(K, 0^128), H^2, H^3 and
|
|
310
|
+
* H^4 at [240, 304), each as two u64s, low then high, of the block read
|
|
311
|
+
* big-endian; at [304, 368) each power's halves XORed together, twice over;
|
|
312
|
+
* and zero everywhere else. A process makes and uses every key under one
|
|
313
|
+
* answer of `nish_aes_hw`, so one array never meets both layouts. */
|
|
314
|
+
/* 1 when this process runs the kernels, 0 when it runs the bitsliced Nish
|
|
315
|
+
code. Resolved on the first call and fixed from then on: `NISH_AES=soft`
|
|
316
|
+
pins 0; unset, `hw` or any other value is the best this machine has, read
|
|
317
|
+
on that call from CPUID leaf 1 (AES, PCLMULQDQ, SSSE3) or AArch64's
|
|
318
|
+
AT_HWCAP (AES, PMULL). Always 0 on other targets, wasm32 among them. */
|
|
319
|
+
int32_t nish_aes_hw(void);
|
|
320
|
+
/* `key` (u8[]) is 16 or 32 bytes and `rounds` 10 or 14 to match. Writes the
|
|
321
|
+
kernel layout above into all of `rk`. */
|
|
322
|
+
void nish_aes_schedule(const nish_array *key, int32_t rounds, nish_array *rk);
|
|
323
|
+
/* `src` and `dst` (u8[]) each hold at least 16 bytes: dst[0..16) = E(K, src[0..16)). */
|
|
324
|
+
void nish_aes_block(const nish_array *rk, int32_t rounds, const nish_array *src, nish_array *dst);
|
|
325
|
+
/* GCTR (SP 800-38D §6.5) from inc32(J0): dst[i] = src[i] ^ keystream[i] for
|
|
326
|
+
i < length, the counter J0's last four bytes read big-endian and wrapping
|
|
327
|
+
mod 2^32. `j0` (u8[]) is 16 bytes, 0 <= length <= min(src.len, dst.len),
|
|
328
|
+
and `dst` may be `src`. */
|
|
329
|
+
void nish_aes_ctr(const nish_array *rk, int32_t rounds, const nish_array *j0, const nish_array *src,
|
|
330
|
+
nish_array *dst, int32_t length);
|
|
331
|
+
/* Absorbs data[0..length) (u8[]) into the GHASH state `y` (u64[2], the high
|
|
332
|
+
word first, each read big-endian), zero-padding the last block. `hHi:hLo` is
|
|
333
|
+
H, which the bitsliced walker reads; the kernel reads H and its powers from
|
|
334
|
+
`rk` instead, and they are the same H. 0 <= length <= data.len. */
|
|
335
|
+
void nish_aes_ghash(nish_array *y, uint64_t hHi, uint64_t hLo, const nish_array *rk, const nish_array *data,
|
|
336
|
+
int32_t length);
|
|
337
|
+
|
|
242
338
|
/* `secureZero(bytes)` (#385): sets every byte of `bytes` to zero through
|
|
243
339
|
* `volatile` stores, out of line, so that neither `-O2` nor `-flto` can drop
|
|
244
340
|
* them as dead when the buffer's life ends straight after the call. For
|
|
@@ -260,6 +356,13 @@ void nish_panic_slice(int64_t start, int64_t end, int64_t len);
|
|
|
260
356
|
extern nish_array *nish_argv;
|
|
261
357
|
void nish_argv_init(int32_t argc, char **argv);
|
|
262
358
|
|
|
359
|
+
/* The 128-bit SipHash-1-3 key of every `Map` and `Set` string hash
|
|
360
|
+
* (docs/security/json.md, JSON-13). A constructor the compiled module
|
|
361
|
+
* registers fills it from `getentropy` before `main`, once per process, and
|
|
362
|
+
* sets bit 0 of word 0 so a filled key is never all zero; nothing writes it
|
|
363
|
+
* afterwards. A host must not change it while any table exists. */
|
|
364
|
+
extern uint64_t nish_hash_key[2];
|
|
365
|
+
|
|
263
366
|
/* ---- Directories and subprocesses (WP14 D4) -----------------------------
|
|
264
367
|
* The calls a self-hosted driver needs to find its inputs and link its own
|
|
265
368
|
* output. Every one of them answers a value instead of exiting, exactly as
|
|
@@ -313,6 +416,28 @@ int32_t nish_spawn(const nish_array *argv);
|
|
|
313
416
|
* implementation in runtime-os.c backs both spawn builtins, which is what keeps
|
|
314
417
|
* the argument vector, the wait and the signal convention written once. */
|
|
315
418
|
int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_str *err);
|
|
419
|
+
/* `spawn(argv)` (WP34 N12): start element 0 of `argv` as `nish_spawn` does and
|
|
420
|
+
* answer the child's pid at once, without waiting, or a negative errno:
|
|
421
|
+
* -EINVAL for an empty `argv` or an argument holding a NUL, `posix_spawnp`'s
|
|
422
|
+
* own error otherwise, and -ENOSYS under WASI. The child is reaped by
|
|
423
|
+
* `nish_wait_pid`. */
|
|
424
|
+
int32_t nish_spawn_child(const nish_array *argv);
|
|
425
|
+
/* `kill(pid, sig)`: send `sig` to `pid` and answer 0, or a negative errno. Only
|
|
426
|
+
* 0, 2 (SIGINT), 9 (SIGKILL) and 15 (SIGTERM) are sent; any other signal, and
|
|
427
|
+
* a pid of 0 or below, is -EINVAL. -ESRCH when no process has the pid, -EPERM
|
|
428
|
+
* when it is another user's. Any process of this uid may be signalled, not
|
|
429
|
+
* only a child. */
|
|
430
|
+
int32_t nish_kill(int32_t pid, int32_t sig);
|
|
431
|
+
/* `waitPid(pid, flags)`: a fresh `i32[]` of two, `[pid, status]`. `pid` is a
|
|
432
|
+
* child's or -1 for any; flag 1 is WNOHANG and any other bit is -EINVAL, as is
|
|
433
|
+
* a pid that is neither -1 nor positive. The status is `nish_spawn`'s: the exit
|
|
434
|
+
* code, or `128 + n` for signal `n`. `[0, 0]` is a WNOHANG wait with a child
|
|
435
|
+
* left and none exited; a failure is `[-errno, 0]` (-ECHILD: no child; -EINTR:
|
|
436
|
+
* a signal interrupted the wait). In the arena. */
|
|
437
|
+
nish_array *nish_wait_pid(int32_t pid, int32_t flags);
|
|
438
|
+
/* `totalMemory()`: bytes of physical memory (Linux: `_SC_PHYS_PAGES` times the
|
|
439
|
+
* page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
|
|
440
|
+
int64_t nish_total_memory(void);
|
|
316
441
|
|
|
317
442
|
/* ---- The environment (WP19 R1) ------------------------------------------
|
|
318
443
|
* `getenv(name)`: the value of environment variable `name`, copied into the
|
|
@@ -410,6 +535,8 @@ bool nish_is_executable(const nish_str *path);
|
|
|
410
535
|
* `nish_net_local_port(fd)`: the port a socket is bound to.
|
|
411
536
|
* `nish_tcp_listen(host, port, backlog)`: `SO_REUSEADDR`, and `::` hears
|
|
412
537
|
* both families, or IPv4 alone on a kernel without IPv6.
|
|
538
|
+
* `nish_tcp_listen_flags(host, port, backlog, flags)`: the same listener,
|
|
539
|
+
* with `SO_REUSEPORT` for flag 1 (any other bit is -22).
|
|
413
540
|
* `nish_tcp_accept(fd, peer)`: a new descriptor and the peer's address.
|
|
414
541
|
* `nish_net_read` / `nish_net_write(fd, buf, off, len)`: bytes `[off, off +
|
|
415
542
|
* len)` of `buf`, which the compiled call has range-checked; a write to a
|
|
@@ -443,6 +570,7 @@ bool nish_is_executable(const nish_str *path);
|
|
|
443
570
|
int32_t nish_net_address(nish_array *out, const nish_str *host, int32_t port);
|
|
444
571
|
int32_t nish_net_local_port(int32_t fd);
|
|
445
572
|
int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog);
|
|
573
|
+
int32_t nish_tcp_listen_flags(const nish_str *host, int32_t port, int32_t backlog, int32_t flags);
|
|
446
574
|
int32_t nish_tcp_accept(int32_t fd, nish_array *peer);
|
|
447
575
|
int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
|
|
448
576
|
int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
|
|
@@ -461,6 +589,31 @@ int32_t nish_poll_modify(int32_t loop, int32_t fd, int32_t events, int32_t token
|
|
|
461
589
|
int32_t nish_poll_remove(int32_t loop, int32_t fd);
|
|
462
590
|
int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms);
|
|
463
591
|
|
|
592
|
+
/* ---- The terminal, runtime/runtime-term.c, `nish:term` -------------------
|
|
593
|
+
*
|
|
594
|
+
* `nish_read_stdin(buf, off, len)`: at most `len` bytes of stdin into `buf`
|
|
595
|
+
* from `off`, without blocking: the count, 0 at the end of the stream, -11
|
|
596
|
+
* when nothing is waiting, or another `-errno`. `nish_wait_stdin(timeout_ms)`:
|
|
597
|
+
* whether stdin became readable before `timeout_ms` passed (negative waits
|
|
598
|
+
* for ever). `nish_is_terminal(fd)`: `isatty`. `nish_terminal_size()`: a fresh
|
|
599
|
+
* `i32[]` of `[columns, rows]`, or `[-errno, 0]`, -25 for "not a terminal".
|
|
600
|
+
* `nish_set_raw_mode(on)`: 0, -25 when stdin is not a terminal, or `-errno`;
|
|
601
|
+
* the saved termios comes back on `false`, at exit, on a panic and on every
|
|
602
|
+
* signal left at its default that ends the program, SIGKILL aside (the list is
|
|
603
|
+
* runtime-term.c's). -11 and -25 are those
|
|
604
|
+
* numbers on WASI too, where the three TTY calls answer "not a terminal".
|
|
605
|
+
* `nish_set_terminal_restore(bytes)`: keeps up to `NISH_TERM_RESTORE_MAX`
|
|
606
|
+
* bytes to write to stdout, once, at exit, on a panic and on each of those
|
|
607
|
+
* signals, before the termios comes back; "" keeps none. 0, -7 past the cap,
|
|
608
|
+
* -25 when stdout is not a terminal (always, for bytes, on WASI). */
|
|
609
|
+
#define NISH_TERM_RESTORE_MAX 4096
|
|
610
|
+
int32_t nish_read_stdin(nish_array *buf, int64_t off, int64_t len);
|
|
611
|
+
bool nish_wait_stdin(int32_t timeout_ms);
|
|
612
|
+
bool nish_is_terminal(int32_t fd);
|
|
613
|
+
nish_array *nish_terminal_size(void);
|
|
614
|
+
int32_t nish_set_raw_mode(bool on);
|
|
615
|
+
int32_t nish_set_terminal_restore(const nish_str *bytes);
|
|
616
|
+
|
|
464
617
|
/* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
|
|
465
618
|
* (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
|
|
466
619
|
* (the whole string, trimmed; blank is 0; `0x` hex accepted, as in JS),
|
|
@@ -480,12 +633,18 @@ void nish_panic_overflow(uint32_t op);
|
|
|
480
633
|
*
|
|
481
634
|
* One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
|
|
482
635
|
* once per chunk of a partition of `[0, len)`: contiguous chunks, at most one
|
|
483
|
-
* element apart in size, at most `
|
|
484
|
-
* `
|
|
485
|
-
*
|
|
636
|
+
* element apart in size, at most `NISH_PAR_PIECES` per thread over at most
|
|
637
|
+
* `nish_cpu_count()` threads and never more than `len / grain` rounded up, with
|
|
638
|
+
* chunk 0 running on the calling thread. Each thread takes the next chunk not
|
|
639
|
+
* yet started when it finishes one, so a thread that runs slower takes fewer.
|
|
640
|
+
* It returns when every chunk has run.
|
|
486
641
|
*
|
|
487
|
-
* Under `-DNISH_THREADS` the other chunks run on
|
|
488
|
-
*
|
|
642
|
+
* Under `-DNISH_THREADS` the other chunks run on the worker pool: threads
|
|
643
|
+
* started on the first region or join that divides, one per CPU but the
|
|
644
|
+
* caller's, which park between calls. Each has its own arena, rewound after
|
|
645
|
+
* every chunk or task it runs. The caller takes chunks off the pool's queue too
|
|
646
|
+
* while it waits, so a chunk the caller took runs in the caller's arena, as
|
|
647
|
+
* chunk 0 does. Without the macro -- and on
|
|
489
648
|
* WASI, which has no threads -- the whole range runs on the calling thread, so
|
|
490
649
|
* the entry point exists either way and the flag decides only whether the work
|
|
491
650
|
* is divided. A body that itself calls this runs its range on one thread: the
|
|
@@ -495,11 +654,31 @@ void nish_panic_overflow(uint32_t op);
|
|
|
495
654
|
* the language surface above it will:
|
|
496
655
|
* - chunks run concurrently, so a body may read what the caller owns and may
|
|
497
656
|
* write only through storage the caller lent it and no two chunks share;
|
|
498
|
-
* - a body
|
|
499
|
-
*
|
|
657
|
+
* - nothing a body leaves in the arena outlives its chunk, because a worker's
|
|
658
|
+
* arena is rewound after each chunk while chunk 0's is the caller's own: a
|
|
659
|
+
* string or an array of scalars a chunk answers is moved to a transfer block
|
|
660
|
+
* first (`nish_cross_keep`, below), and the compiler rewinds the caller's
|
|
661
|
+
* arena after its chunks as the pool rewinds a worker's. */
|
|
500
662
|
typedef void (*nish_par_body)(int64_t lo, int64_t hi, void *ctx);
|
|
663
|
+
/* The CPUs a region may divide over: `nish_cpu_online()` under `--threads`,
|
|
664
|
+
* read once, and 1 in a sequential build. */
|
|
501
665
|
int64_t nish_cpu_count(void);
|
|
666
|
+
/* `availableParallelism()` (WP34 N12), in every build: the fewest of the CPUs
|
|
667
|
+
* online, those the affinity mask allows, the cgroup v2 `cpu.max` quota
|
|
668
|
+
* rounded up, and `NISH_NUM_THREADS` when it is set to a positive number. At
|
|
669
|
+
* least 1; 1 under WASI. Read each call (runtime-os.c). */
|
|
670
|
+
int64_t nish_cpu_online(void);
|
|
502
671
|
void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t grain);
|
|
672
|
+
/* Keeps every later region and join on threads created and joined per call,
|
|
673
|
+
* as before the pool existed, so no parked worker outlives the code it would
|
|
674
|
+
* run: for a host that can unload the runtime. Call it before the first region
|
|
675
|
+
* or join; a pool already started stays. A Node addon calls it from the
|
|
676
|
+
* constructor below, because its shim includes <node_api.h> before this
|
|
677
|
+
* header, and Node can unload an addon while a pool's workers are parked. */
|
|
678
|
+
void nish_pool_disable(void);
|
|
679
|
+
#if defined(NAPI_VERSION) && defined(NISH_THREADS)
|
|
680
|
+
__attribute__((constructor)) static void nish_napi_unpooled(void) { nish_pool_disable(); }
|
|
681
|
+
#endif
|
|
503
682
|
|
|
504
683
|
/* ---- A scope's tasks (wp29 stage P2), runtime/runtime-parallel.c ----
|
|
505
684
|
*
|
|
@@ -507,11 +686,12 @@ void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t gra
|
|
|
507
686
|
* alive until it joins: it copies `size` bytes of `payload` and keeps them with
|
|
508
687
|
* `run` and `finish`, and runs nothing yet. `nish_scope_join` takes every task
|
|
509
688
|
* filed under `scope`, runs `run(payload)` for each -- the first on the calling
|
|
510
|
-
* thread and
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
* running short of threads costs speed and never an
|
|
689
|
+
* thread and the others on the worker pool, oldest first, with the calling
|
|
690
|
+
* thread taking any no worker is free for, in that order -- waits for all of
|
|
691
|
+
* them, and then calls `finish(payload)` for each on the calling thread, in
|
|
692
|
+
* the order they were filed. Without `-DNISH_THREADS` every task runs on the
|
|
693
|
+
* calling thread, so running short of threads costs speed and never an
|
|
694
|
+
* answer.
|
|
515
695
|
*
|
|
516
696
|
* The preconditions are the language's (docs/LANGUAGE.md, "Scoped tasks"):
|
|
517
697
|
* `run` writes nothing another task or the caller can see, its result is a
|
|
@@ -521,6 +701,96 @@ typedef void (*nish_task_fn)(void *payload);
|
|
|
521
701
|
void nish_scope_spawn(void *scope, nish_task_fn run, nish_task_fn finish, const void *payload, int64_t size);
|
|
522
702
|
void nish_scope_join(void *scope);
|
|
523
703
|
|
|
704
|
+
/* ---- A lock that owns its data (wp29 stage P3), runtime/runtime-parallel.c ----
|
|
705
|
+
*
|
|
706
|
+
* A `Mutex`'s lock is one `int32_t` word inside its guard: 0 free, 1 held. The
|
|
707
|
+
* compiler takes it inline, with an acquire compare-and-swap of 0 to 1, and
|
|
708
|
+
* gives it back inline, with a release store of 0 (src/emit-parallel.ts). Only
|
|
709
|
+
* a swap that finds the word held calls `nish_mutex_wait`, which returns once
|
|
710
|
+
* it has taken the lock itself: it spins briefly, then yields the processor
|
|
711
|
+
* between tries. The word lives in the arena with the rest of the `Mutex`, so
|
|
712
|
+
* there is nothing to create and nothing to destroy.
|
|
713
|
+
*
|
|
714
|
+
* The language holds one lock at a time and waits for nothing while it is
|
|
715
|
+
* held (docs/LANGUAGE.md, "A lock that owns its data"), so a holder always
|
|
716
|
+
* gives the word back and a waiter here always returns. Without real threads
|
|
717
|
+
* -- no `-DNISH_THREADS`, or WASI -- nothing else can hold the word, and the
|
|
718
|
+
* first try takes it. */
|
|
719
|
+
void nish_mutex_wait(int32_t *word);
|
|
720
|
+
|
|
721
|
+
/* ---- A channel of scalars or strings (wp29 stage P3), runtime/runtime-parallel.c ----
|
|
722
|
+
*
|
|
723
|
+
* A `Channel` object holds one `int64_t` word, `state`, and each function here
|
|
724
|
+
* takes its address: 0 before first use, then the address of a buffer, a lock
|
|
725
|
+
* and a count this file keeps outside every arena, then 1 once the receiver
|
|
726
|
+
* has drained it closed and it has been given back. A value is one 8-byte
|
|
727
|
+
* slot; the compiler widens a scalar into it and narrows it back, and a
|
|
728
|
+
* string travels as the block `nish_cross_send` copied it into, which the
|
|
729
|
+
* receiver's `nish_cross_in` frees. A string still in the buffer when the
|
|
730
|
+
* receiver stops early (a `break`) is given back with the buffer unread, so
|
|
731
|
+
* its block leaks.
|
|
732
|
+
*
|
|
733
|
+
* `nish_channel_send` appends and never waits: the channel is unbounded.
|
|
734
|
+
* `nish_channel_count` moves the count that keeps it open, which starts at 1:
|
|
735
|
+
* +1 at the spawn of each task that may send on it, -1 when that task returns,
|
|
736
|
+
* and -1 just before its scope joins. At zero the channel is closed.
|
|
737
|
+
* `nish_channel_receive` takes the next value into `*out` and answers 1,
|
|
738
|
+
* waiting while the channel is empty and open, or answers 0 once it is closed
|
|
739
|
+
* and empty, giving the buffer back the first time.
|
|
740
|
+
*
|
|
741
|
+
* The preconditions are the language's (docs/LANGUAGE.md, "Channels"): one
|
|
742
|
+
* receiver, every sender spawned before it, and no send once the scope's
|
|
743
|
+
* block has begun except by its tasks, so nothing touches a channel after its
|
|
744
|
+
* receiver has drained it. */
|
|
745
|
+
void nish_channel_send(int64_t *state, int64_t value);
|
|
746
|
+
void nish_channel_count(int64_t *state, int64_t delta);
|
|
747
|
+
int32_t nish_channel_receive(int64_t *state, int64_t *out);
|
|
748
|
+
|
|
749
|
+
/* ---- Copy at join: a string or a scalar array crosses threads ----
|
|
750
|
+
* runtime/runtime-cross.c
|
|
751
|
+
*
|
|
752
|
+
* A task's or a chunk's arena is rewound once its job is done, so a string or
|
|
753
|
+
* an array of scalars it answers or sends is copied out of it.
|
|
754
|
+
* `nish_cross_out(v, esize, mark)` runs on the thread that made `v`: when `v`
|
|
755
|
+
* was bumped in that thread's arena since `mark` (0: at any time) it answers a
|
|
756
|
+
* copy in a block of its own, outside every arena, with its low bit set; any
|
|
757
|
+
* other value, a literal or one an outer frame made, is answered as it is.
|
|
758
|
+
* `nish_cross_send(s)` is a channel send's: it copies every string `s`, so a
|
|
759
|
+
* send costs the string's length and not a walk of the sender's arena.
|
|
760
|
+
* `nish_cross_keep(v, esize, mark)` is a map element's: it moves `v` to the
|
|
761
|
+
* end of the chunk's transfer block and rewinds the arena to `mark`, the
|
|
762
|
+
* position before the body was called, so a chunk keeps one element's
|
|
763
|
+
* temporaries at a time. `nish_cross_begin(state, list)` pushes the chunk's
|
|
764
|
+
* transfer state, a `{ void *, void *, uint64_t, void * }` the caller holds,
|
|
765
|
+
* whose filled blocks go on `*list`, and `nish_cross_end(state)` pops it; with
|
|
766
|
+
* no list (a map's sequential path), or none pushed, keep copies `v` out and
|
|
767
|
+
* back in below `mark`. `nish_cross_adopt(list, dst, esize)` runs once every
|
|
768
|
+
* chunk is done, with `dst` the array the region wrote: 64 KB or more of
|
|
769
|
+
* blocks on a list become the newest chunks of the calling thread's arena,
|
|
770
|
+
* and less is copied into it, each element of `dst` in a block moved, and the
|
|
771
|
+
* blocks freed.
|
|
772
|
+
* `nish_cross_in(p, esize)` runs on the thread that takes the value: a tagged
|
|
773
|
+
* `p` of 64 KB or more that does not fit the arena's current chunk becomes a
|
|
774
|
+
* chunk of that arena; a smaller one is copied in and its block freed. Either
|
|
775
|
+
* way the block lives exactly as long as a copy would have. `esize` is 0 for a
|
|
776
|
+
* string and the element size of an array whose elements hold no pointer.
|
|
777
|
+
*
|
|
778
|
+
* `nish_guard_keep(old, s)` is a guard's string store (`g.value = s`): it
|
|
779
|
+
* answers a copy of `s` that the `Mutex` owns, outside every arena, and frees
|
|
780
|
+
* `old`, the string the slot held, when it is such a copy. `old` is null for a
|
|
781
|
+
* `string | null` slot that held none, and `s` is null for a `null` stored into
|
|
782
|
+
* one: the slot's copy is freed, nothing is kept, and the store answers null.
|
|
783
|
+
* A copy still held when its `Mutex` is dropped stays until the process ends:
|
|
784
|
+
* one block per string slot, not one per store. */
|
|
785
|
+
void *nish_cross_out(void *v, uint64_t esize, uint64_t mark);
|
|
786
|
+
void *nish_cross_send(void *s);
|
|
787
|
+
void *nish_cross_keep(void *v, uint64_t esize, uint64_t mark);
|
|
788
|
+
void *nish_cross_in(void *p, uint64_t esize);
|
|
789
|
+
void nish_cross_begin(void *state, void **list);
|
|
790
|
+
void nish_cross_end(void *state);
|
|
791
|
+
void nish_cross_adopt(void *list, void *dst, uint64_t esize);
|
|
792
|
+
void *nish_guard_keep(void *old, const void *s);
|
|
793
|
+
|
|
524
794
|
#ifdef __cplusplus
|
|
525
795
|
}
|
|
526
796
|
#endif
|
package/runtime/nish.mjs
CHANGED
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
*
|
|
53
53
|
* Everything here delegates to `runtime/shim.mjs`, the module the differential
|
|
54
54
|
* harness already uses, so the two cannot drift apart: a semantic fixed there
|
|
55
|
-
* is fixed here in the same commit.
|
|
55
|
+
* is fixed here in the same commit. The shim is itself two layers: the
|
|
56
|
+
* host-agnostic core (`runtime/core.mjs`) and the Node host
|
|
57
|
+
* (`runtime/node.mjs`) it installs, so this module adds only what is Node's to
|
|
58
|
+
* add — the globals, the module hooks and `process.argv`.
|
|
56
59
|
*/
|
|
57
60
|
import { registerHooks } from "node:module";
|
|
58
61
|
import * as shim from "./shim.mjs";
|
|
@@ -93,6 +96,13 @@ provide("readdirSync", shim.readdirSync);
|
|
|
93
96
|
provide("realpathSync", shim.realpathSync);
|
|
94
97
|
provide("spawnSync", shim.spawnSync);
|
|
95
98
|
provide("spawnSyncTo", shim.spawnSyncTo);
|
|
99
|
+
// WP34 N12. `spawn` and `waitPid` throw, naming themselves: a child is reaped
|
|
100
|
+
// by Node's event loop. `kill`, `availableParallelism` and `totalMemory` answer.
|
|
101
|
+
provide("spawn", shim.spawn);
|
|
102
|
+
provide("kill", shim.kill);
|
|
103
|
+
provide("waitPid", shim.waitPid);
|
|
104
|
+
provide("availableParallelism", shim.availableParallelism);
|
|
105
|
+
provide("totalMemory", shim.totalMemory);
|
|
96
106
|
provide("getenv", shim.getenv);
|
|
97
107
|
provide("statMtimeSync", shim.statMtimeSync);
|
|
98
108
|
provide("signalFd", shim.signalFd);
|
|
@@ -104,6 +114,7 @@ for (const name of [
|
|
|
104
114
|
"netAddress",
|
|
105
115
|
"netLocalPort",
|
|
106
116
|
"tcpListen",
|
|
117
|
+
"tcpListenFlags",
|
|
107
118
|
"tcpAccept",
|
|
108
119
|
"netRead",
|
|
109
120
|
"netWrite",
|
|
@@ -123,6 +134,17 @@ for (const name of [
|
|
|
123
134
|
provide(name, () => shim.noNetReading(name));
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
// `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
|
|
138
|
+
// Node's answers; reading stdin, waiting for it and raw mode throw, each naming
|
|
139
|
+
// itself, for the net functions' reason, and restore bytes throw for their own
|
|
140
|
+
// (core/host.mjs).
|
|
141
|
+
provide("isTerminal", shim.isTerminal);
|
|
142
|
+
provide("terminalSize", shim.terminalSize);
|
|
143
|
+
for (const name of ["readStdin", "waitStdin", "setRawMode"]) {
|
|
144
|
+
provide(name, () => shim.noTermReading(name));
|
|
145
|
+
}
|
|
146
|
+
provide("setTerminalRestore", shim.setTerminalRestore);
|
|
147
|
+
|
|
126
148
|
// `crypto.getRandomValues(bytes)` (WP34 N3). Node has the global, but it
|
|
127
149
|
// takes only a typed array and a `u8[]` is a plain `Array` here, so the one
|
|
128
150
|
// method is replaced on Node's own `crypto` object by one that fills a plain
|
|
@@ -171,6 +193,7 @@ provide("toU32", (x) => shim.convert(x, "f64", "u32"));
|
|
|
171
193
|
provide("toU64", (x) => shim.convert(x, "f64", "u64"));
|
|
172
194
|
provide("f64ToBits", shim.f64ToBits);
|
|
173
195
|
provide("bitsToF64", shim.bitsToF64);
|
|
196
|
+
provide("f32ToBits", shim.f32ToBits);
|
|
174
197
|
|
|
175
198
|
// Constant time (WP34 N6). Pure functions of their operands, so the answers
|
|
176
199
|
// agree with a native run; the timing does not, and is not claimed here.
|
|
@@ -225,6 +248,14 @@ provide("wrappingMul", (a, b) =>
|
|
|
225
248
|
provide("Ok", shim.Ok);
|
|
226
249
|
provide("Err", shim.Err);
|
|
227
250
|
|
|
251
|
+
// `String.fromBytes` is Nish's own static on `String`, so it is added to
|
|
252
|
+
// Node's `String` rather than installed as a global. Each byte is one UTF-16
|
|
253
|
+
// unit, as `String.fromCharCode` makes it: ASCII agrees with a native run,
|
|
254
|
+
// and a byte above 127 is the same divergence `fromCharCode`'s is.
|
|
255
|
+
if (!("fromBytes" in String)) {
|
|
256
|
+
String.fromBytes = shim.fromBytes;
|
|
257
|
+
}
|
|
258
|
+
|
|
228
259
|
// The string parsers, whose deviations from JavaScript are documented rules
|
|
229
260
|
// (base-10 `parseInt` that saturates, `parseFloat` that reads a `0x` prefix).
|
|
230
261
|
globalThis.parseInt = shim.parseInt;
|
|
@@ -258,11 +289,13 @@ provide("arena", shim.arena);
|
|
|
258
289
|
// `nish:secret` is answered here as well, because the standard library imports
|
|
259
290
|
// it (`std/crypto/p256.ts` and its neighbours): its names are
|
|
260
291
|
// `runtime/shim.mjs`'s, where a `Secret` is a plain wrapper and `wipe`
|
|
261
|
-
// zero-fills it. `nish:unsafe`
|
|
262
|
-
// globals installed above: its functions are globals of this module either
|
|
292
|
+
// zero-fills it. `nish:unsafe` and `nish:term` are each answered by a module
|
|
293
|
+
// that hands back the globals installed above: its functions are globals of this module either
|
|
263
294
|
// way, and the import is what the compiler requires a program to write.
|
|
264
295
|
const UNSAFE_EXPORTS = ["uncheckedGet", "uncheckedSet", "wrappingAdd", "wrappingSub", "wrappingMul"];
|
|
265
296
|
const unsafeSource = UNSAFE_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
297
|
+
const TERM_EXPORTS = ["readStdin", "waitStdin", "isTerminal", "terminalSize", "setRawMode", "setTerminalRestore"];
|
|
298
|
+
const termSource = TERM_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
266
299
|
registerHooks({
|
|
267
300
|
resolve: (specifier, context, next) => {
|
|
268
301
|
if (specifier === "nish:secret") {
|
|
@@ -271,6 +304,9 @@ registerHooks({
|
|
|
271
304
|
if (specifier === "nish:unsafe") {
|
|
272
305
|
return { url: `data:text/javascript,${encodeURIComponent(unsafeSource)}`, shortCircuit: true };
|
|
273
306
|
}
|
|
307
|
+
if (specifier === "nish:term") {
|
|
308
|
+
return { url: `data:text/javascript,${encodeURIComponent(termSource)}`, shortCircuit: true };
|
|
309
|
+
}
|
|
274
310
|
return specifier.startsWith("nish/")
|
|
275
311
|
? next(new URL(`../std/${specifier.slice("nish/".length)}.ts`, import.meta.url).href, context)
|
|
276
312
|
: next(specifier, context);
|