@amritk/nish-x86_64-linux 0.28.0 → 0.29.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 CHANGED
@@ -255,7 +255,8 @@ routes are
255
255
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
256
256
  runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
257
257
  runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-term.c \
258
- runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c -lm -o app
258
+ runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
259
+ runtime/runtime-shm.c -lm -o app
259
260
  ```
260
261
 
261
262
  `--link`ing inside the container is the mistake to avoid: it shells out to the
@@ -391,7 +392,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
391
392
  skips the download. §2a has what the build does with it.
392
393
 
393
394
  The published package ships `bin/` (the `nish` command, which is a launcher),
394
- `runtime/` (the C runtime — thirteen translation units, the wasm subset and their
395
+ `runtime/` (the C runtime — fourteen translation units, the wasm subset and their
395
396
  header — and `runtime/sqlite/`, SQLite's vendored source),
396
397
  `scripts/build.sh` (the link pipeline), `std/` (the standard library) and
397
398
  `docs/AI.md` (the rules card). The
@@ -404,13 +405,14 @@ working directory — and the binary runs from inside its own platform package
404
405
  for exactly that reason.
405
406
 
406
407
  Building the IR yourself rather than through `--link` means naming the runtime
407
- on the `clang` line, and it is thirteen files:
408
+ on the `clang` line, and it is fourteen files:
408
409
 
409
410
  ```bash
410
411
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
411
412
  runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
412
413
  runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-term.c \
413
- runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c -lm -o app
414
+ runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
415
+ runtime/runtime-shm.c -lm -o app
414
416
  ```
415
417
 
416
418
  `runtime.c` is the part every program touches — the arena, strings, arrays,
@@ -428,7 +430,8 @@ blocks to on a CPU with the SHA instructions (`NISH_SHA256=soft` keeps the
428
430
  portable path), `runtime-term.c` the stdin reads and terminal calls of `nish:term`,
429
431
  `runtime-bytes.c` the byte-class runs of `nish/bytes`, which `nish/json` steps
430
432
  over, `runtime-checksum.c` the CRC-32 kernels of `nish/compress/checksum`,
431
- and `runtime-sandbox.c` the confinement a `--sandbox` build applies.
433
+ `runtime-sandbox.c` the confinement a `--sandbox` build applies, and
434
+ `runtime-shm.c` the shared-memory regions of `nish/sync/shm`.
432
435
  They are separate so that each carries its own measured size ceiling
433
436
  ([docs/wp7-runtime.md](wp7-runtime.md)); nothing in the core calls into the
434
437
  others, so a line that names only `runtime.c` and `runtime-os.c` still links a
package/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -180,7 +180,7 @@ rejects. This table is the highest-value part of the page.
180
180
  | `keyof T`, `<T = i32>` (a default type argument) | `` Unsupported type `keyof T` (Phase 1 supports …) `` / `a default type argument … is not supported` | name the type; a type argument is always inferred |
181
181
  | `class B extends A` | `` `extends` is not supported: Nish has no inheritance `` | repeat the fields and `implements` an interface |
182
182
  | `static` members, `get x()` / `set x(v)` | `` … `static` members are not supported `` / `` Getters and setters are not supported in class `C` (use a method) `` | module `const`s and plain methods |
183
- | `const [a, b] = xs`, `({ x }: Point) =>`, `(n = 1)`, `(n?: i32)`, `(...ns: i32[])` | `Destructuring is not supported` / `Destructured parameters are not supported` / `Optional/default parameters are not supported` / `Rest parameters are not supported` | one name per binding, every parameter passed; an array for a variable count |
183
+ | `const [a, b] = xs`, `({ x }: Point) =>`, `(n?: i32)`, `(n = 1)`, `f(...xs)` | `Destructuring is not supported` / `Destructured parameters are not supported` / `Optional parameters are not supported` / `` Parameter `n` has a default and no type annotation `` / `Spread arguments are not supported` | one name per binding; `n: i32 = 1` (a constant or an earlier parameter) for a default; `...ns: i32[]` as the last parameter, or pass the array itself |
184
184
  | `function f(): void;` (an overload), `export default function ()` | `Functions must have a body` / `Functions must be named` | one named function, with its body |
185
185
  | a method in an `interface` | `` Interface `I` cannot declare methods (interfaces describe layout only) `` | fields only; a top-level function over the interface |
186
186
  | `abstract class`, `declare class`, `constructor(public x: i32)`, `interface B extends A`, `[k: string]: T` | `Abstract classes are not supported` / `` `declare class` is not supported `` / `Parameter properties … are not supported; declare the field and assign it` / ``Interface inheritance (`extends`) is not supported; list every field`` / `Index signatures are not supported …` | a plain class with its fields declared and assigned in the constructor; every field listed; a `Map` for a keyed table |
@@ -845,10 +845,14 @@ export const main = (): i32 => {
845
845
  - **Call it, or pass it on to another function parameter. Nothing else**:
846
846
  `const g = f`, `return f` and `[f]` are
847
847
  `` `f` is a function parameter and can only be called or passed on as a function argument ``.
848
- - **An arrow captures nothing.** It sees its own parameters and top-level
849
- names; reading a local of the function around it is
850
- `` The arrow reads `k`, which belongs to the function it is written in ``.
851
- Pass the value in as another argument of the template instead.
848
+ - **An arrow may read the `const` locals and parameters of the function it is
849
+ written in**, and write through a captured reference
850
+ (`(x) => { if (x > limit) { hits.push(x) } }`); each capture becomes one more
851
+ argument of the direct call. It may not assign to one, read a `let`
852
+ (`` The arrow reads `k`, a `let` of the function it is written in: ... bind the value to a `const` before the call ``),
853
+ read `this` (bind `const self = this`), or read a function parameter of its
854
+ function. An arrow inside another arrow, or given to a method, a
855
+ constructor, `expose` or `exposeWith`, captures nothing.
852
856
  - **The argument's type must match exactly**: `(x: i32) => i64` for a
853
857
  `(x: i32) => i32` parameter is refused. A generic function cannot be passed
854
858
  by name — write `(x) => identity(x)`.
@@ -858,12 +862,13 @@ export const main = (): i32 => {
858
862
  - **Only a top-level function** may have one: not a method, a constructor, a
859
863
  field, a local, a return type or an alias.
860
864
 
861
- ```ts nish:err NL2335
865
+ ```ts nish:err NL2585
862
866
  const apply = (f: (x: i32) => i32, x: i32): i32 => f(x);
863
867
 
864
868
  export const main = (): i32 => {
865
- const k = 3;
866
- return apply((x) => x + k, 1); // captures `k`: pass it to `apply` instead
869
+ let k = 3;
870
+ k = k + 1;
871
+ return apply((x) => x + k, 1); // reads the `let` `k`: bind `const kNow = k` before the call
867
872
  };
868
873
  ```
