@amritk/nish-x86_64-linux 0.18.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.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. package/std/threads.ts +438 -14
package/INSTALL.md CHANGED
@@ -109,7 +109,7 @@ Through npm, which is also how an Nish *program* resolves its dependencies
109
109
  the compiler pinned per project in a `package.json`:
110
110
 
111
111
  ```bash
112
- npm install -g @amritk/nish
112
+ npm install -g nish
113
113
  nish --version
114
114
  ```
115
115
 
@@ -171,13 +171,12 @@ never fails over it.
171
171
  > underlying defect is [wp19 §5a](wp19-stage0-retirement.md#5a-what-r6-is-waiting-on)
172
172
  > item 4.
173
173
 
174
- **Do not install `nish`.** That name on the public registry has belonged to
175
- `stdarg`'s "A Node.js Interactive shell" since February 2014 — versions 0.0.0
176
- and 0.0.1, both deprecated by their author, nothing published since. **This
177
- compiler is `@amritk/nish`** (decided 2026-09-19,
178
- [docs/wp12-release.md](wp12-release.md#the-npm-name)), and the command it
179
- installs is still `nish` — the package name and the command are two different
180
- strings, and that is the one thing the scope costs.
174
+ **The package is `nish` from 0.20.0.** Releases up to 0.18.0 were published
175
+ as `@amritk/nish` (0.19.0's publish to npm failed), which is deprecated and
176
+ points here; `npm install -g nish`
177
+ replaces it ([docs/wp12-release.md](wp12-release.md#the-npm-name)). Versions
178
+ 0.0.0 and 0.0.1 of `nish` are an unrelated 2014 project that held the name
179
+ before it, so pin `nish@^0.20.0` or later rather than an older range.
181
180
 
182
181
  **On a platform this project attaches no binary for, `npm install` gets you a
183
182
  command that refuses.** The table below is the whole list, so musl, FreeBSD and
@@ -224,7 +223,8 @@ routes are
224
223
  build/nish app.ts -o app.ll
225
224
  # on the musl host, with its own clang and the runtime from this repository
226
225
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
227
- runtime/runtime-host.c runtime/runtime-net.c -lm -o app
226
+ runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
227
+ runtime/runtime-simd.c runtime/runtime-aes.c -lm -o app
228
228
  ```
229
229
 
230
230
  `--link`ing inside the container is the mistake to avoid: it shells out to the
@@ -240,18 +240,20 @@ routes are
240
240
  If you need one of these to be a first-class install, that is the conversation
241
241
  to have on the issue tracker rather than a workaround to discover here.
242
242
 
243
- **On the registry from 0.10.0.** Every release publishes the main package and
244
- its platform packages to npm from `release.yml`, by trusted publishing
243
+ **On the registry from 0.10.0** — as `@amritk/nish` through 0.18.0, and as
244
+ `nish` from 0.20.0; 0.19.0 is on GitHub only, because its npm publish
245
+ failed. Every release publishes the main package and its platform packages to
246
+ npm from `release.yml`, by trusted publishing
245
247
  ([docs/wp12-release.md](wp12-release.md#release-procedure) step 4), so
246
- `npm install -g @amritk/nish` is the whole install. The rest of this section is
248
+ `npm install -g nish` is the whole install. The rest of this section is
247
249
  for a machine that cannot reach the registry, or a release before 0.10.0.
248
250
 
249
251
  From a release tarball on GitHub — the same package `npm publish` uploads,
250
252
  carried by the release instead of the registry. The `Release` workflow attaches
251
253
  the npm tarball to the release it builds for a `v*` tag. `npm pack` names it
252
- after `package.json#name`, so it is `amritk-nish-<version>.tgz` from 0.4.0 on
253
- and `nish-<version>.tgz` for the releases cut before the scope was taken — the
254
- example below is one of those:
254
+ after `package.json#name`, so it is `nish-<version>.tgz` before 0.4.0 and
255
+ from 0.20.0 on, and `amritk-nish-<version>.tgz` from 0.4.0 to 0.19.0, while the
256
+ package was scoped — the example below is one of the early ones:
255
257
 
256
258
  ```bash
257
259
  curl -LO https://github.com/amritk/nish/releases/download/v0.2.0/nish-0.2.0.tgz
@@ -358,7 +360,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
358
360
  skips the download. §2a has what the build does with it.
359
361
 
360
362
  The published package ships `bin/` (the `nish` command, which is a launcher),
361
- `runtime/` (the C runtime — five translation units and their header),
363
+ `runtime/` (the C runtime — eight translation units, the wasm subset and their header),
362
364
  `scripts/build.sh` (the link pipeline) and `std/` (the standard library). The
363
365
  compiler itself is not in it: it arrives as one prebuilt
364
366
  `@amritk/nish-<asset>` package per platform, and `bin/nish` hands over to
@@ -368,11 +370,12 @@ working directory — and the binary runs from inside its own platform package
368
370
  for exactly that reason.
369
371
 
370
372
  Building the IR yourself rather than through `--link` means naming the runtime
371
- on the `clang` line, and it is five files:
373
+ on the `clang` line, and it is eight files:
372
374
 
373
375
  ```bash
374
376
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
375
- runtime/runtime-host.c runtime/runtime-net.c -lm -o app
377
+ runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
378
+ runtime/runtime-simd.c runtime/runtime-aes.c -lm -o app
376
379
  ```
377
380
 
378
381
  `runtime.c` is the part every program touches — the arena, strings, arrays,
@@ -380,13 +383,16 @@ number formatting, the panics — and `runtime-os.c` wraps the system calls:
380
383
  files, directories, subprocesses, `getenv`, the monotonic clock.
381
384
  `runtime-parallel.c` divides work across threads for `nish/threads` (a
382
385
  program built with `--threads`, as every importer of `nish/threads` is, also
383
- needs `-DNISH_THREADS=1 -pthread` on the line), `runtime-host.c` holds the
384
- wall clock, entropy, file times and signals, and `runtime-net.c` the sockets
385
- of `nish:net`. They are separate so that each
386
- carries its own measured size ceiling ([docs/wp7-runtime.md](wp7-runtime.md));
387
- nothing in the core calls into the others, so a line that names only
388
- `runtime.c` and `runtime-os.c` still links a program that uses none of the
389
- other three. `scripts/build.sh` compiles the other four beside any
386
+ needs `-DNISH_THREADS=1 -pthread` on the line), `runtime-cross.c` copies
387
+ the strings and arrays a task answers, a channel sends or a `Mutex` keeps out
388
+ of the arena they were made in, `runtime-host.c` holds the wall clock,
389
+ entropy, file times and signals, `runtime-net.c` the sockets of `nish:net`,
390
+ and `runtime-simd.c` and `runtime-aes.c` the vector text search and AES-GCM.
391
+ They are separate so that each carries its own measured size ceiling
392
+ ([docs/wp7-runtime.md](wp7-runtime.md)); nothing in the core calls into the
393
+ others, so a line that names only `runtime.c` and `runtime-os.c` still links a
394
+ program that uses none of the other six. `scripts/build.sh` compiles the other
395
+ seven beside any
390
396
  `runtime.c` it is handed, so a build that goes through it — every `--link`,
391
397
  and every `--profile` recipe in these documents — needs to name only the one.
392
398
 
@@ -479,6 +485,18 @@ refuses the run without one rather than keep a binary in a shared directory.
479
485
  The first run that builds into the cache makes its root `0700`, so no other
480
486
  user can plant a binary in it ([docs/security/cli.md](security/cli.md)).
481
487
 
488
+ Every link with the debug, speed or size profile, `nish run`'s and `--link`'s
489
+ alike, keeps the compiled C runtime in `$XDG_CACHE_HOME/nish/runtime` (or
490
+ `~/.cache/nish/runtime`), one entry per profile, `-g`, `--threads`, `CC` and
491
+ the version that compiler reports, made `0700` the same way. The first link
492
+ compiles the runtime's six files at once into it, and every later link
493
+ compiles only the program's modules, also at once, and links against the
494
+ entry: a `hello.ts` debug link goes from about half a second to about a
495
+ tenth. `--link` with no absolute `HOME` or `XDG_CACHE_HOME`, or with a root
496
+ it cannot make private, compiles the runtime itself as it always did; the
497
+ wasi profile and `scripts/build.sh` called directly never use the cache.
498
+ Removing the directory is always safe.
499
+
482
500
  ## 4. Exit codes
483
501
 
484
502
  | Code | Meaning |
package/bin/nish CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.18.0",
4
- "description": "The @amritk/nish native compiler for Linux on x86_64",
3
+ "version": "0.20.0",
4
+ "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
7
7
  "linux"
package/runtime/nish.d.ts CHANGED
@@ -112,10 +112,10 @@ declare function Err<T, E>(error: E): Result<T, E>;
112
112
 
113
113
  /**
114
114
  * The disposable protocol `using` reads (WP29 P2, docs/wp29-thread-surface.md
115
- * §5): declared here so that a program using `nish/threads`'s scope or
116
- * `arena()` needs no `"ESNext.Disposable"` in its `lib`. `nish` itself takes
117
- * `using` only for a `scope()` or an `arena()`, and `[Symbol.dispose]` only in
118
- * `nish/threads`.
115
+ * §5): declared here so that a program using `nish/threads`'s scope, a
116
+ * `Mutex`'s lock or `arena()` needs no `"ESNext.Disposable"` in its `lib`.
117
+ * `nish` itself takes `using` only for a `scope()`, a `lock()` or an
118
+ * `arena()`, and `[Symbol.dispose]` only in `nish/threads`.
119
119
  */
120
120
  interface SymbolConstructor {
121
121
  readonly dispose: unique symbol;
@@ -124,6 +124,16 @@ interface Disposable {
124
124
  [Symbol.dispose](): void;
125
125
  }
126
126
 
127
+ /**
128
+ * `String.fromBytes` is Nish's, not JavaScript's, so it merges into the
129
+ * `StringConstructor` `lib.es2022` declares. Under `runtime/nish.mjs` each byte
130
+ * becomes one UTF-16 unit, as `String.fromCharCode`'s does.
131
+ */
132
+ interface StringConstructor {
133
+ /** The bytes of `bytes[start..end)` as one string; panics unless `0 <= start <= end <= bytes.length`. */
134
+ fromBytes(bytes: readonly u8[], start: number, end: number): string;
135
+ }
136
+
127
137
  interface Console {
128
138
  /** `x` and a newline to stdout. Statement position; exactly one argument. */
129
139
  log(x: string | number | boolean): void;
@@ -193,7 +203,7 @@ declare function isExecutableSync(path: string): boolean;
193
203
  // ---- Unsafe (`nish:unsafe`) --------------------------------------------------
194
204
  //
195
205
  // Declared as globals so `tsc` reads a program that imports them, as every
196
- // `nish:` export is, but `nish` refuses a call to any of the five that is not
206
+ // `nish:` export is, but `nish` refuses a call to any of the six that is not
197
207
  // imported from `nish:unsafe`: the import is the module's visible opt-in.
198
208
 
199
209
  /** `xs[i]` with no bounds check: undefined behaviour when `i` is out of range. */
@@ -206,6 +216,8 @@ declare function wrappingAdd<T extends i32 | i64>(a: T, b: T): T;
206
216
  declare function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
207
217
  /** `a * b` wrapped to the operands' width. */
208
218
  declare function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
219
+ /** The `NUL`-terminated bytes at `p`, copied into a string: undefined behaviour unless `p` is a live C string. */
220
+ declare function readCString(p: CPtr): string;
209
221
 
210
222
  // ---- Streams and files (globals: Nish has no package resolution) ---------
211
223
 
@@ -266,6 +278,28 @@ declare function spawnSync(argv: string[]): number;
266
278
  * either path is refused, as `writeFileSync` refuses one, and the answer is -1.
267
279
  */
268
280
  declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
281
+ /**
282
+ * Start `argv[0]` through `PATH` without waiting: the child's pid, or a
283
+ * negative errno. The child is the program's until `waitPid` reaps it. No
284
+ * reading under Node, which throws.
285
+ */
286
+ declare function spawn(argv: string[]): i32;
287
+ /**
288
+ * Send `sig` to `pid`: 0, or a negative errno (-3 when no such process). `sig`
289
+ * is 0, 2, 9 or 15, and anything else, like a `pid` of 0 or below, is -22.
290
+ */
291
+ declare function kill(pid: i32, sig: i32): i32;
292
+ /**
293
+ * Wait for the child `pid`, or any child for -1: `[pid, status]`, the status
294
+ * as `spawnSync` answers it. Flag 1 is WNOHANG, under which `[0, 0]` means a
295
+ * child remains and none has exited; a failure is `[-errno, 0]`, `[-10, 0]`
296
+ * with no child to wait for. No reading under Node, which throws.
297
+ */
298
+ declare function waitPid(pid: i32, flags: i32): i32[];
299
+ /** How many CPUs the program may use: at least 1, with or without `--threads`. */
300
+ declare function availableParallelism(): i32;
301
+ /** Bytes of physical memory, or -1 when the host does not say. */
302
+ declare function totalMemory(): i64;
269
303
  /** One environment variable, or `null` when it is unset (an empty value is a set variable). */
270
304
  declare function getenv(name: string): string | null;
271
305
  /**
@@ -305,6 +339,8 @@ declare function netAddress(out: u8[], host: string, port: i32): i32;
305
339
  declare function netLocalPort(fd: i32): i32;
306
340
  /** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
307
341
  declare function tcpListen(host: string, port: i32, backlog: i32): i32;
342
+ /** `tcpListen`'s socket, with `SO_REUSEPORT` for flag 1 and `TCP_NODELAY` on each accepted socket for flag 2; any other bit is -22. */
343
+ declare function tcpListenFlags(host: string, port: i32, backlog: i32, flags: i32): i32;
308
344
  /** The next connection's descriptor, its address in `peer`; -11 when none waits. */
309
345
  declare function tcpAccept(fd: i32, peer: u8[]): i32;
310
346
  /** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
@@ -400,6 +436,16 @@ declare module "nish:process" {
400
436
  * at either path is refused, and the answer is -1.
401
437
  */
402
438
  export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
439
+ /** Start a child without waiting: its pid, or a negative errno. */
440
+ export function spawn(argv: string[]): i32;
441
+ /** Send 0, 2, 9 or 15 to `pid`: 0, or a negative errno; any other signal is -22. */
442
+ export function kill(pid: i32, sig: i32): i32;
443
+ /** `[pid, status]` for a child that exited, `[0, 0]` under WNOHANG (flag 1) with none, or `[-errno, 0]`. */
444
+ export function waitPid(pid: i32, flags: i32): i32[];
445
+ /** How many CPUs the program may use, at least 1. */
446
+ export function availableParallelism(): i32;
447
+ /** Bytes of physical memory, or -1. */
448
+ export function totalMemory(): i64;
403
449
  /**
404
450
  * A monotonic clock in nanoseconds. The origin is arbitrary, so only the
405
451
  * difference between two reads is meaningful.
@@ -424,6 +470,8 @@ declare module "nish:net" {
424
470
  export function netLocalPort(fd: i32): i32;
425
471
  /** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
426
472
  export function tcpListen(host: string, port: i32, backlog: i32): i32;
473
+ /** `tcpListen`'s socket, with `SO_REUSEPORT` for flag 1 and `TCP_NODELAY` on each accepted socket for flag 2; any other bit is -22. */
474
+ export function tcpListenFlags(host: string, port: i32, backlog: i32, flags: i32): i32;
427
475
  /** The next connection's descriptor, its address in `peer`; -11 when none waits. */
428
476
  export function tcpAccept(fd: i32, peer: u8[]): i32;
429
477
  /** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
@@ -474,6 +522,8 @@ declare module "nish:unsafe" {
474
522
  export function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
475
523
  /** `a * b` wrapped to the operands' width. */
476
524
  export function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
525
+ /** The `NUL`-terminated bytes at `p`, copied into a string: undefined behaviour unless `p` is a live C string. */
526
+ export function readCString(p: CPtr): string;
477
527
  }
478
528
 
479
529
  declare module "nish:io" {
package/runtime/nish.h CHANGED
@@ -8,11 +8,14 @@
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) and runtime-aes.c (AES-GCM on the CPU's AES instructions) split off
13
+ * the same way since. They are separate so that each carries its own measured
14
+ * code-size ceiling (docs/wp7-runtime.md, "Runtime additions and budget"); a
15
+ * host links all of them, which `scripts/build.sh` does for you when handed
16
+ * runtime.c, runtime-aes.c included: the compiler calls its kernels from
17
+ * std/crypto/aes.ts, so `scripts/build.sh` pairs it and `src/run-cache.ts`
18
+ * caches it like the others. `tests/run.js` checks that every runtime function
16
19
  * declared in runtime.ts has a prototype in this file.
17
20
  *
18
21
  * Headers generated by `nish --emit-header` include this one, so keep
@@ -155,6 +158,18 @@ int64_t nish_str_index_of_any(const nish_str *s, const nish_str *set, int64_t fr
155
158
  void nish_print(const nish_str *s);
156
159
  /* `console.error` and the newline-free `write` / `writeError`: fd 1 or 2. */
157
160
  void nish_write(const nish_str *s, int32_t fd, bool newline);
161
+ /* `panic(message)`, `expect` and a failed range entry: the message and a
162
+ newline on stderr, then `exit(1)`. Only the first panic of a process prints;
163
+ a thread that panics after it parks until that exit (`nish_panic_claim`). */
164
+ __attribute__((noreturn)) void nish_panic(const nish_str *s);
165
+ /* Called by every panic path before it prints: answers for the first caller
166
+ and parks every later one forever. Without threads no second caller can
167
+ exist, so it is empty there. */
168
+ #ifdef NISH_THREADS
169
+ void nish_panic_claim(void);
170
+ #else
171
+ #define nish_panic_claim() ((void)0)
172
+ #endif
158
173
  /* Number to string, as `${n}` does: decimal for integers, shortest round-trip (JS Number#toString) for f64. */
159
174
  nish_str *nish_str_from_i32(int32_t v);
160
175
  nish_str *nish_str_from_f64(double v);
@@ -164,7 +179,8 @@ nish_str *nish_str_from_i64(int64_t v);
164
179
  nish_str *nish_str_from_u64(uint64_t v);
165
180
 
166
181
  /* 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.
182
+ * print a message to stderr and `_exit(1)` on a fatal error, the first panic
183
+ * of a process only (`nish_panic_claim`).
168
184
  *
169
185
  * Every function here that takes a path, a name or an argument vector refuses
170
186
  * one holding a NUL, answering as it answers a path that does not exist: the
@@ -239,6 +255,45 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
239
255
  * elements themselves — which is why the checker refuses to let a program hold
240
256
  * a pointer to one across a `push` (WP15 section 2a). */
241
257
  void nish_array_grow(nish_array *a, uint64_t elem_size);
258
+ /* ---- AES-GCM on the CPU's AES instructions (WP38 S5) ----------------------
259
+ * runtime/runtime-aes.c: AES-NI and PCLMULQDQ on x86-64, the ARMv8 AES and
260
+ * PMULL instructions on AArch64. The compiler calls each kernel in place of one
261
+ * private function of std/crypto/aes.ts, behind `nish_aes_hw`, and calls the
262
+ * bitsliced Nish function itself when that answers 0. The caller has checked
263
+ * every precondition: a kernel checks no bound, and reads and writes only
264
+ * what is listed. Every kernel is constant time in its key, its data and H.
265
+ *
266
+ * `rk` is a `u64[]` of `8 * (rounds + 1)` words, the length the bitsliced
267
+ * schedule has. On this path it holds, in bytes: the FIPS 197 schedule in
268
+ * [0, 16 * (rounds + 1)), round key r at 16 * r; H = E(K, 0^128), H^2, H^3 and
269
+ * H^4 at [240, 304), each as two u64s, low then high, of the block read
270
+ * big-endian; at [304, 368) each power's halves XORed together, twice over;
271
+ * and zero everywhere else. A process makes and uses every key under one
272
+ * answer of `nish_aes_hw`, so one array never meets both layouts. */
273
+ /* 1 when this process runs the kernels, 0 when it runs the bitsliced Nish
274
+ code. Resolved on the first call and fixed from then on: `NISH_AES=soft`
275
+ pins 0; unset, `hw` or any other value is the best this machine has, read
276
+ on that call from CPUID leaf 1 (AES, PCLMULQDQ, SSSE3) or AArch64's
277
+ AT_HWCAP (AES, PMULL). Always 0 on other targets, wasm32 among them. */
278
+ int32_t nish_aes_hw(void);
279
+ /* `key` (u8[]) is 16 or 32 bytes and `rounds` 10 or 14 to match. Writes the
280
+ kernel layout above into all of `rk`. */
281
+ void nish_aes_schedule(const nish_array *key, int32_t rounds, nish_array *rk);
282
+ /* `src` and `dst` (u8[]) each hold at least 16 bytes: dst[0..16) = E(K, src[0..16)). */
283
+ void nish_aes_block(const nish_array *rk, int32_t rounds, const nish_array *src, nish_array *dst);
284
+ /* GCTR (SP 800-38D §6.5) from inc32(J0): dst[i] = src[i] ^ keystream[i] for
285
+ i < length, the counter J0's last four bytes read big-endian and wrapping
286
+ mod 2^32. `j0` (u8[]) is 16 bytes, 0 <= length <= min(src.len, dst.len),
287
+ and `dst` may be `src`. */
288
+ void nish_aes_ctr(const nish_array *rk, int32_t rounds, const nish_array *j0, const nish_array *src,
289
+ nish_array *dst, int32_t length);
290
+ /* Absorbs data[0..length) (u8[]) into the GHASH state `y` (u64[2], the high
291
+ word first, each read big-endian), zero-padding the last block. `hHi:hLo` is
292
+ H, which the bitsliced walker reads; the kernel reads H and its powers from
293
+ `rk` instead, and they are the same H. 0 <= length <= data.len. */
294
+ void nish_aes_ghash(nish_array *y, uint64_t hHi, uint64_t hLo, const nish_array *rk, const nish_array *data,
295
+ int32_t length);
296
+
242
297
  /* `secureZero(bytes)` (#385): sets every byte of `bytes` to zero through
243
298
  * `volatile` stores, out of line, so that neither `-O2` nor `-flto` can drop
244
299
  * them as dead when the buffer's life ends straight after the call. For
@@ -313,6 +368,28 @@ int32_t nish_spawn(const nish_array *argv);
313
368
  * implementation in runtime-os.c backs both spawn builtins, which is what keeps
314
369
  * the argument vector, the wait and the signal convention written once. */
315
370
  int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_str *err);
371
+ /* `spawn(argv)` (WP34 N12): start element 0 of `argv` as `nish_spawn` does and
372
+ * answer the child's pid at once, without waiting, or a negative errno:
373
+ * -EINVAL for an empty `argv` or an argument holding a NUL, `posix_spawnp`'s
374
+ * own error otherwise, and -ENOSYS under WASI. The child is reaped by
375
+ * `nish_wait_pid`. */
376
+ int32_t nish_spawn_child(const nish_array *argv);
377
+ /* `kill(pid, sig)`: send `sig` to `pid` and answer 0, or a negative errno. Only
378
+ * 0, 2 (SIGINT), 9 (SIGKILL) and 15 (SIGTERM) are sent; any other signal, and
379
+ * a pid of 0 or below, is -EINVAL. -ESRCH when no process has the pid, -EPERM
380
+ * when it is another user's. Any process of this uid may be signalled, not
381
+ * only a child. */
382
+ int32_t nish_kill(int32_t pid, int32_t sig);
383
+ /* `waitPid(pid, flags)`: a fresh `i32[]` of two, `[pid, status]`. `pid` is a
384
+ * child's or -1 for any; flag 1 is WNOHANG and any other bit is -EINVAL, as is
385
+ * a pid that is neither -1 nor positive. The status is `nish_spawn`'s: the exit
386
+ * code, or `128 + n` for signal `n`. `[0, 0]` is a WNOHANG wait with a child
387
+ * left and none exited; a failure is `[-errno, 0]` (-ECHILD: no child; -EINTR:
388
+ * a signal interrupted the wait). In the arena. */
389
+ nish_array *nish_wait_pid(int32_t pid, int32_t flags);
390
+ /* `totalMemory()`: bytes of physical memory (Linux: `_SC_PHYS_PAGES` times the
391
+ * page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
392
+ int64_t nish_total_memory(void);
316
393
 
317
394
  /* ---- The environment (WP19 R1) ------------------------------------------
318
395
  * `getenv(name)`: the value of environment variable `name`, copied into the
@@ -410,6 +487,8 @@ bool nish_is_executable(const nish_str *path);
410
487
  * `nish_net_local_port(fd)`: the port a socket is bound to.
411
488
  * `nish_tcp_listen(host, port, backlog)`: `SO_REUSEADDR`, and `::` hears
412
489
  * both families, or IPv4 alone on a kernel without IPv6.
490
+ * `nish_tcp_listen_flags(host, port, backlog, flags)`: the same listener,
491
+ * with `SO_REUSEPORT` for flag 1 (any other bit is -22).
413
492
  * `nish_tcp_accept(fd, peer)`: a new descriptor and the peer's address.
414
493
  * `nish_net_read` / `nish_net_write(fd, buf, off, len)`: bytes `[off, off +
415
494
  * len)` of `buf`, which the compiled call has range-checked; a write to a
@@ -443,6 +522,7 @@ bool nish_is_executable(const nish_str *path);
443
522
  int32_t nish_net_address(nish_array *out, const nish_str *host, int32_t port);
444
523
  int32_t nish_net_local_port(int32_t fd);
445
524
  int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog);
525
+ int32_t nish_tcp_listen_flags(const nish_str *host, int32_t port, int32_t backlog, int32_t flags);
446
526
  int32_t nish_tcp_accept(int32_t fd, nish_array *peer);
447
527
  int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
448
528
  int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
@@ -480,12 +560,18 @@ void nish_panic_overflow(uint32_t op);
480
560
  *
481
561
  * One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
482
562
  * 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.
563
+ * element apart in size, at most `NISH_PAR_PIECES` per thread over at most
564
+ * `nish_cpu_count()` threads and never more than `len / grain` rounded up, with
565
+ * chunk 0 running on the calling thread. Each thread takes the next chunk not
566
+ * yet started when it finishes one, so a thread that runs slower takes fewer.
567
+ * It returns when every chunk has run.
486
568
  *
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
569
+ * Under `-DNISH_THREADS` the other chunks run on the worker pool: threads
570
+ * started on the first region or join that divides, one per CPU but the
571
+ * caller's, which park between calls. Each has its own arena, rewound after
572
+ * every chunk or task it runs. The caller takes chunks off the pool's queue too
573
+ * while it waits, so a chunk the caller took runs in the caller's arena, as
574
+ * chunk 0 does. Without the macro -- and on
489
575
  * WASI, which has no threads -- the whole range runs on the calling thread, so
490
576
  * the entry point exists either way and the flag decides only whether the work
491
577
  * is divided. A body that itself calls this runs its range on one thread: the
@@ -495,11 +581,31 @@ void nish_panic_overflow(uint32_t op);
495
581
  * the language surface above it will:
496
582
  * - chunks run concurrently, so a body may read what the caller owns and may
497
583
  * 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. */
584
+ * - nothing a body leaves in the arena outlives its chunk, because a worker's
585
+ * arena is rewound after each chunk while chunk 0's is the caller's own: a
586
+ * string or an array of scalars a chunk answers is moved to a transfer block
587
+ * first (`nish_cross_keep`, below), and the compiler rewinds the caller's
588
+ * arena after its chunks as the pool rewinds a worker's. */
500
589
  typedef void (*nish_par_body)(int64_t lo, int64_t hi, void *ctx);
590
+ /* The CPUs a region may divide over: `nish_cpu_online()` under `--threads`,
591
+ * read once, and 1 in a sequential build. */
501
592
  int64_t nish_cpu_count(void);
593
+ /* `availableParallelism()` (WP34 N12), in every build: the fewest of the CPUs
594
+ * online, those the affinity mask allows, the cgroup v2 `cpu.max` quota
595
+ * rounded up, and `NISH_NUM_THREADS` when it is set to a positive number. At
596
+ * least 1; 1 under WASI. Read each call (runtime-os.c). */
597
+ int64_t nish_cpu_online(void);
502
598
  void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t grain);
599
+ /* Keeps every later region and join on threads created and joined per call,
600
+ * as before the pool existed, so no parked worker outlives the code it would
601
+ * run: for a host that can unload the runtime. Call it before the first region
602
+ * or join; a pool already started stays. A Node addon calls it from the
603
+ * constructor below, because its shim includes <node_api.h> before this
604
+ * header, and Node can unload an addon while a pool's workers are parked. */
605
+ void nish_pool_disable(void);
606
+ #if defined(NAPI_VERSION) && defined(NISH_THREADS)
607
+ __attribute__((constructor)) static void nish_napi_unpooled(void) { nish_pool_disable(); }
608
+ #endif
503
609
 
504
610
  /* ---- A scope's tasks (wp29 stage P2), runtime/runtime-parallel.c ----
505
611
  *
@@ -507,11 +613,12 @@ void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t gra
507
613
  * alive until it joins: it copies `size` bytes of `payload` and keeps them with
508
614
  * `run` and `finish`, and runs nothing yet. `nish_scope_join` takes every task
509
615
  * 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.
616
+ * thread and the others on the worker pool, oldest first, with the calling
617
+ * thread taking any no worker is free for, in that order -- waits for all of
618
+ * them, and then calls `finish(payload)` for each on the calling thread, in
619
+ * the order they were filed. Without `-DNISH_THREADS` every task runs on the
620
+ * calling thread, so running short of threads costs speed and never an
621
+ * answer.
515
622
  *
516
623
  * The preconditions are the language's (docs/LANGUAGE.md, "Scoped tasks"):
517
624
  * `run` writes nothing another task or the caller can see, its result is a
@@ -521,6 +628,96 @@ typedef void (*nish_task_fn)(void *payload);
521
628
  void nish_scope_spawn(void *scope, nish_task_fn run, nish_task_fn finish, const void *payload, int64_t size);
522
629
  void nish_scope_join(void *scope);
523
630
 
631
+ /* ---- A lock that owns its data (wp29 stage P3), runtime/runtime-parallel.c ----
632
+ *
633
+ * A `Mutex`'s lock is one `int32_t` word inside its guard: 0 free, 1 held. The
634
+ * compiler takes it inline, with an acquire compare-and-swap of 0 to 1, and
635
+ * gives it back inline, with a release store of 0 (src/emit-parallel.ts). Only
636
+ * a swap that finds the word held calls `nish_mutex_wait`, which returns once
637
+ * it has taken the lock itself: it spins briefly, then yields the processor
638
+ * between tries. The word lives in the arena with the rest of the `Mutex`, so
639
+ * there is nothing to create and nothing to destroy.
640
+ *
641
+ * The language holds one lock at a time and waits for nothing while it is
642
+ * held (docs/LANGUAGE.md, "A lock that owns its data"), so a holder always
643
+ * gives the word back and a waiter here always returns. Without real threads
644
+ * -- no `-DNISH_THREADS`, or WASI -- nothing else can hold the word, and the
645
+ * first try takes it. */
646
+ void nish_mutex_wait(int32_t *word);
647
+
648
+ /* ---- A channel of scalars or strings (wp29 stage P3), runtime/runtime-parallel.c ----
649
+ *
650
+ * A `Channel` object holds one `int64_t` word, `state`, and each function here
651
+ * takes its address: 0 before first use, then the address of a buffer, a lock
652
+ * and a count this file keeps outside every arena, then 1 once the receiver
653
+ * has drained it closed and it has been given back. A value is one 8-byte
654
+ * slot; the compiler widens a scalar into it and narrows it back, and a
655
+ * string travels as the block `nish_cross_send` copied it into, which the
656
+ * receiver's `nish_cross_in` frees. A string still in the buffer when the
657
+ * receiver stops early (a `break`) is given back with the buffer unread, so
658
+ * its block leaks.
659
+ *
660
+ * `nish_channel_send` appends and never waits: the channel is unbounded.
661
+ * `nish_channel_count` moves the count that keeps it open, which starts at 1:
662
+ * +1 at the spawn of each task that may send on it, -1 when that task returns,
663
+ * and -1 just before its scope joins. At zero the channel is closed.
664
+ * `nish_channel_receive` takes the next value into `*out` and answers 1,
665
+ * waiting while the channel is empty and open, or answers 0 once it is closed
666
+ * and empty, giving the buffer back the first time.
667
+ *
668
+ * The preconditions are the language's (docs/LANGUAGE.md, "Channels"): one
669
+ * receiver, every sender spawned before it, and no send once the scope's
670
+ * block has begun except by its tasks, so nothing touches a channel after its
671
+ * receiver has drained it. */
672
+ void nish_channel_send(int64_t *state, int64_t value);
673
+ void nish_channel_count(int64_t *state, int64_t delta);
674
+ int32_t nish_channel_receive(int64_t *state, int64_t *out);
675
+
676
+ /* ---- Copy at join: a string or a scalar array crosses threads ----
677
+ * runtime/runtime-cross.c
678
+ *
679
+ * A task's or a chunk's arena is rewound once its job is done, so a string or
680
+ * an array of scalars it answers or sends is copied out of it.
681
+ * `nish_cross_out(v, esize, mark)` runs on the thread that made `v`: when `v`
682
+ * was bumped in that thread's arena since `mark` (0: at any time) it answers a
683
+ * copy in a block of its own, outside every arena, with its low bit set; any
684
+ * other value, a literal or one an outer frame made, is answered as it is.
685
+ * `nish_cross_send(s)` is a channel send's: it copies every string `s`, so a
686
+ * send costs the string's length and not a walk of the sender's arena.
687
+ * `nish_cross_keep(v, esize, mark)` is a map element's: it moves `v` to the
688
+ * end of the chunk's transfer block and rewinds the arena to `mark`, the
689
+ * position before the body was called, so a chunk keeps one element's
690
+ * temporaries at a time. `nish_cross_begin(state, list)` pushes the chunk's
691
+ * transfer state, a `{ void *, void *, uint64_t, void * }` the caller holds,
692
+ * whose filled blocks go on `*list`, and `nish_cross_end(state)` pops it; with
693
+ * no list (a map's sequential path), or none pushed, keep copies `v` out and
694
+ * back in below `mark`. `nish_cross_adopt(list, dst, esize)` runs once every
695
+ * chunk is done, with `dst` the array the region wrote: 64 KB or more of
696
+ * blocks on a list become the newest chunks of the calling thread's arena,
697
+ * and less is copied into it, each element of `dst` in a block moved, and the
698
+ * blocks freed.
699
+ * `nish_cross_in(p, esize)` runs on the thread that takes the value: a tagged
700
+ * `p` of 64 KB or more that does not fit the arena's current chunk becomes a
701
+ * chunk of that arena; a smaller one is copied in and its block freed. Either
702
+ * way the block lives exactly as long as a copy would have. `esize` is 0 for a
703
+ * string and the element size of an array whose elements hold no pointer.
704
+ *
705
+ * `nish_guard_keep(old, s)` is a guard's string store (`g.value = s`): it
706
+ * answers a copy of `s` that the `Mutex` owns, outside every arena, and frees
707
+ * `old`, the string the slot held, when it is such a copy. `old` is null for a
708
+ * `string | null` slot that held none, and `s` is null for a `null` stored into
709
+ * one: the slot's copy is freed, nothing is kept, and the store answers null.
710
+ * A copy still held when its `Mutex` is dropped stays until the process ends:
711
+ * one block per string slot, not one per store. */
712
+ void *nish_cross_out(void *v, uint64_t esize, uint64_t mark);
713
+ void *nish_cross_send(void *s);
714
+ void *nish_cross_keep(void *v, uint64_t esize, uint64_t mark);
715
+ void *nish_cross_in(void *p, uint64_t esize);
716
+ void nish_cross_begin(void *state, void **list);
717
+ void nish_cross_end(void *state);
718
+ void nish_cross_adopt(void *list, void *dst, uint64_t esize);
719
+ void *nish_guard_keep(void *old, const void *s);
720
+
524
721
  #ifdef __cplusplus
525
722
  }
526
723
  #endif
package/runtime/nish.mjs CHANGED
@@ -93,6 +93,13 @@ provide("readdirSync", shim.readdirSync);
93
93
  provide("realpathSync", shim.realpathSync);
94
94
  provide("spawnSync", shim.spawnSync);
95
95
  provide("spawnSyncTo", shim.spawnSyncTo);
96
+ // WP34 N12. `spawn` and `waitPid` throw, naming themselves: a child is reaped
97
+ // by Node's event loop. `kill`, `availableParallelism` and `totalMemory` answer.
98
+ provide("spawn", shim.spawn);
99
+ provide("kill", shim.kill);
100
+ provide("waitPid", shim.waitPid);
101
+ provide("availableParallelism", shim.availableParallelism);
102
+ provide("totalMemory", shim.totalMemory);
96
103
  provide("getenv", shim.getenv);
97
104
  provide("statMtimeSync", shim.statMtimeSync);
98
105
  provide("signalFd", shim.signalFd);
@@ -104,6 +111,7 @@ for (const name of [
104
111
  "netAddress",
105
112
  "netLocalPort",
106
113
  "tcpListen",
114
+ "tcpListenFlags",
107
115
  "tcpAccept",
108
116
  "netRead",
109
117
  "netWrite",
@@ -225,6 +233,14 @@ provide("wrappingMul", (a, b) =>
225
233
  provide("Ok", shim.Ok);
226
234
  provide("Err", shim.Err);
227
235
 
236
+ // `String.fromBytes` is Nish's own static on `String`, so it is added to
237
+ // Node's `String` rather than installed as a global. Each byte is one UTF-16
238
+ // unit, as `String.fromCharCode` makes it: ASCII agrees with a native run,
239
+ // and a byte above 127 is the same divergence `fromCharCode`'s is.
240
+ if (!("fromBytes" in String)) {
241
+ String.fromBytes = shim.fromBytes;
242
+ }
243
+
228
244
  // The string parsers, whose deviations from JavaScript are documented rules
229
245
  // (base-10 `parseInt` that saturates, `parseFloat` that reads a `0x` prefix).
230
246
  globalThis.parseInt = shim.parseInt;