@amritk/nish-x86_64-linux 0.23.1 → 0.25.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 (51) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +50 -23
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/arrays.d.ts +0 -2
  6. package/runtime/core/arrays.mjs +0 -2
  7. package/runtime/core/host.d.ts +8 -0
  8. package/runtime/core/host.mjs +50 -2
  9. package/runtime/core/numbers.mjs +2 -3
  10. package/runtime/nish.d.ts +100 -4
  11. package/runtime/nish.h +34 -0
  12. package/runtime/nish.mjs +23 -5
  13. package/runtime/node.mjs +57 -6
  14. package/runtime/runtime-net.c +45 -4
  15. package/runtime/runtime-os.c +115 -12
  16. package/runtime/runtime-term.c +2 -2
  17. package/runtime/runtime.c +1 -1
  18. package/scripts/build.sh +13 -3
  19. package/std/README.md +44 -7
  20. package/std/cli-args.ts +1216 -0
  21. package/std/cli-flags.ts +585 -0
  22. package/std/cli.ts +308 -19
  23. package/std/compress/checksum.ts +290 -0
  24. package/std/compress/deflate.ts +2790 -0
  25. package/std/compress/gzip.ts +761 -0
  26. package/std/compress/zlib.ts +168 -0
  27. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  28. package/std/crypto/p256.ts +63 -1
  29. package/std/crypto/p384.ts +6044 -0
  30. package/std/crypto/pki-roots.ts +322 -0
  31. package/std/crypto/pki.ts +1207 -0
  32. package/std/crypto/rsa.ts +663 -0
  33. package/std/crypto/sha256.ts +2 -3
  34. package/std/crypto/x25519.ts +3 -4
  35. package/std/crypto/x509.ts +1054 -58
  36. package/std/net/dns-config.ts +734 -0
  37. package/std/net/dns-message.ts +517 -0
  38. package/std/net/dns-resolver.ts +1015 -0
  39. package/std/net/dns.ts +478 -0
  40. package/std/net/http-compress.ts +393 -0
  41. package/std/net/http1-client.ts +1227 -0
  42. package/std/net/http1-server.ts +101 -1
  43. package/std/net/http1.ts +9 -9
  44. package/std/net/tls/client.ts +1094 -0
  45. package/std/net/tls/codec.ts +585 -3
  46. package/std/net/tls/record-client.ts +1192 -0
  47. package/std/net/tls/verify.ts +221 -0
  48. package/std/net/tls-tcp.ts +642 -11
  49. package/std/net/tls.ts +2 -2
  50. package/std/net/url.ts +559 -4
  51. package/std/yaml-write.ts +415 -0
package/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -90,8 +90,7 @@ so it refuses `-o` and `--link`. It rewrites your files in place, with no
90
90
  backup, so run it on a committed or backed-up tree. Run it first, then read
91
91
  what remains. A warning can carry a fix too, and `--fix` applies it only when the program
92
92
  compiles and the warning would be printed: `NL9007`'s inserts the guard that
93
- proves an index in range (not under `--no-warn-performance`), and `NL7002`'s
94
- migrates a site off `--unchecked-indexing` (below).
93
+ proves an index in range (not under `--no-warn-performance`).
95
94
 
96
95
  When it compiles, **check the native build against the program's TypeScript
97
96
  reading**. Run the project's tests natively; where it has none, write the
@@ -247,14 +246,9 @@ the site: `uncheckedGet(xs, i)` and `uncheckedSet(xs, i, v)` on an array of
247
246
  numbers with an `i32` index (out of range is undefined behaviour), and
248
247
  `wrappingAdd` / `wrappingSub` / `wrappingMul` on two `i32`s or two `i64`s.
249
248
  The sixth export, `readCString`, copies a C string out of a `CPtr`
