@amritk/nish-x86_64-linux 0.12.0 → 0.14.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  `nish` compiles a static subset of TypeScript to LLVM IR and, with
4
4
  `--link`, to a native binary. The compiler is itself a native binary, written
5
- in Nish (`self/`), and needs nothing to run; the `--link` step (and anything
5
+ in Nish (`src/`), and needs nothing to run; the `--link` step (and anything
6
6
  else that turns `.ll` into machine code) needs an LLVM toolchain.
7
7
 
8
8
  ## 1. Prerequisites
@@ -142,7 +142,7 @@ The npm route installs the **native** compiler, and it is a download rather than
142
142
  build: nothing is compiled on your machine. The package declares one
143
143
  `nish-<os>-<arch>` package per supported platform as an `optionalDependencies`
144
144
  entry with `os` and `cpu` set, so npm fetches exactly the one that matches and
145
- skips the rest. Each of those carries the self-hosted compiler — `self/`
145
+ skips the rest. Each of those carries the self-hosted compiler — `src/`
146
146
  compiled by itself — already built, `--verify`d and smoke-tested on a machine
147
147
  of its own architecture by the release workflow.
148
148
 
@@ -198,7 +198,7 @@ nish: no prebuilt compiler for freebsd/x64
198
198
 
199
199
  Until 0.6.0 it ran the TypeScript compiler that shipped in the same package
200
200
  instead — the same compiler by every test here, about eight times slower, and
201
- no C toolchain needed. That compiler was `src/`, which was deleted in R6
201
+ no C toolchain needed. That compiler was stage0, which was deleted in R6
202
202
  ([wp19](wp19-stage0-retirement.md)), so there is nothing left in the package to
203
203
  fall back to. The cost is stated where the rest of that deletion's costs are,
204
204
  in [wp19 §6](wp19-stage0-retirement.md#6-what-retirement-costs-stated-plainly),
@@ -218,7 +218,7 @@ routes are
218
218
  # in the glibc container, where build/nish runs
219
219
  build/nish app.ts -o app.ll
220
220
  # on the musl host, with its own clang and the runtime from this repository
221
- clang app.ll runtime/runtime.c runtime/runtime_os.c -lm -o app
221
+ clang app.ll runtime/runtime.c runtime/runtime-os.c -lm -o app
222
222
  ```
223
223
 
224
224
  `--link`ing inside the container is the mistake to avoid: it shells out to the
@@ -268,7 +268,7 @@ npm install -g ./amritk-nish-0.4.0.tgz ./amritk-nish-x86_64-linux-0.4.0.tgz
268
268
  ```
269
269
 
270
270
  As a native compiler, which needs no Node at all. A release also attaches the
271
- self-hosted compiler — the binary `self/` produces by compiling itself — one
271
+ self-hosted compiler — the binary `src/` produces by compiling itself — one
272
272
  per supported platform, from the version named in the last column:
273
273
 
274
274
  | Asset | For | Attached from |
@@ -326,7 +326,7 @@ release exists, take its version from
326
326
  Unpack it and run `bin/nish` from wherever you like; put that on `PATH` if you
327
327
  want it there. Keep the directory intact rather than moving the binary out of
328
328
  it: `--link` runs `scripts/build.sh` and compiles the C runtime
329
- (`runtime/runtime.c` and `runtime/runtime_os.c`, the system-call half), and the
329
+ (`runtime/runtime.c` and `runtime/runtime-os.c`, the system-call half), and the
330
330
  compiler finds all of them relative to its own location — `bin/nish` alone in a
331
331
  directory can still emit IR with `-o`, but `--link` will tell you it cannot
332
332
  find `scripts/build.sh`.
@@ -367,23 +367,23 @@ Building the IR yourself rather than through `--link` means naming the runtime
367
367
  on the `clang` line, and it is two files:
368
368
 
369
369
  ```bash
370
- clang app.ll runtime/runtime.c runtime/runtime_os.c -lm -o app
370
+ clang app.ll runtime/runtime.c runtime/runtime-os.c -lm -o app
371
371
  ```
372
372
 
373
373
  `runtime.c` is the half every program touches — the arena, strings, arrays,
374
- number formatting, the panics — and `runtime_os.c` is the half that wraps the
374
+ number formatting, the panics — and `runtime-os.c` is the half that wraps the
375
375
  system calls: files, directories, subprocesses, `getenv`, the monotonic clock.
376
376
  They are separate so that each carries its own measured size ceiling
377
377
  ([docs/wp7-runtime.md](wp7-runtime.md)); nothing in the core calls into the
378
378
  system-call half, so an older line that names `runtime.c` alone still links a
379
379
  program that reads no files and spawns nothing. `scripts/build.sh` compiles
380
- `runtime_os.c` beside any `runtime.c` it is handed, so a build that goes
380
+ `runtime-os.c` beside any `runtime.c` it is handed, so a build that goes
381
381
  through it — every `--link`, and every `--profile` recipe in these documents —
382
382
  needs to name only the one.
383
383
 
384
384
  ## 2a. What `npm run build` does
385
385
 
386
- `self/` is the compiler, written in Nish, and it compiles itself
386
+ `src/` is the compiler, written in Nish, and it compiles itself
387
387
  ([docs/wp14-selfhost.md](wp14-selfhost.md)). From a checkout, with clang on
388
388
  `PATH`, `npm run build` runs `scripts/bootstrap.sh` with the seed from §2:
389
389
 
@@ -406,11 +406,11 @@ binary itself. It looks for `scripts/build.sh` and the two `runtime/*.c` files
406
406
  one level up from wherever it was invoked, then in the working directory, so
407
407
  it wants a checkout or an installed package around it the way `nish` does.
408
408
 
409
- Because the seed is the last release, `self/` may only *use* in its own
409
+ Because the seed is the last release, `src/` may only *use* in its own
410
410
  source the constructs that release compiles. A new construct is implemented
411
- in `self/` and becomes usable inside `self/` from the next release on; CI's
411
+ in `src/` and becomes usable inside `src/` from the next release on; CI's
412
412
  `bootstrap` job is what checks that the released seed still builds stage1.
413
- Until R6 the seed was a TypeScript compiler in `src/`, run under Node; it was
413
+ Until R6 the seed was a TypeScript compiler, stage0, run under Node; it was
414
414
  deleted once the native one answered every flag it did
415
415
  ([wp19](wp19-stage0-retirement.md)).
416
416
 
package/bin/nish CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "The @amritk/nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
package/runtime/nish.d.ts CHANGED
@@ -54,6 +54,12 @@ type u64 = number;
54
54
  type f32 = number;
55
55
  type f64 = number;
56
56
 
57
+ // ---- Ranged integers (docs/wp31-ranged-integers.md) --------------------------
58
+ //
59
+ // An `i32` the compiler knows lies in `[Lo, Hi]`. The bounds are numeric
60
+ // literal types, and `tsc` checks only that; the range itself is `nish`'s.
61
+ type integer<Lo extends number, Hi extends number> = number;
62
+
57
63
  // ---- Result (docs/LANGUAGE.md -> Result and error handling) ------------------
58
64
  //
59
65
  // Modelled as the tagged union TypeScript would use anyway, intersected with
@@ -104,6 +110,19 @@ declare function Err<T, E>(error: E): Result<T, E>;
104
110
  // `Process` is not that lucky, so a project that needs `@types/node` for other
105
111
  // reasons should drop this file's `process` rather than fight it.
106
112
 
113
+ /**
114
+ * The disposable protocol `using` reads (WP29 P2, docs/wp29-thread-surface.md
115
+ * §5): declared here so that a program using `nish/threads`'s scope needs no
116
+ * `"ESNext.Disposable"` in its `lib`. `nish` itself takes `using` only for a
117
+ * `scope()`, and `[Symbol.dispose]` only in `nish/threads`.
118
+ */
119
+ interface SymbolConstructor {
120
+ readonly dispose: unique symbol;
121
+ }
122
+ interface Disposable {
123
+ [Symbol.dispose](): void;
124
+ }
125
+
107
126
  interface Console {
108
127
  /** `x` and a newline to stdout. Statement position; exactly one argument. */
109
128
  log(x: string | number | boolean): void;
@@ -142,6 +161,18 @@ declare function toF64(x: number | boolean): f64;
142
161
  declare function f64ToBits(x: f64): i64;
143
162
  declare function bitsToF64(bits: i64): f64;
144
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
+
145
176
  // ---- Streams and files (globals: Nish has no package resolution) ---------
146
177
 
147
178
  /** `s` to stdout with no trailing newline and no conversion. */
@@ -152,7 +183,7 @@ declare function writeError(s: string): void;
152
183
  * `message` and a newline to stderr, then exit 1. Terminates control flow, so
153
184
  * it is `never`: that is what lets `tsc` agree that a function ending in a
154
185
  * `panic` returns, and that `x` is not null after `if (x === null) { panic(...); }`
155
- * — the guard-then-panic shape `self/` uses everywhere in place of an assert.
186
+ * — the guard-then-panic shape `src/` uses everywhere in place of an assert.
156
187
  */
157
188
  declare function panic(message: string): never;
158
189
  /**
@@ -163,6 +194,12 @@ declare function panic(message: string): never;
163
194
  declare function readFileSync(path: string): string;
164
195
  /** The same read, answering `null` for every path the other exits over. */
165
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;
166
203
  declare function writeFileSync(path: string, data: string): void;
167
204
  declare function appendFileSync(path: string, data: string): void;
168
205
  /** One directory, not recursive; whether a directory is there afterwards. */
@@ -194,6 +231,34 @@ declare function getenv(name: string): string | null;
194
231
  * origin is arbitrary, so only the difference between two reads is meaningful.
195
232
  */
196
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
+ // ---- `Date` and `crypto` (WP34 N3) -----------------------------------------------
252
+ //
253
+ // `lib.es2022` already declares `Date`, whose `now()` is Nish's one member of it,
254
+ // so nothing is added: `tsc` accepting `new Date()` is `tsc` not being Nish's
255
+ // checker. `crypto` is a Web API that `lib.es2022` leaves out, so it is
256
+ // declared with its one member, typed as Nish types it.
257
+
258
+ declare var crypto: {
259
+ /** Every byte of `bytes` from the system's CSPRNG; at most 65,536 bytes a call. */
260
+ getRandomValues(bytes: u8[]): void;
261
+ };
197
262
 
198
263
  // ---- The builtin modules (`nish:`) ---------------------------------------------
199
264
  //
@@ -210,6 +275,7 @@ declare function monotonicNanos(): i64;
210
275
  declare module "nish:fs" {
211
276
  export function readFileSync(path: string): string;
212
277
  export function readFileSyncOrNull(path: string): string | null;
278
+ export function readFileBytesSync(path: string): u8[] | null;
213
279
  export function writeFileSync(path: string, data: string): void;
214
280
  export function appendFileSync(path: string, data: string): void;
215
281
  /** `true` when the directory was created, `false` when it already existed. */
@@ -221,6 +287,8 @@ declare module "nish:fs" {
221
287
  */
222
288
  export function readdirSync(path: string): string[] | null;
223
289
  export function realpathSync(path: string): string | null;
290
+ /** Node's `mtimeMs` for the path, or NaN when it cannot be stat'd. */
291
+ export function statMtimeSync(path: string): f64;
224
292
  }
225
293
 
226
294
  declare module "nish:process" {
@@ -238,6 +306,10 @@ declare module "nish:process" {
238
306
  * difference between two reads is meaningful.
239
307
  */
240
308
  export function monotonicNanos(): i64;
309
+ /** A descriptor readable when SIGTERM or SIGINT arrives; the same one every call, or -1. */
310
+ export function signalFd(): i32;
311
+ /** Block until SIGTERM or SIGINT arrives: 15 or 2, or -1 for a descriptor that is not `signalFd()`'s. */
312
+ export function readSignal(fd: i32): i32;
241
313
  /** The command line; `argv[0]` is the program path, as in C. Read-only. */
242
314
  export const argv: readonly string[];
243
315
  /** The operating system the program runs on: `"linux"`, `"darwin"`, or `"unknown"`. */
@@ -284,6 +356,18 @@ declare interface CPtr {
284
356
  readonly __nishForeignPointer: unique symbol;
285
357
  }
286
358
 
359
+ // ---- `set` on an array ----------------------------------------------------------
360
+ //
361
+ // `dst.set(src, offset)` copies all of `src` into `dst` from `offset` on, with
362
+ // `TypedArray.prototype.set`'s meaning (WP34 N2). A `u8[]` is an `Array` to
363
+ // `tsc`, and `lib.es5.d.ts` gives `Array` a `fill` of the same meaning but no
364
+ // `set`, so this is the one method added to it; `runtime/nish.mjs` installs it
365
+ // under Node. Nish admits it on an array of numbers only.
366
+
367
+ interface Array<T> {
368
+ set(source: readonly T[], offset?: number): void;
369
+ }
370
+
287
371
  // ---- What this file cannot say ----------------------------------------------
288
372
  //
289
373
  // The typed-array aliases (`Int32Array`, `Float32Array`, `Float64Array`,
package/runtime/nish.h CHANGED
@@ -2,10 +2,10 @@
2
2
  *
3
3
  * Include this from C drivers, N-API shims, or any other host that links the C
4
4
  * runtime next to compiled Nish modules. Everything here is a contract shared
5
- * with `self/runtime.ts` (the IR side) and the implementation, which is
5
+ * with `src/runtime.ts` (the IR side) and the implementation, which is
6
6
  * two translation units: `runtime/runtime.c` holds the core every program
7
7
  * touches — the arena, strings, arrays, number formatting, the panics — and
8
- * `runtime/runtime_os.c` holds everything that wraps a system call: the file
8
+ * `runtime/runtime-os.c` holds everything that wraps a system call: the file
9
9
  * functions, the directory and subprocess calls, `nish_getenv`, the monotonic
10
10
  * clock, `nish_platform` / `nish_arch`. They are separate so that each carries
11
11
  * its own measured code-size ceiling (docs/wp7-runtime.md, "Runtime additions
@@ -41,7 +41,7 @@ extern "C" {
41
41
  * ELF refuses to link that against a non-TLS definition. `scripts/build.sh
42
42
  * --threads` passes the macro to every input, which is how `nish --threads
43
43
  * --link` keeps the two halves in step. The same definition is in
44
- * runtime/runtime.c and runtime/runtime_wasm.c. */
44
+ * runtime/runtime.c and runtime/runtime-wasm.c. */
45
45
  #ifdef NISH_THREADS
46
46
  #define NISH_TLS _Thread_local
47
47
  #else
@@ -73,7 +73,7 @@ typedef struct nish_str {
73
73
  * `off` and `cap` are `uint64_t` and not `size_t` because that `i64` is what
74
74
  * the inlined fast path bumps and compares on every target: under wasm32 a
75
75
  * `size_t` pair would put them at bytes 4 and 8 while compiled code reads 8
76
- * and 16. runtime.c and runtime_wasm.c static-assert these offsets. */
76
+ * and 16. runtime.c and runtime-wasm.c static-assert these offsets. */
77
77
  struct nish_arena {
78
78
  char *buf;
79
79
  uint64_t off;
@@ -140,7 +140,7 @@ nish_str *nish_str_from_u64(uint64_t v);
140
140
 
141
141
  /* Process and file I/O (WP7). `nish_exit` never returns; the file functions
142
142
  * print a message to stderr and exit(1) on a fatal error. Everything from here
143
- * to the end of the clock section below is implemented in runtime_os.c, with
143
+ * to the end of the clock section below is implemented in runtime-os.c, with
144
144
  * `nish_random` and `nish_argv_init` the exceptions: neither asks the operating
145
145
  * system anything (the clock only seeds the first, and the entry point hands the
146
146
  * second its arguments), so both stay with the core. `nish_random` keeps one seed
@@ -181,11 +181,11 @@ void nish_append_file(const nish_str *path, const nish_str *data);
181
181
  * A returned array lives in the arena (valid until the next reset/release):
182
182
  * copy `len` elements out of `data` before recycling.
183
183
  *
184
- * An array field stored inside its object (`self/inline_arrays.ts`) is this
184
+ * An array field stored inside its object (`src/inline-arrays.ts`) is this
185
185
  * header followed by its `K` slots, `struct { nish_array h; T slots[K]; }`
186
- * with `h.data == (char *)slots` and `h.cap == K`; `self/runtime.ts`'s
187
- * `ARRAY_TYPE` and `self/structs.ts`'s `INLINE_HEADER_BYTES` are the same 24
188
- * bytes, and `tests/layout/inline_array.c` holds the three to it. It only
186
+ * with `h.data == (char *)slots` and `h.cap == K`; `src/runtime.ts`'s
187
+ * `ARRAY_TYPE` and `src/structs.ts`'s `INLINE_HEADER_BYTES` are the same 24
188
+ * bytes, and `tests/layout/inline-array.c` holds the three to it. It only
189
189
  * happens where no header, `.d.ts` or N-API shim describes the class, so a
190
190
  * host that includes a generated header never meets one. */
191
191
  typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array;
@@ -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
@@ -260,7 +266,7 @@ int32_t nish_spawn(const nish_array *argv);
260
266
  * paths must differ: each is opened separately with its own offset, so naming
261
267
  * one file twice makes the streams overwrite each other instead of
262
268
  * interleaving; capture them apart and concatenate to merge them. One `static`
263
- * implementation in runtime_os.c backs both spawn builtins, which is what keeps
269
+ * implementation in runtime-os.c backs both spawn builtins, which is what keeps
264
270
  * the argument vector, the wait and the signal convention written once. */
265
271
  int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_str *err);
266
272
 
@@ -306,6 +312,31 @@ 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
+
309
340
  /* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
310
341
  * (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
311
342
  * (the whole string, trimmed; blank is 0; `0x` hex accepted, as in JS),
@@ -316,7 +347,7 @@ double nish_parse_number(const nish_str *s, int32_t mode);
316
347
  /* Checked integer division (Rust semantics): the failed-check path. */
317
348
  void nish_panic_div(bool by_zero);
318
349
 
319
- /* ---- Parallel work (WP20 T1 / wp29 stage P1), runtime/runtime_parallel.c ----
350
+ /* ---- Parallel work (WP20 T1 / wp29 stage P1), runtime/runtime-parallel.c ----
320
351
  *
321
352
  * One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
322
353
  * once per chunk of a partition of `[0, len)`: contiguous chunks, at most one
@@ -341,6 +372,26 @@ typedef void (*nish_par_body)(int64_t lo, int64_t hi, void *ctx);
341
372
  int64_t nish_cpu_count(void);
342
373
  void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t grain);
343
374
 
375
+ /* ---- A scope's tasks (wp29 stage P2), runtime/runtime-parallel.c ----
376
+ *
377
+ * `nish_scope_spawn` files one task under `scope`, an address the caller keeps
378
+ * alive until it joins: it copies `size` bytes of `payload` and keeps them with
379
+ * `run` and `finish`, and runs nothing yet. `nish_scope_join` takes every task
380
+ * filed under `scope`, runs `run(payload)` for each -- the first on the calling
381
+ * thread and each other on a thread of its own, which frees its arena before it
382
+ * exits -- waits for all of them, and then calls `finish(payload)` for each on
383
+ * the calling thread, in the order they were filed. Without `-DNISH_THREADS`,
384
+ * or when a thread cannot be created, a task runs on the calling thread, so
385
+ * running short of threads costs speed and never an answer.
386
+ *
387
+ * The preconditions are the language's (docs/LANGUAGE.md, "Scoped tasks"):
388
+ * `run` writes nothing another task or the caller can see, its result is a
389
+ * scalar it writes into its own payload, and only `finish`, on the calling
390
+ * thread, stores into memory the caller owns. */
391
+ typedef void (*nish_task_fn)(void *payload);
392
+ void nish_scope_spawn(void *scope, nish_task_fn run, nish_task_fn finish, const void *payload, int64_t size);
393
+ void nish_scope_join(void *scope);
394
+
344
395
  #ifdef __cplusplus
345
396
  }
346
397
  #endif
package/runtime/nish.mjs CHANGED
@@ -28,9 +28,9 @@
28
28
  * so is every string offset. ASCII agrees; nothing else does.
29
29
  * - **`a[i]` is unchecked**: out of range is `undefined` here and an exit-1
30
30
  * panic natively. Only a program that indexes out of range can tell.
31
- * - **Method dispatch is virtual under Node** and static natively, so an
32
- * override reached through a base-typed value differs (LANGUAGE.md,
33
- * "Method dispatch is static").
31
+ * - **A record put into an array is shared here** and copied natively, and
32
+ * `slice`, `new Array<T>(n)` and the typed-array names each differ too;
33
+ * the document lists them.
34
34
  * - **`orReturn()` does not propagate.** It throws a marker the rewriter's
35
35
  * `try`/`catch` turns into an early `return`; unmodified there is no
36
36
  * `catch`, so it escapes. `Ok`/`Err`/`isOk`/`isErr`/`value`/`error`/
@@ -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,35 @@ 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
+ // `crypto.getRandomValues(bytes)` (WP34 N3). Node has the global, but it
102
+ // takes only a typed array and a `u8[]` is a plain `Array` here, so the one
103
+ // method is replaced on Node's own `crypto` object by one that fills a plain
104
+ // array too (and hands a typed array to the original).
105
+ Object.defineProperty(globalThis.crypto, "getRandomValues", {
106
+ value: shim.getRandomValues,
107
+ writable: true,
108
+ configurable: true,
109
+ });
110
+
111
+ // `dst.set(src, offset)` (WP34 N2). A `u8[]` is a plain `Array` here, which
112
+ // has `fill` with the typed array's meaning already but no `set`, so the one
113
+ // method is added to `Array.prototype` — non-enumerable, as a builtin method is,
114
+ // so no `for...in` sees it — unless something got there first. It copies the
115
+ // source before writing, as the native `memmove` does, and fails a range past
116
+ // the end with the native panic.
117
+ if (!("set" in Array.prototype)) {
118
+ Object.defineProperty(Array.prototype, "set", {
119
+ value: function set(source, offset) {
120
+ shim.arraySet(this, source, offset);
121
+ },
122
+ writable: true,
123
+ configurable: true,
124
+ });
125
+ }
96
126
 
97
127
  // The clock. `monotonicNanos()` answers an `i64`, so the value is a BigInt here
98
128
  // as it is in the rewritten runner — and unusually for the i64 surface that is
@@ -117,6 +147,11 @@ provide("toU64", (x) => shim.convert(x, "f64", "u64"));
117
147
  provide("f64ToBits", shim.f64ToBits);
118
148
  provide("bitsToF64", shim.bitsToF64);
119
149
 
150
+ // Constant time (WP34 N6). Pure functions of their operands, so the answers
151
+ // agree with a native run; the timing does not, and is not claimed here.
152
+ provide("ctSelect", shim.ctSelect);
153
+ provide("ctEq", shim.ctEq);
154
+
120
155
  // `Result`. Everything works but `orReturn`, which needs the caller's control
121
156
  // flow and therefore the rewriter; see the header.
122
157
  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