@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 +13 -13
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +85 -1
- package/runtime/nish.h +62 -11
- package/runtime/nish.mjs +38 -3
- package/runtime/runtime-host.c +178 -0
- package/runtime/{runtime_os.c → runtime-os.c} +16 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +128 -6
- package/scripts/build.sh +19 -13
- package/std/README.md +42 -2
- package/std/collections.ts +194 -188
- package/std/crypto/base64url.ts +145 -0
- package/std/crypto/ct.ts +64 -0
- package/std/crypto/hkdf.ts +118 -0
- package/std/crypto/hmac.ts +155 -0
- package/std/crypto/sha256.ts +444 -0
- package/std/crypto/sha512.ts +510 -0
- package/std/crypto/x25519.ts +494 -0
- package/std/json.ts +136 -136
- package/std/map.ts +9 -7
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
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 (`
|
|
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 — `
|
|
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
|
|
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/
|
|
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 `
|
|
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/
|
|
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/
|
|
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 `
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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, `
|
|
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 `
|
|
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
|
|
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
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 `
|
|
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 `
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
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 (`
|
|
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`; `
|
|
187
|
-
* `ARRAY_TYPE` and `
|
|
188
|
-
* bytes, and `tests/layout/
|
|
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
|
|
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/
|
|
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
|
-
* - **
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|