@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.
Files changed (104) hide show
  1. package/INSTALL.md +58 -28
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +3 -2
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +99 -5
  22. package/runtime/nish.h +289 -19
  23. package/runtime/nish.mjs +39 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-aes.c +490 -0
  26. package/runtime/runtime-bytes.c +401 -0
  27. package/runtime/runtime-cross.c +366 -0
  28. package/runtime/runtime-host.c +8 -7
  29. package/runtime/runtime-net.c +93 -34
  30. package/runtime/runtime-os.c +214 -16
  31. package/runtime/runtime-parallel.c +590 -107
  32. package/runtime/runtime-simd.c +235 -11
  33. package/runtime/runtime-term.c +504 -0
  34. package/runtime/runtime-wasm.c +5 -14
  35. package/runtime/runtime.c +257 -17
  36. package/runtime/shim.mjs +6 -915
  37. package/runtime/sqlite/LICENSE.md +85 -0
  38. package/runtime/sqlite/README.md +45 -0
  39. package/runtime/sqlite/nish-sqlite.c +359 -0
  40. package/runtime/sqlite/sqlite3.c +271131 -0
  41. package/runtime/sqlite/sqlite3.h +14447 -0
  42. package/scripts/build.sh +437 -28
  43. package/std/LICENSE-fast-float +23 -0
  44. package/std/LICENSE-fdlibm +8 -0
  45. package/std/README.md +108 -58
  46. package/std/base64.ts +116 -0
  47. package/std/bytes.ts +419 -0
  48. package/std/cli.ts +328 -0
  49. package/std/collections.ts +6 -5
  50. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  51. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  52. package/std/crypto/aes.ts +238 -76
  53. package/std/crypto/base64url.ts +125 -0
  54. package/std/crypto/bcrypt.ts +760 -0
  55. package/std/crypto/chacha20poly1305.ts +43 -9
  56. package/std/crypto/ed25519.ts +840 -0
  57. package/std/crypto/hkdf.ts +304 -12
  58. package/std/crypto/hmac.ts +382 -54
  59. package/std/crypto/p256.ts +932 -61
  60. package/std/crypto/x25519.ts +55 -37
  61. package/std/crypto/x509-random.ts +71 -0
  62. package/std/crypto/x509.ts +20 -131
  63. package/std/json-tokenizer.ts +4237 -0
  64. package/std/json-typed.ts +1014 -0
  65. package/std/json-value.ts +1182 -0
  66. package/std/json.ts +1202 -34
  67. package/std/math.ts +608 -0
  68. package/std/net/buffer-pool.ts +151 -0
  69. package/std/net/hpack.ts +416 -112
  70. package/std/net/http-fields.ts +93 -0
  71. package/std/net/http1-server.ts +501 -62
  72. package/std/net/http1.ts +31 -6
  73. package/std/net/http2-tls.ts +112 -24
  74. package/std/net/http2.ts +333 -75
  75. package/std/net/http3-server.ts +261 -43
  76. package/std/net/http3.ts +187 -28
  77. package/std/net/postgres.ts +1875 -0
  78. package/std/net/prefork.ts +412 -0
  79. package/std/net/quic-listener.ts +108 -11
  80. package/std/net/quic-packet.ts +243 -82
  81. package/std/net/quic-recovery.ts +361 -30
  82. package/std/net/quic-stream.ts +424 -24
  83. package/std/net/quic.ts +519 -167
  84. package/std/net/router.ts +676 -0
  85. package/std/net/tls/codec.ts +426 -27
  86. package/std/net/tls/record-server.ts +478 -132
  87. package/std/net/tls/record.ts +133 -42
  88. package/std/net/tls/schedule.ts +464 -55
  89. package/std/net/tls/ticket.ts +610 -0
  90. package/std/net/tls-tcp.ts +196 -53
  91. package/std/net/tls.ts +1018 -260
  92. package/std/net/url.ts +571 -0
  93. package/std/random.ts +94 -0
  94. package/std/regex.ts +4274 -0
  95. package/std/sqlite.ts +391 -0
  96. package/std/term-io.ts +978 -0
  97. package/std/term-pixels.ts +874 -0
  98. package/std/term-probe.ts +459 -0
  99. package/std/term-rate.ts +993 -0
  100. package/std/term.ts +2453 -0
  101. package/std/text.ts +90 -7
  102. package/std/threads.ts +438 -14
  103. package/std/yaml-lines.ts +141 -0
  104. 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 and runtime-simd.c (the byte searches that read a vector at a
12
- * time) split off the same way since. They are separate so that each carries
13
- * its own measured code-size ceiling (docs/wp7-runtime.md, "Runtime additions
14
- * and budget"); a host links all of them, which `scripts/build.sh` does for
15
- * you when handed runtime.c. `tests/run.js` checks that every runtime function
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 exit(1) on a fatal error.
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 `nish_cpu_count()` of them and never more than
484
- * `len / grain`, with chunk 0 running on the calling thread. It returns when
485
- * every chunk has run.
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 their own threads, each with
488
- * its own arena, which it frees before exiting. Without the macro -- and on
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's result may not point into the arena, because a worker's arena is
499
- * freed when its thread exits while chunk 0's is the caller's own. */
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 each other on a thread of its own, which frees its arena before it
511
- * exits -- waits for all of them, and then calls `finish(payload)` for each on
512
- * the calling thread, in the order they were filed. Without `-DNISH_THREADS`,
513
- * or when a thread cannot be created, a task runs on the calling thread, so
514
- * running short of threads costs speed and never an answer.
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` is answered by a module that hands back the
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);