250
- ([Calling C](#calling-c)). Calling one without the import is NL2456. `--unchecked-indexing` and
251
- `--wrapping` are deprecated (NL9014, NL9015) and reach only your own package,
252
- never a dependency or `nish/`. To migrate off `--unchecked-indexing`, run
253
- `nish --unchecked-indexing --fix` on your files: each index the flag leaves
254
- unchecked is a `deprecation` warning of its own (NL7002), and where the site
255
- is a number array with an `i32` index its `fix` rewrites it to `uncheckedGet`
256
- or `uncheckedSet` and adds the import. A site with no rewrite keeps its
257
- warning; dropping the flag gives it back its bounds check.
249
+ ([Calling C](#calling-c)). Calling one without the import is NL2456. No flag turns the checks off for a
250
+ whole program: `--unchecked-indexing` and `--wrapping` were removed before 1.0
251
+ and are refused with exit 2.
258
252
 
259
253
  ```ts nish:ok
260
254
  import { uncheckedGet, wrappingAdd, wrappingMul } from "nish:unsafe";
@@ -280,8 +274,7 @@ where it can prove the result fits — a loop counter under `i < n`, a byte
280
274
  widened with `toI32`, an `integer<Lo, Hi>`, a value masked with `& c`. Arithmetic
281
275
  that is *meant* to wrap — a hash, a PRNG, a checksum — goes in `u32` or `u64`,
282
276
  which always wrap and are never checked; convert back with `toI32` (free), or
283
- call `wrappingAdd` / `wrappingSub` / `wrappingMul` at the site. The deprecated
284
- `--wrapping` makes every signed operation of your own package wrap instead.
277
+ call `wrappingAdd` / `wrappingSub` / `wrappingMul` at the site.
285
278
 
286
279
  ```ts nish:ok
287
280
  // FNV-1a: the multiply overflows on purpose, so the state is a `u32`.
@@ -2140,13 +2133,20 @@ a `\u` escape `nish/json` decodes) is one unit per byte, and counts its bytes.
2140
2133
  **Files and the system.** `readFileSync(path)` (exits 1 on failure),
2141
2134
  `readFileSyncOrNull(path)` (`string | null`), `readFileBytesSync(path)`
2142
2135
  (`u8[] | null`, the bytes as they are on disk), `writeFileSync(path, data)`,
2143
- `appendFileSync(path, data)`, `mkdirSync(path)` (one level, `boolean`),
2136
+ `appendFileSync(path, data)`, `writeFileBytesSync(path, bytes)` (a `u8[]` as
2137
+ it is; `i32`: the length, or a negative errno), `renameSync(from, to)` (`0` or
2138
+ a negative errno; atomic on one filesystem, `-18` across two, so write the
2139
+ temporary beside the target), `unlinkSync(path)` (`0` or a negative errno; a
2140
+ directory is refused), `writeBytes(bytes, off, len)` (a `u8[]` range to stdout
2141
+ as it is, the length or a negative errno; the range panics like
2142
+ `netWrite`'s), `mkdirSync(path)` (one level, `boolean`),
2144
2143
  `isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
2145
2144
  bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
2146
2145
  `getenv(name)` (`string | null` — unset and empty are different answers),
2147
2146
  `realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
2148
2147
  `null` when it does not resolve),
2149
- `monotonicNanos()` (`i64`; elapsed time only), and, for checking a path before
2148
+ `monotonicNanos()` (`i64`; elapsed time only), `wallClockNanos()` (`i64`,
2149
+ nanoseconds since the Unix epoch; the wall clock, which can step), and, for checking a path before
2150
2150
  trusting it, `lstatOwnerModeSync(path)` (an `i64`: the owner's uid in the high
2151
2151
  32 bits, `st_mode` in the low 32, `-1` when it does not resolve; a symbolic
2152
2152
  link answers for itself), `geteuid()` (`i64`) and `isExecutableSync(path)`
@@ -2174,6 +2174,12 @@ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2174
2174
  a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2175
2175
  `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2176
2176
  the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2177
+ `setNice(n)` sets the nice value (clamped to -20..19; lowering it unprivileged
2178
+ is `-13`) and `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2179
+ (`-95` on Darwin); each answers 0 or a negative errno. Call them at the top of
2180
+ `main`, never in a task. An unprivileged process can never leave `SCHED_IDLE`
2181
+ and an idle one can starve on a saturated machine, so keep a normal-priority
2182
+ fallback. Neither exists on wasm; both throw under Node.
2177
2183
 
2178
2184
  **Sockets** (`nish:net`, or the same names as globals). `tcpListen(host, port,
2179
2185
  backlog)`, `tcpAccept(fd, peer)`, `netRead(fd, buf, off, len)`,
@@ -2186,7 +2192,8 @@ and there is no `async` and no callback: your `main` is the loop. A client calls
2186
2192
  `tcpConnect(addr)`, which answers the descriptor while it is still connecting;
2187
2193
  wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffers
2188
2194
  are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
2189
- the port big-endian); hosts are numeric literals, because there is no DNS.
2195
+ the port big-endian); hosts are numeric literals; to reach a name, resolve it first with
2196
+ `nish/net/dns` (`DnsClient` on your loop, or the blocking `resolveHost`).
2190
2197
  `netRead` / `netWrite` panic on a range outside the buffer.
2191
2198
  `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2192
2199
  `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
@@ -2205,11 +2212,18 @@ loop, `pollAdd(loop, fd, events, token)` / `pollModify(...)` watch `fd` for
2205
2212
  for `-1`), fills `ready: i32[]` with pairs — `ready[2k]` a token,
2206
2213
  `ready[2k + 1]` its events, 4 added for a hang-up or error — and answers how
2207
2214
  many, **0 on a timeout or a signal**. It is level-triggered, so what you leave
