@amritk/nish-aarch64-linux 0.13.0 → 0.15.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/bin/nish CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-aarch64-linux",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "The @amritk/nish native compiler for Linux on ARM64",
5
5
  "license": "MIT",
6
6
  "os": [
package/runtime/nish.d.ts CHANGED
@@ -161,6 +161,18 @@ declare function toF64(x: number | boolean): f64;
161
161
  declare function f64ToBits(x: f64): i64;
162
162
  declare function bitsToF64(bits: i64): f64;
163
163
 
164
+ // ---- Constant time (WP34 N6) ------------------------------------------------
165
+
166
+ /**
167
+ * `(a & mask) | (b & ~mask)` with the mask hidden from the optimiser, so it is
168
+ * never turned into a branch: `a` for an all-ones mask, `b` for zero. Every
169
+ * operand is one type, `u32` or `u64`. Both are `number` here, so one generic
170
+ * declaration stands for the two.
171
+ */
172
+ declare function ctSelect<T extends u32 | u64>(mask: T, a: T, b: T): T;
173
+ /** All-ones of the operands' type when `a === b`, zero otherwise, without a branch. */
174
+ declare function ctEq<T extends u32 | u64>(a: T, b: T): T;
175
+
164
176
  // ---- Streams and files (globals: Nish has no package resolution) ---------
165
177
 
166
178
  /** `s` to stdout with no trailing newline and no conversion. */
@@ -182,6 +194,12 @@ declare function panic(message: string): never;
182
194
  declare function readFileSync(path: string): string;
183
195
  /** The same read, answering `null` for every path the other exits over. */
184
196
  declare function readFileSyncOrNull(path: string): string | null;
197
+ /**
198
+ * The file's bytes as they are on disk — no UTF-8 assumed, so a zero byte and
199
+ * a byte of 0x80 or above survive — or `null` for every path
200
+ * `readFileSyncOrNull` answers `null` for.
201
+ */
202
+ declare function readFileBytesSync(path: string): u8[] | null;
185
203
  declare function writeFileSync(path: string, data: string): void;
186
204
  declare function appendFileSync(path: string, data: string): void;
187
205
  /** One directory, not recursive; whether a directory is there afterwards. */
@@ -213,6 +231,83 @@ declare function getenv(name: string): string | null;
213
231
  * origin is arbitrary, so only the difference between two reads is meaningful.
214
232
  */
215
233
  declare function monotonicNanos(): i64;
234
+ /**
235
+ * The modification time of `path` in milliseconds since the epoch, with the
236
+ * sub-millisecond fraction the file system keeps (Node's `mtimeMs`), or NaN
237
+ * when it cannot be stat'd. Follows a symbolic link; a directory has one too.
238
+ */
239
+ declare function statMtimeSync(path: string): f64;
240
+ /**
241
+ * A descriptor that becomes readable when SIGTERM or SIGINT arrives, whichever
242
+ * thread the signal lands on, made once (every call answers the same one), or -1.
243
+ */
244
+ declare function signalFd(): i32;
245
+ /**
246
+ * Block until SIGTERM or SIGINT arrives and answer its number, 15 or 2; -1 for
247
+ * any `fd` that is not `signalFd()`'s. No reading under Node, which throws.
248
+ */
249
+ declare function readSignal(fd: i32): i32;
250
+
251
+ // ---- `nish:net` (WP34 N5) --------------------------------------------------------
252
+ //
253
+ // Every call answers an `i32`: `>= 0` on success, a negative errno otherwise, in
254
+ // Linux's numbering on every platform for -11 (would block), -95, -32, -104,
255
+ // -98 and -22. An address is 18 bytes of a `u8[]`: 16 of IPv6 address (IPv4 as
256
+ // `::ffff:a.b.c.d`), then the port, big-endian. Every socket is non-blocking and
257
+ // close-on-exec. No reading under Node, where each throws.
258
+
259
+ /** Write the address form of a numeric `host` and `port` into `out`: 0, or -22. */
260
+ declare function netAddress(out: u8[], host: string, port: i32): i32;
261
+ /** The port the socket `fd` is bound to. */
262
+ declare function netLocalPort(fd: i32): i32;
263
+ /** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
264
+ declare function tcpListen(host: string, port: i32, backlog: i32): i32;
265
+ /** The next connection's descriptor, its address in `peer`; -11 when none waits. */
266
+ declare function tcpAccept(fd: i32, peer: u8[]): i32;
267
+ /** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
268
+ declare function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
269
+ /** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
270
+ declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
271
+ /** Shut the read side (0), the write side (1) or both (2). */
272
+ declare function netShutdown(fd: i32, how: i32): i32;
273
+ /** Close the descriptor. */
274
+ declare function netClose(fd: i32): i32;
275
+ /** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
276
+ declare function udpBind(host: string, port: i32, flags: i32): i32;
277
+ /** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
278
+ declare function udpSendTo(
279
+ fd: i32,
280
+ buf: readonly u8[],
281
+ off: i32,
282
+ len: i32,
283
+ to: readonly u8[],
284
+ segment: i32,
285
+ ecn: i32
286
+ ): i32;
287
+ /** One datagram, or several GRO coalesced, into `buf[off, off + len)`; the sender in `from`, `meta` [segment size, ECN]. */
288
+ declare function udpRecvFrom(fd: i32, buf: u8[], off: i32, len: i32, from: u8[], meta: i32[]): i32;
289
+ /** A readiness loop, level-triggered: epoll on Linux, kqueue on Darwin. */
290
+ declare function pollCreate(): i32;
291
+ /** Watch `fd` for `events` (1 readable, 2 writable) under `token`. */
292
+ declare function pollAdd(loop: i32, fd: i32, events: i32, token: i32): i32;
293
+ /** New events or a new token for a watched `fd`. */
294
+ declare function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
295
+ /** Stop watching `fd`. */
296
+ declare function pollRemove(loop: i32, fd: i32): i32;
297
+ /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
298
+ declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
299
+
300
+ // ---- `Date` and `crypto` (WP34 N3) -----------------------------------------------
301
+ //
302
+ // `lib.es2022` already declares `Date`, whose `now()` is Nish's one member of it,
303
+ // so nothing is added: `tsc` accepting `new Date()` is `tsc` not being Nish's
304
+ // checker. `crypto` is a Web API that `lib.es2022` leaves out, so it is
305
+ // declared with its one member, typed as Nish types it.
306
+
307
+ declare var crypto: {
308
+ /** Every byte of `bytes` from the system's CSPRNG; at most 65,536 bytes a call. */
309
+ getRandomValues(bytes: u8[]): void;
310
+ };
216
311
 
217
312
  // ---- The builtin modules (`nish:`) ---------------------------------------------
218
313
  //
@@ -229,6 +324,7 @@ declare function monotonicNanos(): i64;
229
324
  declare module "nish:fs" {
230
325
  export function readFileSync(path: string): string;
231
326
  export function readFileSyncOrNull(path: string): string | null;
327
+ export function readFileBytesSync(path: string): u8[] | null;
232
328
  export function writeFileSync(path: string, data: string): void;
233
329
  export function appendFileSync(path: string, data: string): void;
234
330
  /** `true` when the directory was created, `false` when it already existed. */
@@ -240,6 +336,8 @@ declare module "nish:fs" {
240
336
  */
241
337
  export function readdirSync(path: string): string[] | null;
242
338
  export function realpathSync(path: string): string | null;
339
+ /** Node's `mtimeMs` for the path, or NaN when it cannot be stat'd. */
340
+ export function statMtimeSync(path: string): f64;
243
341
  }
244
342
 
245
343
  declare module "nish:process" {
@@ -257,6 +355,10 @@ declare module "nish:process" {
257
355
  * difference between two reads is meaningful.
258
356
  */
259
357
  export function monotonicNanos(): i64;
358
+ /** A descriptor readable when SIGTERM or SIGINT arrives; the same one every call, or -1. */
359
+ export function signalFd(): i32;
360
+ /** Block until SIGTERM or SIGINT arrives: 15 or 2, or -1 for a descriptor that is not `signalFd()`'s. */
361
+ export function readSignal(fd: i32): i32;
260
362
  /** The command line; `argv[0]` is the program path, as in C. Read-only. */
261
363
  export const argv: readonly string[];
262
364
  /** The operating system the program runs on: `"linux"`, `"darwin"`, or `"unknown"`. */
@@ -265,6 +367,48 @@ declare module "nish:process" {
265
367
  export const arch: string;
266
368
  }
267
369
 
370
+ declare module "nish:net" {
371
+ /** The address form of a numeric `host` and `port` in `out`'s first 18 bytes: 0, or -22. */
372
+ export function netAddress(out: u8[], host: string, port: i32): i32;
373
+ /** The port the socket is bound to. */
374
+ export function netLocalPort(fd: i32): i32;
375
+ /** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
376
+ export function tcpListen(host: string, port: i32, backlog: i32): i32;
377
+ /** The next connection's descriptor, its address in `peer`; -11 when none waits. */
378
+ export function tcpAccept(fd: i32, peer: u8[]): i32;
379
+ /** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
380
+ export function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
381
+ /** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
382
+ export function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
383
+ /** Shut the read side (0), the write side (1) or both (2). */
384
+ export function netShutdown(fd: i32, how: i32): i32;
385
+ export function netClose(fd: i32): i32;
386
+ /** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
387
+ export function udpBind(host: string, port: i32, flags: i32): i32;
388
+ /** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
389
+ export function udpSendTo(
390
+ fd: i32,
391
+ buf: readonly u8[],
392
+ off: i32,
393
+ len: i32,
394
+ to: readonly u8[],
395
+ segment: i32,
396
+ ecn: i32
397
+ ): i32;
398
+ /** One datagram, or several GRO coalesced, into `buf[off, off + len)`; the sender in `from`, `meta` [segment size, ECN]. */
399
+ export function udpRecvFrom(fd: i32, buf: u8[], off: i32, len: i32, from: u8[], meta: i32[]): i32;
400
+ /** A readiness loop, level-triggered: epoll on Linux, kqueue on Darwin. */
401
+ export function pollCreate(): i32;
402
+ /** Watch `fd` for `events` (1 readable, 2 writable) under `token`. */
403
+ export function pollAdd(loop: i32, fd: i32, events: i32, token: i32): i32;
404
+ /** New events or a new token for a watched `fd`. */
405
+ export function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
406
+ /** Stop watching `fd`. */
407
+ export function pollRemove(loop: i32, fd: i32): i32;
408
+ /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
409
+ export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
410
+ }
411
+
268
412
  declare module "nish:io" {
269
413
  /** `s` to stdout with no trailing newline and no conversion. */
270
414
  export function write(s: string): void;
@@ -303,6 +447,18 @@ declare interface CPtr {
303
447
  readonly __nishForeignPointer: unique symbol;
304
448
  }
305
449
 
450
+ // ---- `set` on an array ----------------------------------------------------------
451
+ //
452
+ // `dst.set(src, offset)` copies all of `src` into `dst` from `offset` on, with
453
+ // `TypedArray.prototype.set`'s meaning (WP34 N2). A `u8[]` is an `Array` to
454
+ // `tsc`, and `lib.es5.d.ts` gives `Array` a `fill` of the same meaning but no
455
+ // `set`, so this is the one method added to it; `runtime/nish.mjs` installs it
456
+ // under Node. Nish admits it on an array of numbers only.
457
+
458
+ interface Array<T> {
459
+ set(source: readonly T[], offset?: number): void;
460
+ }
461
+
306
462
  // ---- What this file cannot say ----------------------------------------------
307
463
  //
308
464
  // The typed-array aliases (`Int32Array`, `Float32Array`, `Float64Array`,
package/runtime/nish.h CHANGED
@@ -243,6 +243,12 @@ bool nish_is_dir(const nish_str *path);
243
243
  * under WASI: `fd_readdir` lists a preopened directory rather than a path, so
244
244
  * porting this there is a different contract and not a translation. */
245
245
  nish_array *nish_readdir(const nish_str *path);
246
+ /* `readFileBytesSync(path)` (WP34 N2): the file's bytes as a `u8[]` with
247
+ * `len == cap`, zero bytes and bytes of 0x80 and above kept as they are, or
248
+ * NULL for every path `nish_read_file_or_null` answers NULL for. The header
249
+ * and the bytes live in the arena, so copy what you keep before the next reset
250
+ * or release. */
251
+ nish_array *nish_read_file_bytes(const nish_str *path);
246
252
  /* `spawnSync(argv)`: run element 0 of `argv` (searched on `PATH`) with `argv`
247
253
  * as its argument vector, wait for it, and answer its exit status, or
248
254
  * `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
@@ -306,6 +312,89 @@ const nish_str *nish_arch(void);
306
312
  * region into one and measure zero. */
307
313
  int64_t nish_monotonic_nanos(void);
308
314
 
315
+ /* ---- The host (WP34 N3), runtime/runtime-host.c --------------------------
316
+ * `Date.now()`: the wall clock, `CLOCK_REALTIME`, in whole milliseconds since
317
+ * the epoch, as JavaScript answers it. Unlike `nish_monotonic_nanos` it can go
318
+ * backwards when the clock is corrected; it is for a time a person or a
319
+ * certificate means, not for measuring an interval. */
320
+ double nish_date_now(void);
321
+ /* `crypto.getRandomValues(bytes)`: fills `bytes->len` bytes from the kernel's
322
+ * CSPRNG (`getrandom` on Linux, `getentropy` elsewhere). More than 65,536
323
+ * bytes, the Web API's limit for one call, and a failing entropy source both
324
+ * print a message and exit 1; nothing weaker is ever substituted. */
325
+ void nish_random_fill(nish_array *bytes);
326
+ /* `statMtimeSync(path)`: the modification time in milliseconds with its
327
+ * sub-millisecond fraction, computed as Node's `mtimeMs` is, or NaN when
328
+ * `stat` fails. Follows a symbolic link. */
329
+ double nish_stat_mtime(const nish_str *path);
330
+ /* `signalFd()`: a descriptor that becomes readable when SIGTERM or SIGINT
331
+ * arrives: the read end of a pipe that a `sigaction` handler writes each
332
+ * signal's number to, whichever thread the signal lands on. Nothing is
333
+ * blocked. Made once; every call answers the same descriptor, and -1 when it
334
+ * could not be made. `readSignal(fd)`: block until one arrives and
335
+ * answer its number (15 or 2), or -1 for any `fd` that is not that descriptor
336
+ * or a read that fails. */
337
+ int32_t nish_signal_fd(void);
338
+ int32_t nish_read_signal(int32_t fd);
339
+
340
+ /* ---- The network (WP34 N5), runtime/runtime-net.c, `nish:net` ------------
341
+ * Every call answers an `int32_t`: `>= 0` on success (a descriptor, a byte
342
+ * count, 0) and a negative errno otherwise, in Linux's numbering on every
343
+ * platform for -11 (would block), -95 (unsupported), -32 (the peer is gone),
344
+ * -104 (reset), -98 (address in use) and -22 (a bad argument). Every socket
345
+ * is non-blocking and close-on-exec. An address is 18 bytes of a `u8[]`
346
+ * (element type `uint8_t`): 16 of IPv6 address, IPv4 as `::ffff:a.b.c.d`,
347
+ * then the port, big-endian; a shorter array is -22. Nothing allocates.
348
+ *
349
+ * `nish_net_address(out, host, port)`: the form of a numeric `host`.
350
+ * `nish_net_local_port(fd)`: the port a socket is bound to.
351
+ * `nish_tcp_listen(host, port, backlog)`: `SO_REUSEADDR`, and `::` hears
352
+ * both families, or IPv4 alone on a kernel without IPv6.
353
+ * `nish_tcp_accept(fd, peer)`: a new descriptor and the peer's address.
354
+ * `nish_net_read` / `nish_net_write(fd, buf, off, len)`: bytes `[off, off +
355
+ * len)` of `buf`, which the compiled call has range-checked; a write to a
356
+ * gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
357
+ * write, 2 both. `nish_net_close(fd)`.
358
+ *
359
+ * UDP: `nish_udp_bind(host, port, flags)` binds a datagram socket, flag 1
360
+ * `SO_REUSEPORT` and flag 2 `UDP_GRO` (-95 on Darwin). `nish_udp_send_to(fd,
361
+ * buf, off, len, to, segment, ecn)` sends `buf[off, off + len)` to the
362
+ * address `to` in one `sendmsg`, cut into `segment`-byte datagrams by
363
+ * `UDP_SEGMENT` when `segment > 0`, with the ECN bits `ecn` (0..3); either on
364
+ * Darwin is -95. `nish_udp_recv_from(fd, buf, off, len, from, meta)` takes
365
+ * one datagram, or several `UDP_GRO` coalesced, writes the sender into `from`
366
+ * and into `meta` (element type `int32_t`, at least 2) the segment size, 0
367
+ * when none was coalesced, and the ECN bits.
368
+ *
369
+ * The readiness loop, level-triggered: epoll on Linux, kqueue on Darwin.
370
+ * `nish_poll_create()` is a close-on-exec loop descriptor.
371
+ * `nish_poll_add(loop, fd, events, token)` and `nish_poll_modify` watch `fd`
372
+ * for `events` (1 readable, 2 writable; any other bit is -22) under `token`,
373
+ * and `nish_poll_remove(loop, fd)` stops. `nish_poll_wait(loop, ready,
374
+ * timeout_ms)` waits at most `timeout_ms` (a negative one forever) and writes
375
+ * into `ready` (element type `int32_t`) a token and its events, 4 added for a
376
+ * hang-up or an error, per ready descriptor: at most `ready->len / 2` pairs
377
+ * and at most 64 in one call. It answers the count, 0 on a timeout or an
378
+ * interrupting signal, and -22 for a `ready` shorter than 2. */
379
+ int32_t nish_net_address(nish_array *out, const nish_str *host, int32_t port);
380
+ int32_t nish_net_local_port(int32_t fd);
381
+ int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog);
382
+ int32_t nish_tcp_accept(int32_t fd, nish_array *peer);
383
+ int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
384
+ int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
385
+ int32_t nish_net_shutdown(int32_t fd, int32_t how);
386
+ int32_t nish_net_close(int32_t fd);
387
+ int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);
388
+ int32_t nish_udp_send_to(int32_t fd, const nish_array *buf, int64_t off, int64_t len, const nish_array *to,
389
+ int32_t segment, int32_t ecn);
390
+ int32_t nish_udp_recv_from(int32_t fd, nish_array *buf, int64_t off, int64_t len, nish_array *from,
391
+ nish_array *meta);
392
+ int32_t nish_poll_create(void);
393
+ int32_t nish_poll_add(int32_t loop, int32_t fd, int32_t events, int32_t token);
394
+ int32_t nish_poll_modify(int32_t loop, int32_t fd, int32_t events, int32_t token);
395
+ int32_t nish_poll_remove(int32_t loop, int32_t fd);
396
+ int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms);
397
+
309
398
  /* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
310
399
  * (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
311
400
  * (the whole string, trimmed; blank is 0; `0x` hex accepted, as in JS),
package/runtime/nish.mjs CHANGED
@@ -84,6 +84,7 @@ provide("writeError", shim.writeError);
84
84
  provide("panic", shim.panic);
85
85
  provide("readFileSync", shim.readFileSync);
86
86
  provide("readFileSyncOrNull", shim.readFileSyncOrNull);
87
+ provide("readFileBytesSync", shim.readFileBytesSync);
87
88
  provide("writeFileSync", shim.writeFileSync);
88
89
  provide("appendFileSync", shim.appendFileSync);
89
90
  provide("mkdirSync", shim.mkdirSync);
@@ -93,6 +94,58 @@ provide("realpathSync", shim.realpathSync);
93
94
  provide("spawnSync", shim.spawnSync);
94
95
  provide("spawnSyncTo", shim.spawnSyncTo);
95
96
  provide("getenv", shim.getenv);
97
+ provide("statMtimeSync", shim.statMtimeSync);
98
+ provide("signalFd", shim.signalFd);
99
+ provide("readSignal", shim.readSignal);
100
+
101
+ // `nish:net` (WP34 N5). Each throws, naming itself: a socket is ready only to
102
+ // Node's event loop, which a program that owns its loop never returns to.
103
+ for (const name of [
104
+ "netAddress",
105
+ "netLocalPort",
106
+ "tcpListen",
107
+ "tcpAccept",
108
+ "netRead",
109
+ "netWrite",
110
+ "netShutdown",
111
+ "netClose",
112
+ "udpBind",
113
+ "udpSendTo",
114
+ "udpRecvFrom",
115
+ "pollCreate",
116
+ "pollAdd",
117
+ "pollModify",
118
+ "pollRemove",
119
+ "pollWait",
120
+ ]) {
121
+ provide(name, () => shim.noNetReading(name));
122
+ }
123
+
124
+ // `crypto.getRandomValues(bytes)` (WP34 N3). Node has the global, but it
125
+ // takes only a typed array and a `u8[]` is a plain `Array` here, so the one
126
+ // method is replaced on Node's own `crypto` object by one that fills a plain
127
+ // array too (and hands a typed array to the original).
128
+ Object.defineProperty(globalThis.crypto, "getRandomValues", {
129
+ value: shim.getRandomValues,
130
+ writable: true,
131
+ configurable: true,
132
+ });
133
+
134
+ // `dst.set(src, offset)` (WP34 N2). A `u8[]` is a plain `Array` here, which
135
+ // has `fill` with the typed array's meaning already but no `set`, so the one
136
+ // method is added to `Array.prototype` — non-enumerable, as a builtin method is,
137
+ // so no `for...in` sees it — unless something got there first. It copies the
138
+ // source before writing, as the native `memmove` does, and fails a range past
139
+ // the end with the native panic.
140
+ if (!("set" in Array.prototype)) {
141
+ Object.defineProperty(Array.prototype, "set", {
142
+ value: function set(source, offset) {
143
+ shim.arraySet(this, source, offset);
144
+ },
145
+ writable: true,
146
+ configurable: true,
147
+ });
148
+ }
96
149
 
97
150
  // The clock. `monotonicNanos()` answers an `i64`, so the value is a BigInt here
98
151
  // as it is in the rewritten runner — and unusually for the i64 surface that is
@@ -117,6 +170,11 @@ provide("toU64", (x) => shim.convert(x, "f64", "u64"));
117
170
  provide("f64ToBits", shim.f64ToBits);
118
171
  provide("bitsToF64", shim.bitsToF64);
119
172
 
173
+ // Constant time (WP34 N6). Pure functions of their operands, so the answers
174
+ // agree with a native run; the timing does not, and is not claimed here.
175
+ provide("ctSelect", shim.ctSelect);
176
+ provide("ctEq", shim.ctEq);
177
+
120
178
  // `Result`. Everything works but `orReturn`, which needs the caller's control
121
179
  // flow and therefore the rewriter; see the header.
122
180
  provide("Ok", shim.Ok);
@@ -0,0 +1,178 @@
1
+ /* Nish runtime, the host half: the wall clock, entropy, a file's modification
2
+ * time and a descriptor that becomes readable when SIGTERM or SIGINT arrives
3
+ * (WP34 N3) — the four facts a program that runs for days asks of the machine
4
+ * it runs on.
5
+ *
6
+ * A translation unit of its own for the reason runtime-os.c is one: each file
7
+ * carries its own measured `.text*` ceiling in tests/run.js. runtime-os.c had
8
+ * 143 bytes of its ceiling left when these arrived, the four measure more than
9
+ * that, and a ceiling is not raised to make room (docs/MASTER_PLAN.md §2 has
10
+ * the numbers). Section GC still means a program that calls none of them pays
11
+ * for none of them, and scripts/build.sh pairs this file with runtime.c like
12
+ * the other two halves, so a link line still names one runtime.
13
+ *
14
+ * Every function here is platform code, and the two platforms differ in three
15
+ * places: `getrandom` on Linux and `getentropy` elsewhere, `st_mtim`
16
+ * against Darwin's `st_mtimespec`, and `pipe2` against `pipe` for the signal
17
+ * descriptor. A WASI build has none of this
18
+ * (the checker refuses all four under a wasm target), so there the file is
19
+ * empty.
20
+ */
21
+ #if defined(__wasi__) || defined(__wasm__)
22
+ /* ISO C wants at least one declaration in a translation unit. */
23
+ typedef int nish_host_unused;
24
+ #else
25
+ #if defined(__linux__)
26
+ /* glibc declares `clock_gettime`, `sigaction` and `pipe2` under `-std=c11`
27
+ only with a feature macro, and `pipe2` only with this one. Darwin declares
28
+ everything by default and hides `getentropy` and the `st_mtimespec`
29
+ spelling behind a strict feature level, so it gets none. */
30
+ #define _GNU_SOURCE
31
+ #endif
32
+ #include <errno.h>
33
+ #include <signal.h>
34
+ #include <stdint.h>
35
+ #include <stdio.h>
36
+ #include <sys/stat.h>
37
+ #include <time.h>
38
+ #include <unistd.h>
39
+ #include <fcntl.h>
40
+ #include <sys/random.h>
41
+
42
+ #include "nish.h"
43
+
44
+ /* The spelling runtime.c and runtime-os.c use for their cold paths. */
45
+ #define NISH_COLD __attribute__((noreturn, cold, noinline))
46
+
47
+ /* `Date.now()`: `CLOCK_REALTIME` in whole milliseconds since the epoch, as
48
+ JavaScript answers it — the milliseconds are counted in integers and then
49
+ converted, so the answer is exact until the year 287,396 and a negative
50
+ time floors the way `Date.now` does rather than rounding towards zero. */
51
+ double nish_date_now(void) {
52
+ struct timespec ts;
53
+ clock_gettime(CLOCK_REALTIME, &ts);
54
+ return (double)((int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
55
+ }
56
+
57
+ static NISH_COLD void nish_entropy_fail(uint64_t asked) {
58
+ if (asked > 65536) {
59
+ dprintf(2, "crypto.getRandomValues: %llu bytes asked for, and one call fills at most 65536\n",
60
+ (unsigned long long)asked);
61
+ } else {
62
+ dprintf(2, "crypto.getRandomValues: the system's entropy source failed\n");
63
+ }
64
+ _exit(1);
65
+ }
66
+
67
+ /* `crypto.getRandomValues(bytes)`: every byte of `bytes` from the kernel's
68
+ CSPRNG, or a panic. There is no fallback to a weaker source, because a
69
+ program handed predictable bytes for a key or a nonce cannot tell. The Web
70
+ API's limit of 65,536 bytes a call is kept, so a program behaves the same
71
+ under Node. `getrandom` without flags blocks only until the pool is first
72
+ initialised at boot and can return short or be interrupted, so it loops;
73
+ `getentropy` fills at most 256 bytes a call and retries nothing itself. */
74
+ void nish_random_fill(nish_array *bytes) {
75
+ uint64_t n = bytes->len;
76
+ char *p = bytes->data;
77
+ if (n > 65536) nish_entropy_fail(n);
78
+ while (n > 0) {
79
+ #if defined(__linux__)
80
+ ssize_t got = getrandom(p, n, 0);
81
+ if (got < 0) {
82
+ if (errno == EINTR) continue;
83
+ nish_entropy_fail(0);
84
+ }
85
+ #else
86
+ size_t got = n < 256 ? n : 256;
87
+ if (getentropy(p, got) != 0) nish_entropy_fail(0);
88
+ #endif
89
+ p += got;
90
+ n -= (uint64_t)got;
91
+ }
92
+ }
93
+
94
+ /* `statMtimeSync(path)`: the modification time in milliseconds, with the
95
+ sub-millisecond part the file system keeps, or NaN when `stat` fails. The
96
+ arithmetic is Node's `mtimeMs` operation for operation — seconds times 1e3
97
+ plus nanoseconds over 1e6, in doubles — so the two readings print the same
98
+ digits. `stat` follows a symbolic link, as `fs.statSync` does. */
99
+ double nish_stat_mtime(const nish_str *path) {
100
+ struct stat st;
101
+ if (stat(path->data, &st) != 0) return __builtin_nan("");
102
+ #if defined(__APPLE__)
103
+ struct timespec t = st.st_mtimespec;
104
+ #else
105
+ struct timespec t = st.st_mtim;
106
+ #endif
107
+ return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
108
+ }
109
+
110
+ /* ---- Signals: `signalFd()` and `readSignal(fd)`
111
+ *
112
+ * A descriptor rather than a handler in the language, because a handler would
113
+ * be a function value the language does not have, and because a descriptor is
114
+ * what a loop waiting in `epoll` or `poll` can wait on beside its sockets.
115
+ *
116
+ * Underneath it is a C handler writing each signal's number, one byte, to a
117
+ * pipe. It is not a `signalfd` on Linux, deliberately: a `signalfd` only
118
+ * hears a signal that is blocked in **every** thread, and a mask reaches only
119
+ * the calling thread, so a `scope()` task or a parallel worker already
120
+ * running when `signalFd()` was called would take the signal at its default
121
+ * action and end the process. A handler runs in whichever thread the kernel
122
+ * picks, so no thread's mask matters, and nothing is blocked for a child
123
+ * `spawnSync` starts to inherit: `exec` resets a caught signal to its
124
+ * default. Installing the handler also overrides a disposition the process
125
+ * was started with, so a signal its parent ignored is heard, as
126
+ * `process.on('SIGTERM')` hears it under Node.
127
+ *
128
+ * The handler is the whole of what runs in signal context, and `write` is
129
+ * async-signal-safe. The write end is non-blocking, so a thousand unread
130
+ * signals lose the newest rather than wedge the handler, and `errno` is put
131
+ * back for the code the signal interrupted. The descriptor is made once and
132
+ * every later `signalFd()` answers the same one. */
133
+ static int nish_signal_read_end = -1;
134
+ static int nish_signal_write_end = -1;
135
+
136
+ static void nish_on_signal(int sig) {
137
+ int saved = errno;
138
+ unsigned char b = (unsigned char)sig;
139
+ (void)!write(nish_signal_write_end, &b, 1);
140
+ errno = saved;
141
+ }
142
+
143
+ int32_t nish_signal_fd(void) {
144
+ if (nish_signal_read_end >= 0) return nish_signal_read_end;
145
+ int p[2];
146
+ #if defined(__linux__)
147
+ /* Both ends close-on-exec from the moment they exist, so a child a sibling
148
+ thread spawns meanwhile cannot inherit either. */
149
+ if (pipe2(p, O_CLOEXEC) != 0) return -1;
150
+ #else
151
+ /* Darwin has no `pipe2`, so there is a window between `pipe` and the two
152
+ `fcntl`s in which a child spawned by another thread inherits the ends. */
153
+ if (pipe(p) != 0) return -1;
154
+ fcntl(p[0], F_SETFD, FD_CLOEXEC);
155
+ fcntl(p[1], F_SETFD, FD_CLOEXEC);
156
+ #endif
157
+ fcntl(p[1], F_SETFL, O_NONBLOCK);
158
+ nish_signal_write_end = p[1];
159
+ struct sigaction sa;
160
+ sa.sa_handler = nish_on_signal;
161
+ sa.sa_flags = SA_RESTART;
162
+ sigemptyset(&sa.sa_mask);
163
+ sigaction(SIGINT, &sa, 0);
164
+ sigaction(SIGTERM, &sa, 0);
165
+ return nish_signal_read_end = p[0];
166
+ }
167
+
168
+ /* Block until a signal's byte arrives and answer it: 15 or 2, or -1 for any
169
+ `fd` that is not the signal descriptor and for a read that fails. */
170
+ int32_t nish_read_signal(int32_t fd) {
171
+ if (fd != nish_signal_read_end) return -1;
172
+ unsigned char b;
173
+ ssize_t n;
174
+ do n = read(fd, &b, 1);
175
+ while (n < 0 && errno == EINTR);
176
+ return n == 1 ? (int32_t)b : -1;
177
+ }
178
+ #endif