869
874
 
@@ -976,6 +981,11 @@ export const main = (): i32 => {
976
981
  (NL2388): `using s = scope()`.
977
982
  The scope is only ever the receiver of a `spawn` statement: never pass it,
978
983
  store it or return it.
984
+ - **A task that recurses deeply gets a bigger stack from `scopeWithStack`.**
985
+ `using s = scopeWithStack(64 * 1024 * 1024)` is a scope whose tasks each run
986
+ on a new thread with a 64 MiB stack; a thread's default is 8 MiB. Ask for at
987
+ least 131072 bytes: a number below it is refused (NL2584). Under Node the size
988
+ is ignored, so run Node with `--stack-size` too.
979
989
  - **The task is a top-level function named in the `spawn`, or an arrow written
980
990
  there.** `s.spawn(entry, arg)` with no destination runs an entry that
981
991
  answers `void`; `s.spawn(() => work(a, b))` runs an arrow that reads the
@@ -1893,32 +1903,52 @@ export const main = (): i32 => {
1893
1903
  `push` inside the body extends the loop. `for...in`, `for await` and
1894
1904
  `for (x of xs)` over an existing `x` are each refused by name.
1895
1905
  - `switch` takes an **integer or enum** discriminant; every `case` label is an
1896
- integer constant expression of that type. **There is no implicit
1897
- fallthrough** — a clause with statements ends in `break`, `return`,
1898
- `continue` or `process.exit`, unless it is the last. An *empty* clause does
1899
- fall through, which is how `case 1: case 2:` shares a body. A clause cannot
1900
- declare a variable directly; wrap its body in a block.
1901
- - `break` / `continue` are unlabelled only; labeled statements are forbidden.
1906
+ integer constant expression of that type. A clause that does not end in
1907
+ `break`, `return`, `continue` or `process.exit` **falls through** into the
1908
+ next, as in TypeScript; an *empty* clause is how `case 1: case 2:` shares a
1909
+ body. A clause reached by falling into it is not narrowed by its label. A
1910
+ clause cannot declare a variable directly; wrap its body in a block.
1911
+ - **Labels** go on a loop, a block or a `switch`: `break outer` leaves the
1912
+ statement named `outer` from any depth, `continue outer` starts the next
1913
+ turn of the loop named `outer`. A label on any other statement is refused,
1914
+ and a jump cannot leave an arrow for a label outside it.
1902
1915
  - `process.exit(code)` and `panic(msg)` **terminate control flow**, so a
1903
1916
  non-`void` function may end with either.
1904
1917
  - **Unreachable code is an error**, not a warning: anything after a `return`,
1905
1918
  `break`, `continue`, `process.exit`, an `if` whose branches all return, or an
1906
1919
  infinite loop.
1907
1920
 
1908
- ```ts nish:err NL2166
1909
- const classify = (n: i32): string => {
1921
+ ```ts nish:ok
1922
+ const firstPair = (limit: i32, target: i32): i32 => {
1923
+ outer: for (let i: i32 = 0; i < limit; i++) {
1924
+ for (let j: i32 = 0; j < limit; j++) {
1925
+ if (i * j === target) {
1926
+ return i * 100 + j;
1927
+ }
1928
+ if (j > i) {
1929
+ continue outer; // the next `i`
1930
+ }
1931
+ }
1932
+ }
1933
+ return -1;
1934
+ };
1935
+
1936
+ const classify = (n: i32): i32 => {
1937
+ let score: i32 = 0;
1910
1938
  switch (n) {
1911
1939
  case 0:
1912
- console.log("zero"); // no `break`: a clause with statements must end in one
1940
+ score += 10; // no `break`: falls into `case 1`
1913
1941
  case 1:
1914
- return "one";
1942
+ score += 1;
1943
+ break;
1915
1944
  default:
1916
- return "many";
1945
+ score = -1;
1917
1946
  }
1947
+ return score;
1918
1948
  };
1919
1949
 
1920
1950
  export const main = (): i32 => {
1921
- console.log(classify(0));
1951
+ console.log(`${firstPair(10, 12)} ${classify(0)} ${classify(1)}`); // 304 11 1
1922
1952
  return 0;
1923
1953
  };
1924
1954
  ```
@@ -2181,6 +2211,10 @@ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2181
2211
  a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2182
2212
  `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2183
2213
  the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2214
+ `spawnWithFds(argv, fds)` is `spawn` that hands the child `fds[i]` as
2215
+ descriptor `3 + i` (systemd's `LISTEN_FDS` numbering); every other descriptor
2216
+ still closes at exec. More than 64, or a negative one, is `-22`; it needs
2217
+ `process.spawn` under `--sandbox`, refuses a wasm target and throws under Node.
2184
2218
  `setNice(n)` sets the nice value, and it is lower-only even as root: `n` takes
2185
2219
  0..19 (`-22` outside it) and a value below the current one is `-13`, with no
2186
2220
  system call either way. `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
@@ -2230,8 +2264,42 @@ unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM.
2230
2264
  `NET_SO_KEEPALIVE`, `NET_SO_SNDBUF` and `NET_SO_RCVBUF`, `i32` ids exported
2231
2265
  beside it; any other id is `-22`. Linux drops quick-ACK mode on its own, so set
2232
2266
  it again after each response. The ids are not module constants: pass them
2233
- directly. None
2234
- exists on a wasm target, and under Node each throws.
2267
+ directly. `netMss(fd)` answers a connected TCP socket's send MSS, the payload
2268
+ bytes one segment carries, and `-1` for anything else (never an errno), so size
2269
+ writes by it with a fallback. None but `netMss` exists on a wasm target (there
2270
+ it is always `-1`), and under Node each throws.
2271
+
2272
+ Unix-domain stream sockets: `unixListen(path, backlog, flags)` listens on a
2273
+ socket file left at 0600 (`unixListenMode(path, backlog, flags, mode)` for
2274
+ another mode), `unixConnect(path)` and `unixAccept(fd)` answer descriptors
2275
+ that `netRead`, `netWrite`, `netShutdown`, `netClose` and the loop take as
2276
+ they are, and `unixSocketPair(NET_UNIX_STREAM, fds)` fills `fds: i32[]` with
2277
+ two connected ends (`NET_UNIX_SEQPACKET` keeps message boundaries; Linux
2278
+ only, `-95` on Darwin). A path starting with `@` is Linux's abstract
2279
+ namespace and makes no file (`-95` on Darwin); write `./@name` for a socket
2280
+ file so named. A path that exists is `-98`; flag `NET_UNIX_UNLINK_STALE`
2281
+ first removes a socket file nobody listens on (Linux; on Darwin it is `-95`
2282
+ at a socket file, since a full backlog and a dead file both refuse there).
2283
+ `NET_UNIX_UNLINK_STALE`, `NET_UNIX_STREAM` and `NET_TCP_NODELAY` are all `1`,
2284
+ so pass the constant each call names. A path as written is at most 107
2285
+ bytes (103 on Darwin) and an abstract name at most 107 after its `@`; longer
2286
+ is `-36`. A full backlog makes `unixConnect`
2287
+ `-11` on Linux and `-111` on Darwin. Under `--sandbox`, a listener on a path
2288
+ also needs `fs.write` (`-13` without it); an abstract name does not, and a
2289
+ connect to a path is not confined. `sendFd(sock, fd, buf, off, len)` sends
2290
+ `buf[off, off + len)` with descriptor `fd` attached (`len` 0 is `-22`) and
2291
+ answers the bytes sent, which on a stream socket can be short: the descriptor
2292
+ went with the first bytes, so send the rest with `netWrite`; `recvFd(sock, buf, off, len, fd)` reads into the
2293
+ range and sets `fd[0]` to the descriptor that came with it, or `-1`; the
2294
+ descriptor arrives close-on-exec, its status flags (`O_NONBLOCK` among them)
2295
+ the open file's, which the sender shares, any beyond the first are closed,
2296
+ and `fd` shorter than 1 is `-22`. `peerCredentials(sock, out)` writes
2297
+ `[pid, uid, gid]` of the process that made the connection, as it was at
2298
+ connect time, into an `out` of length 3 or more (`-22` shorter, `-107` for a
2299
+ listener or a socket never connected); on Darwin the
2300
+ pid can be `-1`. All three answer `-88` or `-95` for a socket that is not
2301
+ Unix-domain. The Unix calls are refused on a wasm target, and under Node each
2302
+ throws.
2235
2303
 
2236
2304
  **Arena.** `Arena.mark()`, `Arena.used()` and `using a = arena()` — see below.
2237
2305
  `Arena.release(m)` and `Arena.reset()` were removed before 1.0: write
@@ -2261,6 +2329,10 @@ console.log(`${seen.size} ${counts.size} ${seen.has("b")}`);
2261
2329
  - A key is a string, any number type, a `boolean`, an enum or a class instance
2262
2330
  (by identity). An interface, an array, a nullable type or a `Result` is not a
2263
2331
  key; a value is anything but `void` or an interface.
2332
+ - A pair of `i32`s is one `i64` key, not a string: `(toI64(a) << toI64(32)) |
2333
+ toI64(toU32(b))`. Every operand is an `i64` so it also runs
2334
+ under Node, where `i64` is a `BigInt`; it is exact for every pair. A `+` in
2335
+ place of `|` overflows at `a = -2^31` with a negative `b`.
2264
2336
  - A module that declares its own `Map` or `Set` keeps it, but then no other
2265
2337
  module of the program may name the global one.
2266
2338
 
@@ -2710,8 +2782,11 @@ export const main = (): i32 => {
2710
2782
  };
2711
2783
  ```
2712
2784
 
2713
- **Optional parameters** — there are none. Write two functions with different
2714
- names, or take the value and document the sentinel.
2785
+ **Optional parameters** — write a default instead: `(n: i32 = 1)`. A default
2786
+ is a literal, an enum member or an earlier parameter, and the call site fills
2787
+ it in. `x?: T` is refused. A variable count of one type is a rest parameter,
2788
+ `(...ns: i32[])`, last in the list; a call never spreads (`f(...xs)`), it passes
2789
+ `xs` to an array parameter.
2715
2790
 
2716
2791
  **A calendar, regex, JSON, threads of your own** — not in the language (the
2717
2792
  wall clock is `Date.now()`, and that is all of `Date`). Say so rather than
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -73,8 +73,9 @@ export declare function isTerminal(fd: i32): boolean;
73
73
  export declare function terminalSize(): i32[];
74
74
  /** `crypto.getRandomValues(bytes)` for a `u8[]`; a typed array goes straight through. */
75
75
  export declare function getRandomValues<T extends u8[] | Uint8Array>(bytes: T): T;
76
- /** These four have no synchronous reading: each throws, naming itself, and is typed as `runtime/nish.d.ts` types it. */
76
+ /** These five have no synchronous reading: each throws, naming itself, and is typed as `runtime/nish.d.ts` types it. */
77
77
  export declare function spawn(argv: string[]): i32;
78
+ export declare function spawnWithFds(argv: string[], fds: readonly i32[]): i32;
78
79
  export declare function waitPid(pid: i32, flags: i32): i32[];
79
80
  export declare function signalFd(): i32;
80
81
  export declare function readSignal(fd: i32): i32;
@@ -309,6 +309,10 @@ export function spawn() {
309
309
  return noProcessReading("spawn");
310
310
  }
311
311
 
312
+ export function spawnWithFds() {
313
+ return noProcessReading("spawnWithFds");
314
+ }
315
+
312
316
  export function waitPid() {
313
317
  return noProcessReading("waitPid");
314
318
  }
package/runtime/nish.d.ts CHANGED
@@ -333,6 +333,14 @@ declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: str
333
333
  * the child is then killed and each array keeps at most the first 16 MiB.
334
334
  */
335
335
  declare function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
336
+ /**
337
+ * `spawn` with the child holding `fds[i]` as descriptor `3 + i`, inherited
338
+ * across the exec; every other descriptor stays close-on-exec. The child's pid,
339
+ * or a negative errno: -22 more than 64 descriptors or a negative one, -9 one
340
+ * that is not open, -24 (or -22 above the descriptor limit) a scratch
341
+ * descriptor the system cannot give. No reading under Node, which throws.
342
+ */
343
+ declare function spawnWithFds(argv: string[], fds: readonly i32[]): i32;
336
344
  /**
337
345
  * Start `argv[0]` through `PATH` without waiting: the child's pid, or a
338
346
  * negative errno. The child is the program's until `waitPid` reaps it. No
@@ -460,6 +468,11 @@ declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
460
468
  * a negative errno (-9 a closed descriptor, -88 not a socket).
461
469
  */
462
470
  declare function netSetOption(fd: i32, option: i32, value: i32): i32;
471
+ /**
472
+ * A connected TCP socket's send MSS, the payload bytes one segment carries;
473
+ * -1, never an errno, for anything else, and always on wasm.
474
+ */
475
+ declare function netMss(fd: i32): i32;
463
476
  /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
464
477
  declare const NET_TCP_NODELAY: i32;
465
478
  /**
@@ -474,6 +487,57 @@ declare const NET_SO_KEEPALIVE: i32;
474
487
  declare const NET_SO_SNDBUF: i32;
475
488
  /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
476
489
  declare const NET_SO_RCVBUF: i32;
490
+ /**
491
+ * A listening Unix stream socket at `path` (`@name`: Linux's abstract
492
+ * namespace), its file mode 0600. A path that exists is -98, unless `flags`
493
+ * holds `NET_UNIX_UNLINK_STALE`; -36 a path too long, -22 an empty one.
494
+ */
495
+ declare function unixListen(path: string, backlog: i32, flags: i32): i32;
496
+ /** `unixListen` with the socket file's mode chosen (0 to 0o777); -22 for an `@name`. */
497
+ declare function unixListenMode(path: string, backlog: i32, flags: i32, mode: i32): i32;
498
+ /** A non-blocking socket connected to the listener at `path`; -11 a full backlog on Linux (-111 on Darwin), -111 nothing listening. */
499
+ declare function unixConnect(path: string): i32;
500
+ /** The next connection on a Unix listener, non-blocking; -11 when none is waiting. */
501
+ declare function unixAccept(fd: i32): i32;
502
+ /**
503
+ * Two connected sockets into `fds[0]` and `fds[1]`, and 0: `kind` is
504
+ * `NET_UNIX_STREAM` or `NET_UNIX_SEQPACKET` (-95 on Darwin); -22 another kind
505
+ * or an `fds` shorter than 2.
506
+ */
507
+ declare function unixSocketPair(kind: i32, fds: i32[]): i32;
508
+ /**
509
+ * Send `buf[off..off+len)` over the Unix socket `sock` with a copy of the
510
+ * descriptor `fd` (`SCM_RIGHTS`): the bytes sent, or a negative errno; -22 a
511
+ * zero `len` or a negative `fd`, -9 a closed `sock` or `fd`, -88 a `sock`
512
+ * that is not a socket, -95 one that is not Unix. A stream socket can send
513
+ * short: the descriptor travels with the first bytes, so send the rest with
514
+ * `netWrite`.
515
+ */
516
+ declare function sendFd(sock: i32, fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
517
+ /**
518
+ * Read at most `len` bytes into `buf[off..]` from the Unix socket `sock`, and
519
+ * the first descriptor that came with them into `fd[0]`, or -1: the bytes read,
520
+ * or a negative errno. Every other descriptor in the message is closed; the
521
+ * one kept is close-on-exec and keeps the sender's blocking mode. -22 a zero
522
+ * `len` or an empty `fd`, -9 a closed `sock`, -88 not a socket, -95 not
523
+ * Unix.
524
+ */
525
+ declare function recvFd(sock: i32, buf: u8[], off: i32, len: i32, fd: i32[]): i32;
526
+ /**
527
+ * The peer of the Unix socket `fd` as of its connect: `out` gets its pid, uid
528
+ * and gid, and the answer is 0; the pid is -1 where Darwin has no
529
+ * `LOCAL_PEERPID`. A uid or gid of 2^31 or above wraps (-2 is uid
530
+ * 4294967294). -22 an `out` shorter than 3, -9 a closed `fd`, -88 not a
531
+ * socket, -95 not Unix, -107 a socket with no peer (a listener, or one never
532
+ * connected).
533
+ */
534
+ declare function peerCredentials(fd: i32, out: i32[]): i32;
535
+ /** `unixListen` flag: unlink a socket file nothing listens on, then bind. */
536
+ declare const NET_UNIX_UNLINK_STALE: i32;
537
+ /** `unixSocketPair` kind: a byte stream. */
538
+ declare const NET_UNIX_STREAM: i32;
539
+ /** `unixSocketPair` kind: each write one message (`SOCK_SEQPACKET`). */
540
+ declare const NET_UNIX_SEQPACKET: i32;
477
541
 
478
542
  // ---- `nish:term` ------------------------------------------------------------------
479
543
  //
@@ -580,6 +644,13 @@ declare module "nish:process" {
580
644
  export function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
581
645
  /** Start a child without waiting: its pid, or a negative errno. */
582
646
  export function spawn(argv: string[]): i32;
647
+ /**
648
+ * `spawn` with the child holding `fds[i]` as descriptor `3 + i`: its pid, or
649
+ * a negative errno; -22 more than 64 descriptors or a negative one, -9 one
650
+ * that is not open, -24 (or -22 above the descriptor limit) a scratch
651
+ * descriptor the system cannot give.
652
+ */
653
+ export function spawnWithFds(argv: string[], fds: readonly i32[]): i32;
583
654
  /** Send 0, 2, 9 or 15 to `pid`: 0, or a negative errno; any other signal is -22. */
584
655
  export function kill(pid: i32, sig: i32): i32;
585
656
  /** `[pid, status]` for a child that exited, `[0, 0]` under WNOHANG (flag 1) with none, or `[-errno, 0]`. */
@@ -667,6 +738,11 @@ declare module "nish:net" {
667
738
  * a negative errno (-9 a closed descriptor, -88 not a socket).
668
739
  */
669
740
  export function netSetOption(fd: i32, option: i32, value: i32): i32;
741
+ /**
742
+ * A connected TCP socket's send MSS, the payload bytes one segment carries;
743
+ * -1, never an errno, for anything else, and always on wasm.
744
+ */
745
+ export function netMss(fd: i32): i32;
670
746
  /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
671
747
  export const NET_TCP_NODELAY: i32;
672
748
  /**
@@ -681,6 +757,57 @@ declare module "nish:net" {
681
757
  export const NET_SO_SNDBUF: i32;
682
758
  /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
683
759
  export const NET_SO_RCVBUF: i32;
760
+ /**
761
+ * A listening Unix stream socket at `path` (`@name`: Linux's abstract
762
+ * namespace), its file mode 0600. A path that exists is -98, unless `flags`
763
+ * holds `NET_UNIX_UNLINK_STALE`; -36 a path too long, -22 an empty one.
764
+ */
765
+ export function unixListen(path: string, backlog: i32, flags: i32): i32;
766
+ /** `unixListen` with the socket file's mode chosen (0 to 0o777); -22 for an `@name`. */
767
+ export function unixListenMode(path: string, backlog: i32, flags: i32, mode: i32): i32;
768
+ /** A non-blocking socket connected to the listener at `path`; -11 a full backlog on Linux (-111 on Darwin), -111 nothing listening. */
769
+ export function unixConnect(path: string): i32;
770
+ /** The next connection on a Unix listener, non-blocking; -11 when none is waiting. */
771
+ export function unixAccept(fd: i32): i32;
772
+ /**
773
+ * Two connected sockets into `fds[0]` and `fds[1]`, and 0: `kind` is
774
+ * `NET_UNIX_STREAM` or `NET_UNIX_SEQPACKET` (-95 on Darwin); -22 another kind
775
+ * or an `fds` shorter than 2.
776
+ */
777
+ export function unixSocketPair(kind: i32, fds: i32[]): i32;
778
+ /**
779
+ * Send `buf[off..off+len)` over the Unix socket `sock` with a copy of the
780
+ * descriptor `fd` (`SCM_RIGHTS`): the bytes sent, or a negative errno; -22 a
781
+ * zero `len` or a negative `fd`, -9 a closed `sock` or `fd`, -88 a `sock`
782
+ * that is not a socket, -95 one that is not Unix. A stream socket can send
783
+ * short: the descriptor travels with the first bytes, so send the rest with
784
+ * `netWrite`.
785
+ */
786
+ export function sendFd(sock: i32, fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
787
+ /**
788
+ * Read at most `len` bytes into `buf[off..]` from the Unix socket `sock`, and
789
+ * the first descriptor that came with them into `fd[0]`, or -1: the bytes read,
790
+ * or a negative errno. Every other descriptor in the message is closed; the
791
+ * one kept is close-on-exec and keeps the sender's blocking mode. -22 a zero
792
+ * `len` or an empty `fd`, -9 a closed `sock`, -88 not a socket, -95 not
793
+ * Unix.
794
+ */
795
+ export function recvFd(sock: i32, buf: u8[], off: i32, len: i32, fd: i32[]): i32;
796
+ /**
797
+ * The peer of the Unix socket `fd` as of its connect: `out` gets its pid, uid
798
+ * and gid, and the answer is 0; the pid is -1 where Darwin has no
799
+ * `LOCAL_PEERPID`. A uid or gid of 2^31 or above wraps (-2 is uid
800
+ * 4294967294). -22 an `out` shorter than 3, -9 a closed `fd`, -88 not a
801
+ * socket, -95 not Unix, -107 a socket with no peer (a listener, or one never
802
+ * connected).
803
+ */
804
+ export function peerCredentials(fd: i32, out: i32[]): i32;
805
+ /** `unixListen` flag: unlink a socket file nothing listens on, then bind. */
806
+ export const NET_UNIX_UNLINK_STALE: i32;
807
+ /** `unixSocketPair` kind: a byte stream. */
808
+ export const NET_UNIX_STREAM: i32;
809
+ /** `unixSocketPair` kind: each write one message (`SOCK_SEQPACKET`). */
810
+ export const NET_UNIX_SEQPACKET: i32;
684
811
  }
685
812
 
686
813
  declare module "nish:unsafe" {
package/runtime/nish.h CHANGED
@@ -200,6 +200,16 @@ extern void (*nish_fatal_hook)(void);
200
200
  /* Number to string, as `${n}` does: decimal for integers, shortest round-trip (JS Number#toString) for f64. */
201
201
  nish_str *nish_str_from_i32(int32_t v);
202
202
  nish_str *nish_str_from_f64(double v);
203
+ /* The bytes `nish_str_from_f64` would hold, written into `out` with no NUL;
204
+ returns the length. 32 bytes is room to spare: the longest spelling,
205
+ -0.0000012345678901234567, is 25 (runtime.c says why, and runtime-test.c
206
+ pins it). For a caller that writes numbers into its own buffer. */
207
+ #define NISH_F64_MAX 32
208
+ int nish_f64_format(double v, char out[NISH_F64_MAX]);
209
+ /* `nish_f64_format`'s bytes into `buf[at]` (std/json.ts's JsonWriter): the new
210
+ end, or -1, with nothing written, when `at < 0` or they do not fit before
211
+ `len`. `buf` may be null only when nothing fits. */
212
+ int32_t nish_json_put_f64(uint8_t *buf, int32_t len, int32_t at, double v);
203
213
  nish_str *nish_str_from_i64(int64_t v);
204
214
  /* Unsigned decimal (WP15). u8/u16/u32 are zero-extended by the caller, so one
205
215
  symbol serves every unsigned width. */
@@ -514,6 +524,14 @@ int32_t nish_spawn_child(const nish_array *argv);
514
524
  * copied; SIGPIPE is blocked on the calling thread for the call, a SIGPIPE the
515
525
  * call raised is consumed, and the thread's mask is restored after it. */
516
526
  int32_t nish_spawn_piped(const nish_array *argv, const nish_array *in, nish_array *out, nish_array *err);
527
+ /* `spawnWithFds(argv, fds)`: `nish_spawn_child` with the child holding
528
+ * `fds[i]` (element type `int32_t`) as descriptor `3 + i`, close-on-exec
529
+ * cleared there. More than 64 descriptors or a negative one is -EINVAL before
530
+ * anything starts, a source that is not open is -EBADF, and a scratch
531
+ * descriptor the system cannot give is -EMFILE (-EINVAL above the descriptor
532
+ * limit); -ENOSYS under
533
+ * WASI. This process's descriptors are left as they were. */
534
+ int32_t nish_spawn_with_fds(const nish_array *argv, const nish_array *fds);
517
535
  /* `kill(pid, sig)`: send `sig` to `pid` and answer 0, or a negative errno. Only
518
536
  * 0, 2 (SIGINT), 9 (SIGKILL) and 15 (SIGTERM) are sent; any other signal, and
519
537
  * a pid of 0 or below, is -EINVAL. -ESRCH when no process has the pid, -EPERM
@@ -651,6 +669,9 @@ bool nish_is_executable(const nish_str *path);
651
669
  * whitelist, by the runtime's own id (1 `TCP_NODELAY`, 2 `TCP_QUICKACK`, 3
652
670
  * `SO_KEEPALIVE`, 4 `SO_SNDBUF`, 5 `SO_RCVBUF`); any other id is -22, and
653
671
  * `TCP_QUICKACK` off Linux is -95.
672
+ * `nish_net_mss(fd)`: a connected TCP socket's send MSS, from `TCP_INFO`'s
673
+ * `tcpi_snd_mss` or else `TCP_MAXSEG`; a plain -1, never an errno, for
674
+ * anything else (a closed descriptor, an unconnected or non-TCP socket).
654
675
  * `nish_tcp_connect(addr)`: a new socket connecting to `addr`, answered at
655
676
  * once; its `EINPROGRESS` is not a failure, and any other closes it.
656
677
  * `nish_connect_result(fd)`: `SO_ERROR` once the socket is writable, 0 when
@@ -675,7 +696,36 @@ bool nish_is_executable(const nish_str *path);
675
696
  * into `ready` (element type `int32_t`) a token and its events, 4 added for a
676
697
  * hang-up or an error, per ready descriptor: at most `ready->len / 2` pairs
677
698
  * and at most 64 in one call. It answers the count, 0 on a timeout or an
678
- * interrupting signal, and -22 for a `ready` shorter than 2. */
699
+ * interrupting signal, and -22 for a `ready` shorter than 2.
700
+ *
701
+ * Unix-domain sockets. A `path` names a socket file, or with a leading `@`
702
+ * Linux's abstract namespace (-95 on Darwin); empty, holding a NUL, or a bare
703
+ * `@` is -22, and one too long for `sun_path` with its NUL is -36, before any
704
+ * socket exists. `nish_unix_listen(path, backlog, flags)` is a listening
705
+ * stream socket whose file has mode 0600; a path that exists is -98, unless
706
+ * flag 1 (`NET_UNIX_UNLINK_STALE`) unlinks a socket file nothing listens on
707
+ * (anything but a socket is -17, a live listener -98). `nish_unix_listen_mode`
708
+ * takes the mode (0..0o777, -22 for an abstract name). `nish_unix_connect(path)`
709
+ * connects, -11 for a full backlog on Linux (-111 on Darwin). `nish_unix_accept(fd)` takes the next
710
+ * connection, -11 when none waits. `nish_unix_socket_pair(kind, fds)` writes
711
+ * two connected sockets into `fds` (element type `int32_t`, at least 2): kind
712
+ * 1 a stream, kind 2 `SOCK_SEQPACKET` (-95 on Darwin). Every descriptor is
713
+ * non-blocking and close-on-exec.
714
+ *
715
+ * Descriptors over a Unix socket: each of the three refuses a descriptor that
716
+ * is closed with -9, one that is not a socket with -88 and a socket of another
717
+ * family with -95.
718
+ * `nish_send_fd(sock, fd, buf, off, len)` sends `len` bytes (at least 1, else
719
+ * -22, as is a negative `fd`) carrying one `SCM_RIGHTS` copy of `fd`; a
720
+ * stream socket can send short, the descriptor gone with the first bytes. `nish_recv_fd(sock, buf, off,
721
+ * len, fd)` reads at most `len` bytes (at least 1, and `fd` at least one
722
+ * `int32_t` long, else -22) and writes the first descriptor received, or -1,
723
+ * into `fd[0]`, closing every other; the one kept is close-on-exec and its
724
+ * status flags are left as the sender's. `nish_peer_credentials(fd, out)`
725
+ * writes the peer's pid, uid and gid as of the connect into `out` (at least
726
+ * 3, else -22), wrapping a uid or gid of 2^31 or above into `int32_t`; a
727
+ * socket with no peer is -ENOTCONN (-107) on both platforms, and Darwin's pid
728
+ * is -1 where `LOCAL_PEERPID` is missing. */
679
729
  int32_t nish_net_address(nish_array *out, const nish_str *host, int32_t port);
680
730
  int32_t nish_net_local_port(int32_t fd);
681
731
  int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog);
@@ -686,6 +736,7 @@ int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t l
686
736
  int32_t nish_net_shutdown(int32_t fd, int32_t how);
687
737
  int32_t nish_net_close(int32_t fd);
688
738
  int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value);
739
+ int32_t nish_net_mss(int32_t fd);
689
740
  int32_t nish_tcp_connect(const nish_array *addr);
690
741
  int32_t nish_connect_result(int32_t fd);
691
742
  int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);
@@ -698,6 +749,14 @@ int32_t nish_poll_add(int32_t loop, int32_t fd, int32_t events, int32_t token);
698
749
  int32_t nish_poll_modify(int32_t loop, int32_t fd, int32_t events, int32_t token);
699
750
  int32_t nish_poll_remove(int32_t loop, int32_t fd);
700
751
  int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms);
752
+ int32_t nish_unix_listen(const nish_str *path, int32_t backlog, int32_t flags);
753
+ int32_t nish_unix_listen_mode(const nish_str *path, int32_t backlog, int32_t flags, int32_t mode);
754
+ int32_t nish_unix_connect(const nish_str *path);
755
+ int32_t nish_unix_accept(int32_t fd);
756
+ int32_t nish_unix_socket_pair(int32_t kind, nish_array *fds);
757
+ int32_t nish_send_fd(int32_t sock, int32_t fd, const nish_array *buf, int64_t off, int64_t len);
758
+ int32_t nish_recv_fd(int32_t sock, nish_array *buf, int64_t off, int64_t len, nish_array *fd);
759
+ int32_t nish_peer_credentials(int32_t fd, nish_array *out);
701
760
 
702
761
  /* ---- The terminal, runtime/runtime-term.c, `nish:term` -------------------
703
762
  *
@@ -781,9 +840,12 @@ int64_t nish_cpu_online(void);
781
840
  /* WP37 `--sandbox` (runtime-sandbox.c): `nish_sandbox` confines the process to
782
841
  the capability mask `caps` from `.preinit_array`, `mode` 1 strict and 2
783
842
  best-effort, and `nish_sandbox_check` refuses to run at the top of `main`
784
- when it never ran. The directory lists are a later stage's and are null.
843
+ when it never ran. `read_dirs` and `write_dirs` are the directories
844
+ Landlock scopes `fs.read` and `fs.write` to, `exec_paths` the programs
845
+ `process.spawn` may execute, each a run of NUL-terminated paths ended by an
846
+ empty one; null leaves the capability whole.
785
847
  `nish_cpu_prime` caches the pool's CPU count while /sys is still readable. */
786
- void nish_sandbox(int32_t caps, int32_t mode, const char *read_dirs, const char *write_dirs);
848
+ void nish_sandbox(int32_t caps, int32_t mode, const char *read_dirs, const char *write_dirs, const char *exec_paths);
787
849
  void nish_sandbox_check(void);
788
850
  void nish_cpu_prime(void);
789
851
  void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t grain);
@@ -811,6 +873,13 @@ __attribute__((constructor)) static void nish_napi_unpooled(void) { nish_pool_di
811
873
  * calling thread, so running short of threads costs speed and never an
812
874
  * answer.
813
875
  *
876
+ * `scope` points at a `ThreadScope` (std/threads.ts), whose first `int32_t` is
877
+ * the stack `scopeWithStack` asked for, or 0. A nonzero one runs every task on
878
+ * a new thread of its own started with that stack, rounded up to the page,
879
+ * plus a guard page, none on the calling thread; if one cannot be started the
880
+ * program ends, saying whether the system was out of threads or memory or
881
+ * refused the size (`nish_stack_fail`). Without threads the word is ignored.
882
+ *
814
883
  * The preconditions are the language's (docs/LANGUAGE.md, "Scoped tasks"):
815
884
  * `run` writes nothing another task or the caller can see, its result is a
816
885
  * scalar it writes into its own payload, and only `finish`, on the calling
package/runtime/nish.mjs CHANGED
@@ -104,9 +104,11 @@ provide("realpathSync", shim.realpathSync);
104
104
  provide("spawnSync", shim.spawnSync);
105
105
  provide("spawnSyncTo", shim.spawnSyncTo);
106
106
  provide("spawnPiped", shim.spawnPiped);
107
- // WP34 N12. `spawn` and `waitPid` throw, naming themselves: a child is reaped
108
- // by Node's event loop. `kill`, `availableParallelism` and `totalMemory` answer.
107
+ // WP34 N12. `spawn`, `spawnWithFds` and `waitPid` throw, naming themselves: a
108
+ // child is reaped by Node's event loop. `kill`, `availableParallelism` and
109
+ // `totalMemory` answer.
109
110
  provide("spawn", shim.spawn);
111
+ provide("spawnWithFds", shim.spawnWithFds);
110
112
  provide("kill", shim.kill);
111
113
  provide("waitPid", shim.waitPid);
112
114
  provide("availableParallelism", shim.availableParallelism);
@@ -147,16 +149,28 @@ for (const name of [
147
149
  "pollRemove",
148
150
  "pollWait",
149
151
  "netSetOption",
152
+ "netMss",
153
+ "unixListen",
154
+ "unixListenMode",
155
+ "unixConnect",
156
+ "unixAccept",
157
+ "unixSocketPair",
158
+ "sendFd",
159
+ "recvFd",
160
+ "peerCredentials",
150
161
  ]) {
151
162
  provide(name, () => shim.noNetReading(name));
152
163
  }
153
- // `netSetOption`'s option ids are plain numbers, so they read the same here
154
- // as natively: only the call that takes one throws.
164
+ // `netSetOption`'s option ids and the `NET_UNIX_` flag and kinds are plain
165
+ // numbers, so they read the same here as natively: only a call throws.
155
166
  provide("NET_TCP_NODELAY", 1);
156
167
  provide("NET_TCP_QUICKACK", 2);
157
168
  provide("NET_SO_KEEPALIVE", 3);
158
169
  provide("NET_SO_SNDBUF", 4);
159
170
  provide("NET_SO_RCVBUF", 5);
171
+ provide("NET_UNIX_UNLINK_STALE", 1);
172
+ provide("NET_UNIX_STREAM", 1);
173
+ provide("NET_UNIX_SEQPACKET", 2);
160
174
 
161
175
  // `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
162
176
  // Node's answers; reading stdin, waiting for it and raw mode throw, each naming