2208
- unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM. None
2215
+ unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM.
2216
+ `netSetOption(fd, option, value)` sets one socket option from a whitelist:
2217
+ `NET_TCP_NODELAY`, `NET_TCP_QUICKACK` (Linux; `-95` on Darwin),
2218
+ `NET_SO_KEEPALIVE`, `NET_SO_SNDBUF` and `NET_SO_RCVBUF`, `i32` ids exported
2219
+ beside it; any other id is `-22`. Linux drops quick-ACK mode on its own, so set
2220
+ it again after each response. The ids are not module constants: pass them
2221
+ directly. None
2209
2222
  exists on a wasm target, and under Node each throws.
2210
2223
 
2211
2224
  **Arena.** `Arena.mark()`, `Arena.used()` and `using a = arena()` — see below.
2212
- `Arena.release(m)` and `Arena.reset()` are deprecated (NL7001).
2225
+ `Arena.release(m)` and `Arena.reset()` were removed before 1.0: write
2226
+ `using a = arena()`.
2213
2227
 
2214
2228
  ### Map and Set
2215
2229
 
@@ -2494,10 +2508,9 @@ anything that runs for a long time, should compile with `--deny-retention`,
2494
2508
  which makes this warning and the three like it errors.
2495
2509
 
2496
2510
  Everything else is bumped from the arena, which is released when `main`
2497
- returns. **Do not write `Arena.release` or `Arena.reset`**: releasing while
2498
- anything allocated after the mark is still referenced is undefined behaviour
2499
- the compiler does not check, so both are deprecated, and each call compiles
2500
- with a `deprecation` warning (NL7001) pointing to `using a = arena()`.
2511
+ returns. To give memory back sooner, scope it with `using a = arena()`;
2512
+ `Arena.release` and `Arena.reset` are gone, and a call to either is an unknown
2513
+ builtin (NL2244).
2501
2514
 
2502
2515
  ## Recipes for what is missing
2503
2516
 
@@ -2584,6 +2597,21 @@ most 100 pairs. (No block, as for `nish/text`; `tests/link/std_url` is its
2584
2597
  program, and the user guide's [standard-library
2585
2598
  page](guide/using-nish/standard-library.md#nishneturl) its walk-through.)
2586
2599
 
2600
+ **Gzip a response, or read a gzipped body** — there is no `node:zlib` and no
2601
+ `CompressionStream`: `nish/compress/gzip` (and `zlib`, `deflate`) compress
2602
+ and decompress into buffers you own. Build a `GzipCompressor(DEFLATE_FAST)`
2603
+ once and call `compress(src, off, len, out, at)`, which answers the end or -1
2604
+ when `out` is shorter than `compressBound(len)`; `GzipDecompressor`'s
2605
+ `decompress(..., limit)` never writes more than `limit` bytes and answers a
2606
+ negative `DEFLATE_ERR_*` / `GZIP_ERR_*` code for any malformed stream, so test
2607
+ it before reading `out`. `GzipWriter` and `GzipReader` stream. On HTTP/1.1,
2608
+ `conn.respondEncoded(status, names, values, body, off, len, enc,
2609
+ conn.acceptEncoding())` with an `HttpCompressor` from `nish/net/http-compress`
2610
+ negotiates, compresses and sets the fields. Do not compress a secret beside
2611
+ text the client chose (BREACH). (No block, as for `nish/text`;
2612
+ `tests/link/std_compress` is its program, and the user guide's
2613
+ [compression page](guide/using-nish/compression.md) its walk-through.)
2614
+
2587
2615
  **Pass a list to a PostgreSQL statement** — there is no driver that expands
2588
2616
  `IN (...)` and no `pg`'s array serialiser: in `nish/net/postgres`, write
2589
2617
  `= any($1::int8[])` or `unnest($1::text[])` and bind the list as one
@@ -2731,8 +2759,7 @@ export const at = (xs: i32[], i: i32): i32 => xs[i];
2731
2759
  A call into the standard library or a dependency that may panic is refused at
2732
2760
  the call (NL2458), because only its own author can prove it: call a function
2733
2761
  that cannot panic, or make the call from a module outside the scope.
2734
- `--unchecked-indexing` is not a proof, and an index it leaves unchecked is
2735
- still refused. `uncheckedGet` and `uncheckedSet` are not proofs either, but
2762
+ `uncheckedGet` and `uncheckedSet` are not proofs, but
2736
2763
  they cannot panic, and their `nish:unsafe` import is the module's visible
2737
2764
  opt-in to an index out of range being undefined behaviour, so the scope allows
2738
2765
  them (as `"allowed": true` sites) and a call into them is never refused:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.23.1",
3
+ "version": "0.25.0",
4
4
  "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -10,9 +10,9 @@
10
10
  * - `exit(code)` throws a `NishExit` carrying the code, since a page cannot
11
11
  * end itself; the caller that started the program catches it, and does
12
12
  * not call the module again (docs/security/runtime-browser.md, BROWSER-2).
13
- * - `monotonicNanos` reads `performance.now()`, `getenv` answers `null`,
14
- * `isTerminal` answers `false` and `terminalSize` `[-25, 0]`, and `argv()`
15
- * is the program alone.
13
+ * - `monotonicNanos` reads `performance.now()`, `wallClockNanos` `Date.now()`,
14
+ * `getenv` answers `null`, `isTerminal` answers `false` and `terminalSize`
15
+ * `[-25, 0]`, and `argv()` is the program alone.
16
16
  * - Every file-system, process and machine call panics, naming itself and
17
17
  * the browser.
18
18
  */
@@ -69,6 +69,7 @@ installHost({
69
69
  call(name) {
70
70
  if (name === "getenv") return null;
71
71
  if (name === "monotonicNanos") return BigInt(Math.round(performance.now() * 1e6));
72
+ if (name === "wallClockNanos") return BigInt(Date.now()) * 1000000n;
72
73
  // A page has no terminal, which `nish:term` answers as a pipe does.
73
74
  if (name === "isTerminal") return false;
74
75
  if (name === "terminalSize") return [-25, 0];
@@ -15,8 +15,6 @@ export declare function arraySetAt<T>(dst: T[], src: readonly T[], offset?: numb
15
15
  export declare function newArray<T>(n: number | bigint, zero: unknown): T[];
16
16
  export declare function arenaUsed(): i64;
17
17
  export declare function arenaMark(): i64;
18
- export declare function arenaRelease(m?: unknown): void;
19
- export declare function arenaReset(): void;
20
18
  /**
21
19
  * The protocol `using` reads, declared here as `nish.d.ts` does, so `using a =
22
20
  * arena()` type-checks with no `"esnext.disposable"` in a program's `lib`.
@@ -145,8 +145,6 @@ export function newArray(n, zero) {
145
145
  // that is when this file grows a second arena to keep count of.
146
146
  export function arenaUsed() { return 0n; }
147
147
  export function arenaMark() { return 0n; }
148
- export function arenaRelease() {}
149
- export function arenaReset() {}
150
148
  /** `using a = arena()`: a disposable whose disposal does nothing, because there is no arena to release. */
151
149
  export function arena() {
152
150
  return { [Symbol.dispose]() {} };
@@ -35,6 +35,7 @@ export declare function log(x: unknown): void;
35
35
  export declare function error(x: unknown): void;
36
36
  export declare function write(s: string): void;
37
37
  export declare function writeError(s: string): void;
38
+ export declare function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
38
39
  export declare function panic(message: string): never;
39
40
  export declare function exit(code: i32): never;
40
41
  export declare function argv(): string[];
@@ -43,6 +44,9 @@ export declare function readFileSyncOrNull(path: string): string | null;
43
44
  export declare function readFileBytesSync(path: string): u8[] | null;
44
45
  export declare function writeFileSync(path: string, data: string): void;
45
46
  export declare function appendFileSync(path: string, data: string): void;
47
+ export declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
48
+ export declare function renameSync(from: string, to: string): i32;
49
+ export declare function unlinkSync(path: string): i32;
46
50
  export declare function mkdirSync(path: string): boolean;
47
51
  export declare function isDirectorySync(path: string): boolean;
48
52
  export declare function readdirSync(path: string): string[] | null;
@@ -56,6 +60,7 @@ export declare function kill(pid: i32, sig: i32): i32;
56
60
  export declare function availableParallelism(): i32;
57
61
  export declare function totalMemory(): i64;
58
62
  export declare function monotonicNanos(): i64;
63
+ export declare function wallClockNanos(): i64;
59
64
  export declare function statMtimeSync(path: string): f64;
60
65
  export declare function lstatOwnerModeSync(path: string): i64;
61
66
  export declare function geteuid(): i64;
@@ -69,6 +74,9 @@ export declare function spawn(argv: string[]): i32;
69
74
  export declare function waitPid(pid: i32, flags: i32): i32[];
70
75
  export declare function signalFd(): i32;
71
76
  export declare function readSignal(fd: i32): i32;
77
+ /** These two have no reading under Node: each throws, naming itself. */
78
+ export declare function setNice(n: i32): i32;
79
+ export declare function setIdlePriority(): i32;
72
80
  /** What each `nish:net` function is under the core: a throw naming `name`. */
73
81
  export declare function noNetReading(name: string): never;
74
82
  /** What `readStdin`, `waitStdin`, `setRawMode` and `setTerminalRestore` are under the core: a throw naming `name`. */
@@ -15,9 +15,11 @@
15
15
  * its own fails loudly rather than writing nowhere.
16
16
  *
17
17
  * The functions that have no synchronous reading under Node (`spawn`,
18
- * `waitPid`, `signalFd`, `readSignal`, the `nish:net` set) throw here without
19
- * asking any host.
18
+ * `waitPid`, `signalFd`, `readSignal`, the `nish:net` set), and `setNice` and
19
+ * `setIdlePriority`, which have none at all, throw here without asking any
20
+ * host.
20
21
  */
22
+ import { panicSlice, toIndex } from "./checks.mjs";
21
23
 
22
24
  /** What every member of the default host throws: no adapter has been imported. */
23
25
  export class NishNoHost extends Error {
@@ -86,6 +88,18 @@ export function writeError(s) {
86
88
  host.writeErr(s);
87
89
  }
88
90
 
91
+ /**
92
+ * `writeBytes(bytes, off, len)`: the range checked as the compiled call checks
93
+ * it, `0 <= off <= off + len <= bytes.length`, through the slice panic, then
94
+ * the host's write of those bytes to stdout.
95
+ */
96
+ export function writeBytes(bytes, off, len) {
97
+ const from = toIndex(off);
98
+ const to = from + toIndex(len);
99
+ if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
100
+ return host.call("writeBytes", [bytes, from, to]);
101
+ }
102
+
89
103
  /** `panic(message)`: the message on stderr, then exit 1. */
90
104
  export function panic(message) {
91
105
  host.writeErr(`${message}\n`);
@@ -131,6 +145,18 @@ export function appendFileSync(path, data) {
131
145
  return host.call("appendFileSync", [path, data]);
132
146
  }
133
147
 
148
+ export function writeFileBytesSync(path, bytes) {
149
+ return host.call("writeFileBytesSync", [path, bytes]);
150
+ }
151
+
152
+ export function renameSync(from, to) {
153
+ return host.call("renameSync", [from, to]);
154
+ }
155
+
156
+ export function unlinkSync(path) {
157
+ return host.call("unlinkSync", [path]);
158
+ }
159
+
134
160
  export function mkdirSync(path) {
135
161
  return host.call("mkdirSync", [path]);
136
162
  }
@@ -183,6 +209,10 @@ export function monotonicNanos() {
183
209
  return host.call("monotonicNanos", []);
184
210
  }
185
211
 
212
+ export function wallClockNanos() {
213
+ return host.call("wallClockNanos", []);
214
+ }
215
+
186
216
  export function statMtimeSync(path) {
187
217
  return host.call("statMtimeSync", [path]);
188
218
  }
@@ -259,6 +289,24 @@ export function waitPid() {
259
289
  return noProcessReading("waitPid");
260
290
  }
261
291
 
292
+ /**
293
+ * `setNice(n)` and `setIdlePriority()` have no reading under Node: Node has one
294
+ * of the nice value (`os.setPriority`) but none of `SCHED_IDLE`, and a reading
295
+ * of one without the other would differ from the native build on the same host.
296
+ * Each throws, naming itself, as `runtime/nish.mjs` does.
297
+ */
298
+ function noSchedulingReading(name) {
299
+ throw new Error(`\`${name}\` has no reading under Node: it changes how the kernel schedules the native program`);
300
+ }
301
+
302
+ export function setNice() {
303
+ return noSchedulingReading("setNice");
304
+ }
305
+
306
+ export function setIdlePriority() {
307
+ return noSchedulingReading("setIdlePriority");
308
+ }
309
+
262
310
  /**
263
311
  * `signalFd()` and `readSignal(fd)` have no faithful reading under Node, and
264
312
  * these say so rather than answer something else. Node delivers a signal to
@@ -90,7 +90,7 @@ export function ctEq(a, b) {
90
90
  // ---- Checked arithmetic (wp33 R4) ----------------------------------------------------
91
91
  //
92
92
  // What `--emit-ts` writes for a signed `+ - *` and unary minus the compiler
93
- // neither proved in range nor was told to wrap (`--wrapping`): the exact
93
+ // did not prove in range: the exact
94
94
  // answer, or the panic `nish_panic_overflow` prints, in its words. An i32 is
95
95
  // exact in a double until it leaves the range, and `Math.imul` is the
96
96
  // wrapped product, so a product that differs from the double one overflowed.
@@ -121,8 +121,7 @@ function checkI64(r, op) {
121
121
  }
122
122
 
123
123
  // `wrappingAdd`, `wrappingSub` and `wrappingMul` from `nish:unsafe`: the
124
- // two's-complement answer at the operands' width, i32 or i64, whatever
125
- // `--wrapping` says.
124
+ // two's-complement answer at the operands' width, i32 or i64.
126
125
  export function wrappingAdd(a, b) {
127
126
  return typeof a === "bigint" ? BigInt.asIntN(64, a + b) : (a + b) | 0;
128
127
  }
package/runtime/nish.d.ts CHANGED
@@ -227,6 +227,12 @@ declare function readCString(p: CPtr): string;
227
227
  declare function write(s: string): void;
228
228
  /** `s` to stderr, likewise. */
229
229
  declare function writeError(s: string): void;
230
+ /**
231
+ * `bytes[off .. off + len)` to stdout, every byte as it is, partial writes and
232
+ * interruptions retried: the count, or a negative errno. A range outside
233
+ * `bytes` panics, as `dst.set` does. The shape is `netWrite`'s.
234
+ */
235
+ declare function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
230
236
  /**
231
237
  * `message` and a newline to stderr, then exit 1. Terminates control flow, so
232
238
  * it is `never`: that is what lets `tsc` agree that a function ending in a
@@ -257,6 +263,19 @@ declare function readFileBytesSync(path: string): u8[] | null;
257
263
  declare function writeFileSync(path: string, data: string): void;
258
264
  /** `data` added at the end of the file, with the same refusal of a symbolic link. */
259
265
  declare function appendFileSync(path: string, data: string): void;
266
+ /**
267
+ * The file's contents replaced by `bytes`, created at 0644 when missing, with
268
+ * the same refusal of a symbolic link: the byte count, or a negative errno.
269
+ * Never an exit.
270
+ */
271
+ declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
272
+ /**
273
+ * `from` moved to `to`, replacing a file there in one step on one file system:
274
+ * 0, or a negative errno (-18 across file systems, -2 for a missing `from`).
275
+ */
276
+ declare function renameSync(from: string, to: string): i32;
277
+ /** The name `path` removed: 0, or a negative errno. A directory is refused, not removed. */
278
+ declare function unlinkSync(path: string): i32;
260
279
  /** One directory, not recursive; whether a directory is there afterwards. */
261
280
  declare function mkdirSync(path: string): boolean;
262
281
  /** Whether a directory is at `path` right now. One `stat`, and never an exit. */
@@ -302,6 +321,21 @@ declare function waitPid(pid: i32, flags: i32): i32[];
302
321
  declare function availableParallelism(): i32;
303
322
  /** Bytes of physical memory, or -1 when the host does not say. */
304
323
  declare function totalMemory(): i64;
324
+ /**
325
+ * Set this process's nice value to `n`: 0, or a negative errno. The kernel
326
+ * clamps `n` to -20..19, and lowering it below the current value needs the
327
+ * privilege to (-13 without it on Linux). Children started afterwards inherit
328
+ * it. Throws under Node.
329
+ */
330
+ declare function setNice(n: i32): i32;
331
+ /**
332
+ * Move this process to `SCHED_IDLE`, which runs it only when nothing else wants
333
+ * the CPU: 0, or a negative errno, and -95 where the host has no such policy
334
+ * (Darwin). An unprivileged process can never leave `SCHED_IDLE` again, and an
335
+ * idle process can starve on a saturated machine, so a caller keeps a
336
+ * normal-priority fallback for work that must finish. Throws under Node.
337
+ */
338
+ declare function setIdlePriority(): i32;
305
339
  /** One environment variable, or `null` when it is unset (an empty value is a set variable). */
306
340
  declare function getenv(name: string): string | null;
307
341
  /**
@@ -309,6 +343,11 @@ declare function getenv(name: string): string | null;
309
343
  * origin is arbitrary, so only the difference between two reads is meaningful.
310
344
  */
311
345
  declare function monotonicNanos(): i64;
346
+ /**
347
+ * The wall clock in nanoseconds since the Unix epoch, for dating an event. It
348
+ * steps when the system time is set, so a duration is `monotonicNanos`'s.
349
+ */
350
+ declare function wallClockNanos(): i64;
312
351
  /**
313
352
  * The modification time of `path` in milliseconds since the epoch, with the
314
353
  * sub-millisecond fraction the file system keeps (Node's `mtimeMs`), or NaN
@@ -381,6 +420,26 @@ declare function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
381
420
  declare function pollRemove(loop: i32, fd: i32): i32;
382
421
  /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
383
422
  declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
423
+ /**
424
+ * Set one socket option from a whitelist: `option` is one of the `NET_*` ids
425
+ * below, and any other is -22; there is no raw level and option number. 0, or
426
+ * a negative errno (-9 a closed descriptor, -88 not a socket).
427
+ */
428
+ declare function netSetOption(fd: i32, option: i32, value: i32): i32;
429
+ /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
430
+ declare const NET_TCP_NODELAY: i32;
431
+ /**
432
+ * `TCP_QUICKACK` (Linux; -95 elsewhere): `value` 1 acknowledges at once, 0
433
+ * delays. Linux does not keep quick-ACK mode: the kernel leaves it on its own,
434
+ * so a server that wants a mode sets it again after every response.
435
+ */
436
+ declare const NET_TCP_QUICKACK: i32;
437
+ /** `SO_KEEPALIVE`: `value` 1 probes an idle connection, 0 does not. */
438
+ declare const NET_SO_KEEPALIVE: i32;
439
+ /** `SO_SNDBUF`: the send buffer, `value` in bytes; Linux doubles the value it stores. */
440
+ declare const NET_SO_SNDBUF: i32;
441
+ /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
442
+ declare const NET_SO_RCVBUF: i32;
384
443
 
385
444
  // ---- `nish:term` ------------------------------------------------------------------
386
445
  //
@@ -441,6 +500,12 @@ declare module "nish:fs" {
441
500
  export function writeFileSync(path: string, data: string): void;
442
501
  /** Refuses a symbolic link as the last component of the path, as the global does. */
443
502
  export function appendFileSync(path: string, data: string): void;
503
+ /** The byte count, or a negative errno; refuses a symbolic link as `writeFileSync` does. */
504
+ export function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
505
+ /** 0, or a negative errno: -18 across file systems. */
506
+ export function renameSync(from: string, to: string): i32;
507
+ /** 0, or a negative errno: a directory is refused, not removed. */
508
+ export function unlinkSync(path: string): i32;
444
509
  /** `true` when the directory was created, `false` when it already existed. */
445
510
  export function mkdirSync(path: string): boolean;
446
511
  export function isDirectorySync(path: string): boolean;
@@ -475,11 +540,21 @@ declare module "nish:process" {
475
540
  export function availableParallelism(): i32;
476
541
  /** Bytes of physical memory, or -1. */
477
542
  export function totalMemory(): i64;
543
+ /** Set this process's nice value (clamped to -20..19): 0, or a negative errno. */
544
+ export function setNice(n: i32): i32;
545
+ /**
546
+ * Move this process to `SCHED_IDLE`: 0, a negative errno, or -95 off Linux.
547
+ * An unprivileged process can never leave it, and an idle process can starve
548
+ * on a saturated machine, so a caller keeps a normal-priority fallback.
549
+ */
550
+ export function setIdlePriority(): i32;
478
551
  /**
479
552
  * A monotonic clock in nanoseconds. The origin is arbitrary, so only the
480
553
  * difference between two reads is meaningful.
481
554
  */
482
555
  export function monotonicNanos(): i64;
556
+ /** The wall clock in nanoseconds since the Unix epoch; it steps when the system time is set. */
557
+ export function wallClockNanos(): i64;
483
558
  /** A descriptor readable when SIGTERM or SIGINT arrives; the same one every call, or -1. */
484
559
  export function signalFd(): i32;
485
560
  /** Block until SIGTERM or SIGINT arrives: 15 or 2, or -1 for a descriptor that is not `signalFd()`'s. */
@@ -538,6 +613,26 @@ declare module "nish:net" {
538
613
  export function pollRemove(loop: i32, fd: i32): i32;
539
614
  /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
540
615
  export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
616
+ /**
617
+ * Set one socket option from a whitelist: `option` is one of the `NET_*` ids
618
+ * below, and any other is -22; there is no raw level and option number. 0, or
619
+ * a negative errno (-9 a closed descriptor, -88 not a socket).
620
+ */
621
+ export function netSetOption(fd: i32, option: i32, value: i32): i32;
622
+ /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
623
+ export const NET_TCP_NODELAY: i32;
624
+ /**
625
+ * `TCP_QUICKACK` (Linux; -95 elsewhere): `value` 1 acknowledges at once, 0
626
+ * delays. Linux does not keep quick-ACK mode: the kernel leaves it on its own,
627
+ * so a server that wants a mode sets it again after every response.
628
+ */
629
+ export const NET_TCP_QUICKACK: i32;
630
+ /** `SO_KEEPALIVE`: `value` 1 probes an idle connection, 0 does not. */
631
+ export const NET_SO_KEEPALIVE: i32;
632
+ /** `SO_SNDBUF`: the send buffer, `value` in bytes; Linux doubles the value it stores. */
633
+ export const NET_SO_SNDBUF: i32;
634
+ /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
635
+ export const NET_SO_RCVBUF: i32;
541
636
  }
542
637
 
543
638
  declare module "nish:unsafe" {
@@ -560,6 +655,11 @@ declare module "nish:io" {
560
655
  export function write(s: string): void;
561
656
  /** The same, on stderr. */
562
657
  export function writeError(s: string): void;
658
+ /**
659
+ * `bytes[off .. off + len)` to stdout, every byte as it is: the count, or a
660
+ * negative errno. A range outside `bytes` panics, as `dst.set` does.
661
+ */
662
+ export function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
563
663
  /** `message` to stderr, then exit 1. A terminator, as `process.exit` is. */
564
664
  export function panic(message: string): never;
565
665
  }
@@ -617,10 +717,6 @@ declare module "nish:secret" {
617
717
  declare const Arena: {
618
718
  /** The current bump address. */
619
719
  mark(): i64;
620
- /** Free everything allocated since `m`. */
621
- release(m: i64): void;
622
- /** Recycle everything in O(1), keeping the newest chunk. */
623
- reset(): void;
624
720
  /** Bytes bumped in the current chunk. */
625
721
  used(): i64;
626
722
  };
package/runtime/nish.h CHANGED
@@ -396,6 +396,22 @@ nish_array *nish_readdir(const nish_str *path);
396
396
  * and the bytes live in the arena, so copy what you keep before the next reset
397
397
  * or release. */
398
398
  nish_array *nish_read_file_bytes(const nish_str *path);
399
+ /* `renameSync(from, to)`, `unlinkSync(path)` and `writeFileBytesSync(path,
400
+ * bytes)`: rename(2), unlink(2), and every element of a `u8[]` written to a
401
+ * file opened as `nish_write_file` opens it (`O_TRUNC`, 0644, a link refused).
402
+ * None exits: each answers 0 (the write, the length written) or a negative
403
+ * errno — -EXDEV for a rename across file systems, which moves nothing, and
404
+ * -EISDIR or -EPERM for a directory handed to `nish_unlink`, which never calls
405
+ * rmdir. A path holding a NUL answers -EINVAL, not -ENOENT, so a program can
406
+ * tell a malformed path from a missing file. */
407
+ int32_t nish_rename(const nish_str *from, const nish_str *to);
408
+ int32_t nish_unlink(const nish_str *path);
409
+ int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes);
410
+ /* `writeBytes(bytes, off, len)` (nish:io), in runtime-os.c: the bytes
411
+ * `[off, off + len)` of a `u8[]` on stdout, a short write finished and `EINTR`
412
+ * retried. The caller has range-checked `off` and `len`. Answers `len`, or the
413
+ * negative errno of the write that failed; never exits. */
414
+ int32_t nish_write_bytes(const nish_array *bytes, int64_t off, int64_t len);
399
415
  /* `spawnSync(argv)`: run element 0 of `argv` (searched on `PATH`) with `argv`
400
416
  * as its argument vector, wait for it, and answer its exit status, or
401
417
  * `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
@@ -438,6 +454,15 @@ nish_array *nish_wait_pid(int32_t pid, int32_t flags);
438
454
  /* `totalMemory()`: bytes of physical memory (Linux: `_SC_PHYS_PAGES` times the
439
455
  * page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
440
456
  int64_t nish_total_memory(void);
457
+ /* `setNice(n)`: this process's nice value set to `n` (`setpriority`), 0 or a
458
+ * negative errno. The kernel clamps `n` to -20..19; lowering it below the
459
+ * current value without the privilege is -EACCES (or -EPERM). -ENOSYS under
460
+ * WASI. */
461
+ int32_t nish_set_nice(int32_t n);
462
+ /* `setIdlePriority()`: this process moved to `SCHED_IDLE` (Linux), 0 or a
463
+ * negative errno; -95 (Linux's EOPNOTSUPP) elsewhere, -ENOSYS under WASI. An
464
+ * unprivileged process cannot leave the policy again. */
465
+ int32_t nish_set_idle_priority(void);
441
466
 
442
467
  /* ---- The environment (WP19 R1) ------------------------------------------
443
468
  * `getenv(name)`: the value of environment variable `name`, copied into the
@@ -485,6 +510,10 @@ const nish_str *nish_arch(void);
485
510
  * purpose: `readnone` would let LLVM fold the two reads around a measured
486
511
  * region into one and measure zero. */
487
512
  int64_t nish_monotonic_nanos(void);
513
+ /* `wallClockNanos()`: `CLOCK_REALTIME` in nanoseconds since the epoch, the
514
+ * clock `Date.now` reads at a millisecond's grain. It steps when the system
515
+ * time is set, so it dates an event and measures nothing. */
516
+ int64_t nish_wall_clock_nanos(void);
488
517
 
489
518
  /* ---- The host (WP34 N3), runtime/runtime-host.c --------------------------
490
519
  * `Date.now()`: the wall clock, `CLOCK_REALTIME`, in whole milliseconds since
@@ -542,6 +571,10 @@ bool nish_is_executable(const nish_str *path);
542
571
  * len)` of `buf`, which the compiled call has range-checked; a write to a
543
572
  * gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
544
573
  * write, 2 both. `nish_net_close(fd)`.
574
+ * `nish_net_set_option(fd, option, value)`: one socket option from a
575
+ * whitelist, by the runtime's own id (1 `TCP_NODELAY`, 2 `TCP_QUICKACK`, 3
576
+ * `SO_KEEPALIVE`, 4 `SO_SNDBUF`, 5 `SO_RCVBUF`); any other id is -22, and
577
+ * `TCP_QUICKACK` off Linux is -95.
545
578
  * `nish_tcp_connect(addr)`: a new socket connecting to `addr`, answered at
546
579
  * once; its `EINPROGRESS` is not a failure, and any other closes it.
547
580
  * `nish_connect_result(fd)`: `SO_ERROR` once the socket is writable, 0 when
@@ -576,6 +609,7 @@ int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
576
609
  int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
577
610
  int32_t nish_net_shutdown(int32_t fd, int32_t how);
578
611
  int32_t nish_net_close(int32_t fd);
612
+ int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value);
579
613
  int32_t nish_tcp_connect(const nish_array *addr);
580
614
  int32_t nish_connect_result(int32_t fd);
581
615
  int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);