@amritk/nish-x86_64-linux 0.16.0 → 0.17.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 +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +30 -7
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-wasm.c +1 -0
- package/runtime/runtime.c +46 -8
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +166 -35
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +148 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
package/INSTALL.md
CHANGED
|
@@ -61,8 +61,9 @@ The `wasi` profile links the runtime against wasi-libc so that whole programs
|
|
|
61
61
|
needs a WASI sysroot and compiler-rt's wasm32 builtins next to your clang:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
# Ubuntu / Debian: the packaged sysroot lands in /usr/lib/wasi
|
|
64
|
+
# Ubuntu / Debian: the packaged sysroot lands in /usr (/usr/include/wasm32-wasi, /usr/lib/wasm32-wasi)
|
|
65
65
|
sudo apt-get install -y wasi-libc libclang-rt-18-dev-wasm32
|
|
66
|
+
export WASI_SYSROOT=/usr # add to your shell profile
|
|
66
67
|
|
|
67
68
|
# Any platform: wasi-sdk's sysroot and builtins tarballs (versions that match your clang)
|
|
68
69
|
curl -L -o - https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sysroot-24.0.tar.gz | tar -xz -C /opt
|
|
@@ -74,7 +75,11 @@ export WASI_SYSROOT=/opt/wasi-sysroot-24.0 # add to your shell profile
|
|
|
74
75
|
`/usr/lib/wasi-sysroot`, `/opt/wasi-sdk/share/wasi-sysroot` and
|
|
75
76
|
`/usr/share/wasi-sysroot`, and for `libclang_rt.builtins-wasm32.a` in clang's
|
|
76
77
|
resource directory, next to the sysroot (as the tarball above unpacks it),
|
|
77
|
-
in `<sysroot>/lib/wasm32-wasi/`, or at `WASI_BUILTINS=<file>`.
|
|
78
|
+
in `<sysroot>/lib/wasm32-wasi/`, or at `WASI_BUILTINS=<file>`.
|
|
79
|
+
|
|
80
|
+
The packaged wasi-libc predates wasi-libc's `realpath`, so a module linked
|
|
81
|
+
against it answers `realpathSync` with `null` for every path. Use wasi-sdk's
|
|
82
|
+
sysroot (22 or later) when a program needs `realpathSync` to resolve. Then:
|
|
78
83
|
|
|
79
84
|
```bash
|
|
80
85
|
nish examples/argv.ts --link build/argv.wasm --profile wasi
|
|
@@ -218,7 +223,8 @@ routes are
|
|
|
218
223
|
# in the glibc container, where build/nish runs
|
|
219
224
|
build/nish app.ts -o app.ll
|
|
220
225
|
# 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 -
|
|
226
|
+
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
227
|
+
runtime/runtime-host.c runtime/runtime-net.c -lm -o app
|
|
222
228
|
```
|
|
223
229
|
|
|
224
230
|
`--link`ing inside the container is the mistake to avoid: it shells out to the
|
|
@@ -285,21 +291,20 @@ to the machine that built it.
|
|
|
285
291
|
|
|
286
292
|
The last column is there because a release is a past event and a workflow is
|
|
287
293
|
not. `release.yml` builds all four, but a release already published cannot grow
|
|
288
|
-
an asset: **v0.
|
|
294
|
+
an asset: **the releases before v0.4.0 attach `x86_64-linux` only**. Those
|
|
289
295
|
versions are not prose — they are `attachedSince` in
|
|
290
296
|
[`.github/seed-targets.json`](https://github.com/amritk/nish/blob/main/.github/seed-targets.json),
|
|
291
297
|
the same file the release workflow builds its matrix from, so this table and
|
|
292
298
|
the assets cannot drift apart without a test failing.
|
|
293
299
|
|
|
294
|
-
**The three
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
than a green tick. `scripts/verify-binaries.sh`'s header is the long version.
|
|
300
|
+
**The three newer rows say v0.4.0 rather than v0.3.0 on purpose.** Nothing in
|
|
301
|
+
this repository had run on macOS or on ARM Linux before, and a row that has
|
|
302
|
+
never run is not something to put in front of a release: `release` needs the
|
|
303
|
+
whole matrix, so one red row blocks the publish. So they waited until a run on
|
|
304
|
+
their own hardware had exercised them. The two macOS rows waited on a second
|
|
305
|
+
thing as well: the bootstrap's `stage3 == stage2` check did not hold as a raw
|
|
306
|
+
byte comparison under `ld64` until both stages were linked at one output path.
|
|
307
|
+
`scripts/verify-binaries.sh`'s header is the long version.
|
|
303
308
|
|
|
304
309
|
Check [the releases page](https://github.com/amritk/nish/releases/latest) for
|
|
305
310
|
what a given version actually carries; on a platform whose row has not shipped
|
|
@@ -307,15 +312,15 @@ yet, take the npm package above, or build from source below.
|
|
|
307
312
|
|
|
308
313
|
```bash
|
|
309
314
|
# pick the row above that matches `uname -s` and `uname -m`
|
|
310
|
-
curl -LO https://github.com/amritk/nish/releases/download/v0.
|
|
311
|
-
tar -xzf nish-0.
|
|
312
|
-
nish-0.
|
|
315
|
+
curl -LO https://github.com/amritk/nish/releases/download/v0.16.0/nish-0.16.0-x86_64-linux.tar.gz
|
|
316
|
+
tar -xzf nish-0.16.0-x86_64-linux.tar.gz
|
|
317
|
+
nish-0.16.0-x86_64-linux/bin/nish --version
|
|
313
318
|
```
|
|
314
319
|
|
|
315
320
|
Both URLs name the version rather than using GitHub's version-neutral
|
|
316
321
|
`/releases/latest/download/` form, because that form needs the asset's exact
|
|
317
|
-
file name and every asset name carries the version in it.
|
|
318
|
-
|
|
322
|
+
file name and every asset name carries the version in it. The example names
|
|
323
|
+
v0.16.0; v0.1.1 was the first release with a binary attached.
|
|
319
324
|
The `v0.1.0` tag exists but has no release behind it and no assets, so every
|
|
320
325
|
`v0.1.0` download URL is a 404 — the tag was pushed by a workflow, and GitHub
|
|
321
326
|
raises no event for that, so nothing ever built it
|
|
@@ -333,8 +338,7 @@ find `scripts/build.sh`.
|
|
|
333
338
|
|
|
334
339
|
It still needs `clang` and `lld` on `PATH` for `--link` (§1), because linking
|
|
335
340
|
is the C toolchain's job, not the compiler's; what it does not need is Node.
|
|
336
|
-
|
|
337
|
-
`.tgz` above or build from a checkout.
|
|
341
|
+
On a platform outside the table, build from a checkout.
|
|
338
342
|
|
|
339
343
|
From a checkout:
|
|
340
344
|
|
|
@@ -354,7 +358,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
|
|
|
354
358
|
skips the download. §2a has what the build does with it.
|
|
355
359
|
|
|
356
360
|
The published package ships `bin/` (the `nish` command, which is a launcher),
|
|
357
|
-
`runtime/` (the C runtime —
|
|
361
|
+
`runtime/` (the C runtime — five translation units and their header),
|
|
358
362
|
`scripts/build.sh` (the link pipeline) and `std/` (the standard library). The
|
|
359
363
|
compiler itself is not in it: it arrives as one prebuilt
|
|
360
364
|
`@amritk/nish-<asset>` package per platform, and `bin/nish` hands over to
|
|
@@ -364,22 +368,27 @@ working directory — and the binary runs from inside its own platform package
|
|
|
364
368
|
for exactly that reason.
|
|
365
369
|
|
|
366
370
|
Building the IR yourself rather than through `--link` means naming the runtime
|
|
367
|
-
on the `clang` line, and it is
|
|
371
|
+
on the `clang` line, and it is five files:
|
|
368
372
|
|
|
369
373
|
```bash
|
|
370
|
-
clang app.ll runtime/runtime.c runtime/runtime-os.c -
|
|
374
|
+
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
375
|
+
runtime/runtime-host.c runtime/runtime-net.c -lm -o app
|
|
371
376
|
```
|
|
372
377
|
|
|
373
|
-
`runtime.c` is the
|
|
374
|
-
number formatting, the panics — and `runtime-os.c`
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
`runtime.c` is the part every program touches — the arena, strings, arrays,
|
|
379
|
+
number formatting, the panics — and `runtime-os.c` wraps the system calls:
|
|
380
|
+
files, directories, subprocesses, `getenv`, the monotonic clock.
|
|
381
|
+
`runtime-parallel.c` divides work across threads for `nish/threads` (a
|
|
382
|
+
program built with `--threads`, as every importer of `nish/threads` is, also
|
|
383
|
+
needs `-DNISH_THREADS=1 -pthread` on the line), `runtime-host.c` holds the
|
|
384
|
+
wall clock, entropy, file times and signals, and `runtime-net.c` the sockets
|
|
385
|
+
of `nish:net`. They are separate so that each
|
|
386
|
+
carries its own measured size ceiling ([docs/wp7-runtime.md](wp7-runtime.md));
|
|
387
|
+
nothing in the core calls into the others, so a line that names only
|
|
388
|
+
`runtime.c` and `runtime-os.c` still links a program that uses none of the
|
|
389
|
+
other three. `scripts/build.sh` compiles the other four beside any
|
|
390
|
+
`runtime.c` it is handed, so a build that goes through it — every `--link`,
|
|
391
|
+
and every `--profile` recipe in these documents — needs to name only the one.
|
|
383
392
|
|
|
384
393
|
## 2a. What `npm run build` does
|
|
385
394
|
|
|
@@ -402,7 +411,7 @@ release is expected to move it. `--stages 1` stops one link sooner.
|
|
|
402
411
|
`build/nish` is then the compiler you run: it takes the same `-o`, `--link`
|
|
403
412
|
and `--profile` spellings as an installed `nish`, because it is the same
|
|
404
413
|
program, and makes every directory in the way of the IR, a sidecar or the
|
|
405
|
-
binary itself. It looks for `scripts/build.sh` and the
|
|
414
|
+
binary itself. It looks for `scripts/build.sh` and the `runtime/*.c` files
|
|
406
415
|
one level up from wherever it was invoked (and one level up from the file a
|
|
407
416
|
symbolic link to it names), so it wants a checkout or an installed package
|
|
408
417
|
around it the way `nish` does. It falls back to the working directory only
|
package/bin/nish
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime/nish.d.ts
CHANGED
|
@@ -112,9 +112,10 @@ declare function Err<T, E>(error: E): Result<T, E>;
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
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
|
|
116
|
-
* `"ESNext.Disposable"` in its `lib`. `nish` itself takes
|
|
117
|
-
* `scope()`, and `[Symbol.dispose]` only in
|
|
115
|
+
* §5): declared here so that a program using `nish/threads`'s scope or
|
|
116
|
+
* `arena()` needs no `"ESNext.Disposable"` in its `lib`. `nish` itself takes
|
|
117
|
+
* `using` only for a `scope()` or an `arena()`, and `[Symbol.dispose]` only in
|
|
118
|
+
* `nish/threads`.
|
|
118
119
|
*/
|
|
119
120
|
interface SymbolConstructor {
|
|
120
121
|
readonly dispose: unique symbol;
|
|
@@ -172,6 +173,39 @@ declare function bitsToF64(bits: i64): f64;
|
|
|
172
173
|
declare function ctSelect<T extends u32 | u64>(mask: T, a: T, b: T): T;
|
|
173
174
|
/** All-ones of the operands' type when `a === b`, zero otherwise, without a branch. */
|
|
174
175
|
declare function ctEq<T extends u32 | u64>(a: T, b: T): T;
|
|
176
|
+
/**
|
|
177
|
+
* Every byte of `bytes` set to zero, in stores neither `-O2` nor `-flto` may
|
|
178
|
+
* drop as dead (#385): how a key, a secret scalar or another secret is cleared
|
|
179
|
+
* once it is used. A statement.
|
|
180
|
+
*/
|
|
181
|
+
declare function secureZero(bytes: u8[]): void;
|
|
182
|
+
/**
|
|
183
|
+
* One `lstat` of `path`, so a symbolic link answers for itself: the owner's uid
|
|
184
|
+
* in the high 32 bits and `st_mode` in the low 32, or -1 when the path does not
|
|
185
|
+
* resolve (#386).
|
|
186
|
+
*/
|
|
187
|
+
declare function lstatOwnerModeSync(path: string): i64;
|
|
188
|
+
/** The effective user id, the one to compare an owner from `lstatOwnerModeSync` with. */
|
|
189
|
+
declare function geteuid(): i64;
|
|
190
|
+
/** Whether the real user may run `path`: `access(path, X_OK)`. */
|
|
191
|
+
declare function isExecutableSync(path: string): boolean;
|
|
192
|
+
|
|
193
|
+
// ---- Unsafe (`nish:unsafe`) --------------------------------------------------
|
|
194
|
+
//
|
|
195
|
+
// Declared as globals so `tsc` reads a program that imports them, as every
|
|
196
|
+
// `nish:` export is, but `nish` refuses a call to any of the five that is not
|
|
197
|
+
// imported from `nish:unsafe`: the import is the module's visible opt-in.
|
|
198
|
+
|
|
199
|
+
/** `xs[i]` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
200
|
+
declare function uncheckedGet<T extends number>(xs: readonly T[], i: i32): T;
|
|
201
|
+
/** `xs[i] = v` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
202
|
+
declare function uncheckedSet<T extends number>(xs: T[], i: i32, v: T): void;
|
|
203
|
+
/** `a + b` wrapped to the operands' width, `i32` or `i64`: defined for every pair. */
|
|
204
|
+
declare function wrappingAdd<T extends i32 | i64>(a: T, b: T): T;
|
|
205
|
+
/** `a - b` wrapped to the operands' width. */
|
|
206
|
+
declare function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
|
|
207
|
+
/** `a * b` wrapped to the operands' width. */
|
|
208
|
+
declare function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
|
|
175
209
|
|
|
176
210
|
// ---- Streams and files (globals: Nish has no package resolution) ---------
|
|
177
211
|
|
|
@@ -200,7 +234,14 @@ declare function readFileSyncOrNull(path: string): string | null;
|
|
|
200
234
|
* `readFileSyncOrNull` answers `null` for.
|
|
201
235
|
*/
|
|
202
236
|
declare function readFileBytesSync(path: string): u8[] | null;
|
|
237
|
+
/**
|
|
238
|
+
* The file's contents replaced by `data`, created at 0644 when missing. A
|
|
239
|
+
* symbolic link as the last component of the path is refused rather than
|
|
240
|
+
* followed, natively and under Node alike, and a path that cannot be written
|
|
241
|
+
* prints `nish: cannot write <path>` and exits 1.
|
|
242
|
+
*/
|
|
203
243
|
declare function writeFileSync(path: string, data: string): void;
|
|
244
|
+
/** `data` added at the end of the file, with the same refusal of a symbolic link. */
|
|
204
245
|
declare function appendFileSync(path: string, data: string): void;
|
|
205
246
|
/** One directory, not recursive; whether a directory is there afterwards. */
|
|
206
247
|
declare function mkdirSync(path: string): boolean;
|
|
@@ -221,7 +262,8 @@ declare function realpathSync(path: string): string | null;
|
|
|
221
262
|
declare function spawnSync(argv: string[]): number;
|
|
222
263
|
/**
|
|
223
264
|
* The same run with each non-empty path receiving that stream, created or
|
|
224
|
-
* truncated; an empty string leaves that stream inherited.
|
|
265
|
+
* truncated; an empty string leaves that stream inherited. A symbolic link at
|
|
266
|
+
* either path is refused, as `writeFileSync` refuses one, and the answer is -1.
|
|
225
267
|
*/
|
|
226
268
|
declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
227
269
|
/** One environment variable, or `null` when it is unset (an empty value is a set variable). */
|
|
@@ -252,9 +294,10 @@ declare function readSignal(fd: i32): i32;
|
|
|
252
294
|
//
|
|
253
295
|
// Every call answers an `i32`: `>= 0` on success, a negative errno otherwise, in
|
|
254
296
|
// Linux's numbering on every platform for -11 (would block), -95, -32, -104,
|
|
255
|
-
// -98 and -22. An address is 18 bytes of a
|
|
256
|
-
// `::ffff:a.b.c.d`), then the port,
|
|
257
|
-
// close-on-exec. No reading under
|
|
297
|
+
// -98, -111 (refused), -110 (timed out) and -22. An address is 18 bytes of a
|
|
298
|
+
// `u8[]`: 16 of IPv6 address (IPv4 as `::ffff:a.b.c.d`), then the port,
|
|
299
|
+
// big-endian. Every socket is non-blocking and close-on-exec. No reading under
|
|
300
|
+
// Node, where each throws.
|
|
258
301
|
|
|
259
302
|
/** Write the address form of a numeric `host` and `port` into `out`: 0, or -22. */
|
|
260
303
|
declare function netAddress(out: u8[], host: string, port: i32): i32;
|
|
@@ -272,6 +315,10 @@ declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
|
|
|
272
315
|
declare function netShutdown(fd: i32, how: i32): i32;
|
|
273
316
|
/** Close the descriptor. */
|
|
274
317
|
declare function netClose(fd: i32): i32;
|
|
318
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
319
|
+
declare function tcpConnect(addr: readonly u8[]): i32;
|
|
320
|
+
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
321
|
+
declare function connectResult(fd: i32): i32;
|
|
275
322
|
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
276
323
|
declare function udpBind(host: string, port: i32, flags: i32): i32;
|
|
277
324
|
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
@@ -325,7 +372,9 @@ declare module "nish:fs" {
|
|
|
325
372
|
export function readFileSync(path: string): string;
|
|
326
373
|
export function readFileSyncOrNull(path: string): string | null;
|
|
327
374
|
export function readFileBytesSync(path: string): u8[] | null;
|
|
375
|
+
/** Refuses a symbolic link as the last component of the path, as the global does. */
|
|
328
376
|
export function writeFileSync(path: string, data: string): void;
|
|
377
|
+
/** Refuses a symbolic link as the last component of the path, as the global does. */
|
|
329
378
|
export function appendFileSync(path: string, data: string): void;
|
|
330
379
|
/** `true` when the directory was created, `false` when it already existed. */
|
|
331
380
|
export function mkdirSync(path: string): boolean;
|
|
@@ -347,7 +396,8 @@ declare module "nish:process" {
|
|
|
347
396
|
export function spawnSync(argv: string[]): number;
|
|
348
397
|
/**
|
|
349
398
|
* The same run with each non-empty path receiving that stream, created or
|
|
350
|
-
* truncated; an empty string leaves that stream inherited.
|
|
399
|
+
* truncated; an empty string leaves that stream inherited. A symbolic link
|
|
400
|
+
* at either path is refused, and the answer is -1.
|
|
351
401
|
*/
|
|
352
402
|
export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
353
403
|
/**
|
|
@@ -383,6 +433,10 @@ declare module "nish:net" {
|
|
|
383
433
|
/** Shut the read side (0), the write side (1) or both (2). */
|
|
384
434
|
export function netShutdown(fd: i32, how: i32): i32;
|
|
385
435
|
export function netClose(fd: i32): i32;
|
|
436
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
437
|
+
export function tcpConnect(addr: readonly u8[]): i32;
|
|
438
|
+
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
439
|
+
export function connectResult(fd: i32): i32;
|
|
386
440
|
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
387
441
|
export function udpBind(host: string, port: i32, flags: i32): i32;
|
|
388
442
|
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
@@ -409,6 +463,19 @@ declare module "nish:net" {
|
|
|
409
463
|
export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
|
|
410
464
|
}
|
|
411
465
|
|
|
466
|
+
declare module "nish:unsafe" {
|
|
467
|
+
/** `xs[i]` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
468
|
+
export function uncheckedGet<T extends number>(xs: readonly T[], i: i32): T;
|
|
469
|
+
/** `xs[i] = v` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
470
|
+
export function uncheckedSet<T extends number>(xs: T[], i: i32, v: T): void;
|
|
471
|
+
/** `a + b` wrapped to the operands' width, `i32` or `i64`. */
|
|
472
|
+
export function wrappingAdd<T extends i32 | i64>(a: T, b: T): T;
|
|
473
|
+
/** `a - b` wrapped to the operands' width. */
|
|
474
|
+
export function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
|
|
475
|
+
/** `a * b` wrapped to the operands' width. */
|
|
476
|
+
export function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
|
|
477
|
+
}
|
|
478
|
+
|
|
412
479
|
declare module "nish:io" {
|
|
413
480
|
/** `s` to stdout with no trailing newline and no conversion. */
|
|
414
481
|
export function write(s: string): void;
|
|
@@ -418,6 +485,39 @@ declare module "nish:io" {
|
|
|
418
485
|
export function panic(message: string): never;
|
|
419
486
|
}
|
|
420
487
|
|
|
488
|
+
// `nish:secret` has source behind it, `std/secret.ts`, but a program imports it
|
|
489
|
+
// by this name, so `tsc` reads these declarations rather than that file. The
|
|
490
|
+
// class is declared with a private constructor and a private field: `tsc` then
|
|
491
|
+
// refuses `new Secret(...)`, `s.value` and a structural look-alike, as `nish`
|
|
492
|
+
// does. Everything else `nish` refuses about a `Secret` — printing it,
|
|
493
|
+
// comparing it, branching on it, dropping it unwiped, an `expose` whose
|
|
494
|
+
// function does I/O — is flow, which `tsc` cannot see
|
|
495
|
+
// (docs/LANGUAGE.md -> Secrets).
|
|
496
|
+
declare module "nish:secret" {
|
|
497
|
+
/**
|
|
498
|
+
* Key material: an array of integers or a record of integer fields, held
|
|
499
|
+
* opaque. Made by `secret`, read only through `expose`, and wiped or
|
|
500
|
+
* returned by the function that made it.
|
|
501
|
+
*/
|
|
502
|
+
export class Secret<T> {
|
|
503
|
+
private constructor();
|
|
504
|
+
private readonly value: T;
|
|
505
|
+
}
|
|
506
|
+
/** `value`, wrapped. A local handed in is moved: it may not be read again. */
|
|
507
|
+
export function secret<T>(value: T): Secret<T>;
|
|
508
|
+
/**
|
|
509
|
+
* `f(value)`. `f` reaches no I/O and no C, keeps nothing of `value`, and
|
|
510
|
+
* declares a return type that holds no `Secret`: that type is what leaves.
|
|
511
|
+
*/
|
|
512
|
+
export function expose<T, R>(s: Secret<T>, f: (value: T) => R): R;
|
|
513
|
+
/** `f(value, arg)`, under `expose`'s rules; `f` may not write `arg` either. */
|
|
514
|
+
export function exposeWith<T, A, R>(s: Secret<T>, arg: A, f: (value: T, arg: A) => R): R;
|
|
515
|
+
/** Zero the value's whole storage, with a store the optimiser may not remove. */
|
|
516
|
+
export function wipe<T>(target: Secret<T>): void;
|
|
517
|
+
/** Zero an array or record of integers that `expose`'s function holds, likewise. */
|
|
518
|
+
export function wipe(target: object): void;
|
|
519
|
+
}
|
|
520
|
+
|
|
421
521
|
// ---- Arena (docs/LANGUAGE.md -> Arena) ---------------------------------------
|
|
422
522
|
|
|
423
523
|
declare const Arena: {
|
|
@@ -431,6 +531,15 @@ declare const Arena: {
|
|
|
431
531
|
used(): i64;
|
|
432
532
|
};
|
|
433
533
|
|
|
534
|
+
/**
|
|
535
|
+
* `using a = arena()` (docs/LANGUAGE.md -> Arena): everything the block
|
|
536
|
+
* allocates after this declaration is released when the block ends, on every
|
|
537
|
+
* exit, and the compiler refuses the block if anything allocated in it could
|
|
538
|
+
* outlive it. Only a `using` initialiser, and the binding is never read; under
|
|
539
|
+
* Node there is no arena and the disposal does nothing.
|
|
540
|
+
*/
|
|
541
|
+
declare function arena(): Disposable;
|
|
542
|
+
|
|
434
543
|
/**
|
|
435
544
|
* `CPtr` (WP27 S2): the address a `declare function` hands back, opaque and
|
|
436
545
|
* eight bytes wide. `docs/LANGUAGE.md` has the rules — it may be written in a
|
package/runtime/nish.h
CHANGED
|
@@ -225,6 +225,11 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
|
225
225
|
* elements themselves — which is why the checker refuses to let a program hold
|
|
226
226
|
* a pointer to one across a `push` (WP15 section 2a). */
|
|
227
227
|
void nish_array_grow(nish_array *a, uint64_t elem_size);
|
|
228
|
+
/* `secureZero(bytes)` (#385): sets every byte of `bytes` to zero through
|
|
229
|
+
* `volatile` stores, out of line, so that neither `-O2` nor `-flto` can drop
|
|
230
|
+
* them as dead when the buffer's life ends straight after the call. For
|
|
231
|
+
* secret material -- a private key, a secret scalar -- once it is used. */
|
|
232
|
+
void nish_wipe(nish_array *bytes);
|
|
228
233
|
void nish_panic_index(uint64_t idx, uint64_t len);
|
|
229
234
|
/* The failed range check of `s.slice(start, end)` (WP15 section 4): prints the
|
|
230
235
|
half-open interval that was asked for and the byte length it left, then
|
|
@@ -313,7 +318,12 @@ nish_str *nish_getenv(const nish_str *name);
|
|
|
313
318
|
* but the last must exist for POSIX `realpath` to answer at all.
|
|
314
319
|
*
|
|
315
320
|
* `path` is read and never retained. The answer is a fresh arena string, so
|
|
316
|
-
* it outlives the call the way every other string here does.
|
|
321
|
+
* it outlives the call the way every other string here does.
|
|
322
|
+
*
|
|
323
|
+
* Under the wasi profile the answer is the sysroot's `realpath`, which
|
|
324
|
+
* wasi-sdk 22 and later have. A wasi-libc without one (the one Ubuntu and
|
|
325
|
+
* Debian package) answers NULL for every path, which a caller already
|
|
326
|
+
* handles. */
|
|
317
327
|
nish_str *nish_realpath(const nish_str *path);
|
|
318
328
|
|
|
319
329
|
/* ---- What machine this is (WP14 §7a) ------------------------------------
|
|
@@ -361,8 +371,9 @@ double nish_stat_mtime(const nish_str *path);
|
|
|
361
371
|
* or a read that fails. */
|
|
362
372
|
int32_t nish_signal_fd(void);
|
|
363
373
|
int32_t nish_read_signal(int32_t fd);
|
|
364
|
-
/* Who owns a path, and whether it runs (docs/security/runtime.md, RT-9;
|
|
365
|
-
*
|
|
374
|
+
/* Who owns a path, and whether it runs (docs/security/runtime.md, RT-9; the
|
|
375
|
+
* builtins `lstatOwnerModeSync`, `geteuid` and `isExecutableSync`, #386).
|
|
376
|
+
* `nish_lstat_owner_mode(path)`: `lstat`, so a
|
|
366
377
|
* symbolic link answers for itself, as the owner's uid in the high 32 bits
|
|
367
378
|
* and `st_mode` in the low 32, or exactly -1 when the path does not resolve.
|
|
368
379
|
* `nish_euid()`: the effective uid. `nish_is_executable(path)`:
|
|
@@ -375,10 +386,11 @@ bool nish_is_executable(const nish_str *path);
|
|
|
375
386
|
* Every call answers an `int32_t`: `>= 0` on success (a descriptor, a byte
|
|
376
387
|
* count, 0) and a negative errno otherwise, in Linux's numbering on every
|
|
377
388
|
* platform for -11 (would block), -95 (unsupported), -32 (the peer is gone),
|
|
378
|
-
* -104 (reset), -98 (address in use)
|
|
379
|
-
* is non-blocking and close-on-exec. An
|
|
380
|
-
* (element type `uint8_t`): 16 of IPv6
|
|
381
|
-
* then the port, big-endian; a shorter
|
|
389
|
+
* -104 (reset), -98 (address in use), -111 (refused), -110 (timed out) and
|
|
390
|
+
* -22 (a bad argument). Every socket is non-blocking and close-on-exec. An
|
|
391
|
+
* address is 18 bytes of a `u8[]` (element type `uint8_t`): 16 of IPv6
|
|
392
|
+
* address, IPv4 as `::ffff:a.b.c.d`, then the port, big-endian; a shorter
|
|
393
|
+
* array is -22. Nothing allocates.
|
|
382
394
|
*
|
|
383
395
|
* `nish_net_address(out, host, port)`: the form of a numeric `host`.
|
|
384
396
|
* `nish_net_local_port(fd)`: the port a socket is bound to.
|
|
@@ -389,6 +401,10 @@ bool nish_is_executable(const nish_str *path);
|
|
|
389
401
|
* len)` of `buf`, which the compiled call has range-checked; a write to a
|
|
390
402
|
* gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
|
|
391
403
|
* write, 2 both. `nish_net_close(fd)`.
|
|
404
|
+
* `nish_tcp_connect(addr)`: a new socket connecting to `addr`, answered at
|
|
405
|
+
* once; its `EINPROGRESS` is not a failure, and any other closes it.
|
|
406
|
+
* `nish_connect_result(fd)`: `SO_ERROR` once the socket is writable, 0 when
|
|
407
|
+
* the connection is made.
|
|
392
408
|
*
|
|
393
409
|
* UDP: `nish_udp_bind(host, port, flags)` binds a datagram socket, flag 1
|
|
394
410
|
* `SO_REUSEPORT` and flag 2 `UDP_GRO` (-95 on Darwin). `nish_udp_send_to(fd,
|
|
@@ -418,6 +434,8 @@ int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
|
|
|
418
434
|
int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
|
|
419
435
|
int32_t nish_net_shutdown(int32_t fd, int32_t how);
|
|
420
436
|
int32_t nish_net_close(int32_t fd);
|
|
437
|
+
int32_t nish_tcp_connect(const nish_array *addr);
|
|
438
|
+
int32_t nish_connect_result(int32_t fd);
|
|
421
439
|
int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);
|
|
422
440
|
int32_t nish_udp_send_to(int32_t fd, const nish_array *buf, int64_t off, int64_t len, const nish_array *to,
|
|
423
441
|
int32_t segment, int32_t ecn);
|
|
@@ -439,6 +457,11 @@ double nish_parse_number(const nish_str *s, int32_t mode);
|
|
|
439
457
|
/* Checked integer division (Rust semantics): the failed-check path. */
|
|
440
458
|
void nish_panic_div(bool by_zero);
|
|
441
459
|
|
|
460
|
+
/* Checked signed arithmetic: the failed-check path. `op` is 0 add, 1
|
|
461
|
+
* subtract, 2 multiply, 3 negate; prints "attempt to <op> with overflow" and
|
|
462
|
+
* exits 1. */
|
|
463
|
+
void nish_panic_overflow(uint32_t op);
|
|
464
|
+
|
|
442
465
|
/* ---- Parallel work (WP20 T1 / wp29 stage P1), runtime/runtime-parallel.c ----
|
|
443
466
|
*
|
|
444
467
|
* One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
|
package/runtime/nish.mjs
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
* That is enough for a real and useful subset — **f64 mode**, where JavaScript's
|
|
17
17
|
* `+ - * / %` on doubles *are* `fadd/fsub/fmul/fdiv/frem` and `Math.*` are the
|
|
18
18
|
* same IEEE operations, so the arithmetic needs no help. It is not enough for
|
|
19
|
-
* i32 mode, and it never will be: `number` is a
|
|
20
|
-
* and every operator would have to change.
|
|
19
|
+
* i32 mode, and it never will be: `number` is a 32-bit integer there, whose
|
|
20
|
+
* signed overflow panics, and every operator would have to change.
|
|
21
21
|
*
|
|
22
22
|
* `docs/RUN_UNDER_NODE.md` states the overlap and the whole list of what stays
|
|
23
23
|
* divergent. The short version, because it belongs next to the code too:
|
|
24
24
|
*
|
|
25
|
-
* - **i32 mode is out.**
|
|
26
|
-
* panic are all operator-level and cannot be reached from here.
|
|
25
|
+
* - **i32 mode is out.** The overflow panic, truncating `/`, and the
|
|
26
|
+
* divide-by-zero panic are all operator-level and cannot be reached from here.
|
|
27
27
|
* - **`s.length` is UTF-16 units under Node** and UTF-8 bytes natively, and
|
|
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
|
|
@@ -109,6 +109,8 @@ for (const name of [
|
|
|
109
109
|
"netWrite",
|
|
110
110
|
"netShutdown",
|
|
111
111
|
"netClose",
|
|
112
|
+
"tcpConnect",
|
|
113
|
+
"connectResult",
|
|
112
114
|
"udpBind",
|
|
113
115
|
"udpSendTo",
|
|
114
116
|
"udpRecvFrom",
|
|
@@ -174,6 +176,49 @@ provide("bitsToF64", shim.bitsToF64);
|
|
|
174
176
|
// agree with a native run; the timing does not, and is not claimed here.
|
|
175
177
|
provide("ctSelect", shim.ctSelect);
|
|
176
178
|
provide("ctEq", shim.ctEq);
|
|
179
|
+
// `secureZero` (#385): the bytes are zero afterwards here too, though what a
|
|
180
|
+
// collector may have copied before is out of reach.
|
|
181
|
+
provide("secureZero", shim.secureZero);
|
|
182
|
+
// The RT-9 owner checks (#386): one `lstat`, the effective uid and
|
|
183
|
+
// `access(X_OK)`, answered as `runtime-host.c` answers them.
|
|
184
|
+
provide("lstatOwnerModeSync", shim.lstatOwnerModeSync);
|
|
185
|
+
provide("geteuid", shim.geteuid);
|
|
186
|
+
provide("isExecutableSync", shim.isExecutableSync);
|
|
187
|
+
|
|
188
|
+
// `nish:unsafe`. The element access is JavaScript's own, which never checks
|
|
189
|
+
// anything; the wraps are the two's-complement ones the native instructions
|
|
190
|
+
// perform: `| 0` and `Math.imul` at `i32`, and `BigInt.asIntN(64, ...)` on
|
|
191
|
+
// the BigInt an `i64` is here. Pure functions of their operands, so a program
|
|
192
|
+
// that stays in range agrees with a native run.
|
|
193
|
+
const wrapAt = (a, b, wide, narrow) => (typeof a === "bigint" ? BigInt.asIntN(64, wide(a, b)) : narrow(a, b));
|
|
194
|
+
provide("uncheckedGet", (xs, i) => xs[i]);
|
|
195
|
+
provide("uncheckedSet", (xs, i, v) => {
|
|
196
|
+
xs[i] = v;
|
|
197
|
+
});
|
|
198
|
+
provide("wrappingAdd", (a, b) =>
|
|
199
|
+
wrapAt(
|
|
200
|
+
a,
|
|
201
|
+
b,
|
|
202
|
+
(x, y) => x + y,
|
|
203
|
+
(x, y) => (x + y) | 0
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
provide("wrappingSub", (a, b) =>
|
|
207
|
+
wrapAt(
|
|
208
|
+
a,
|
|
209
|
+
b,
|
|
210
|
+
(x, y) => x - y,
|
|
211
|
+
(x, y) => (x - y) | 0
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
provide("wrappingMul", (a, b) =>
|
|
215
|
+
wrapAt(
|
|
216
|
+
a,
|
|
217
|
+
b,
|
|
218
|
+
(x, y) => x * y,
|
|
219
|
+
(x, y) => Math.imul(x, y)
|
|
220
|
+
)
|
|
221
|
+
);
|
|
177
222
|
|
|
178
223
|
// `Result`. Everything works but `orReturn`, which needs the caller's control
|
|
179
224
|
// flow and therefore the rewriter; see the header.
|
|
@@ -200,15 +245,34 @@ provide("Arena", {
|
|
|
200
245
|
reset: shim.arenaReset,
|
|
201
246
|
used: shim.arenaUsed,
|
|
202
247
|
});
|
|
248
|
+
// `using a = arena()` releases nothing here either: what the block allocated
|
|
249
|
+
// is the garbage collector's, and the disposal is a no-op.
|
|
250
|
+
provide("arena", shim.arena);
|
|
203
251
|
|
|
204
252
|
// The standard library. A program imports it as `nish/<module>`, which the
|
|
205
253
|
// compiler resolves to `std/<module>.ts` beside itself; this does the same for
|
|
206
254
|
// Node, relative to this file rather than to the program, so the same
|
|
207
255
|
// specifier works from any directory. Only the `nish/` package is answered
|
|
208
256
|
// here, and every other specifier goes to Node as it was written.
|
|
257
|
+
//
|
|
258
|
+
// `nish:secret` is answered here as well, because the standard library imports
|
|
259
|
+
// it (`std/crypto/p256.ts` and its neighbours): its names are
|
|
260
|
+
// `runtime/shim.mjs`'s, where a `Secret` is a plain wrapper and `wipe`
|
|
261
|
+
// zero-fills it. `nish:unsafe` is answered by a module that hands back the
|
|
262
|
+
// globals installed above: its functions are globals of this module either
|
|
263
|
+
// way, and the import is what the compiler requires a program to write.
|
|
264
|
+
const UNSAFE_EXPORTS = ["uncheckedGet", "uncheckedSet", "wrappingAdd", "wrappingSub", "wrappingMul"];
|
|
265
|
+
const unsafeSource = UNSAFE_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
209
266
|
registerHooks({
|
|
210
|
-
resolve: (specifier, context, next) =>
|
|
211
|
-
specifier
|
|
267
|
+
resolve: (specifier, context, next) => {
|
|
268
|
+
if (specifier === "nish:secret") {
|
|
269
|
+
return next(new URL("./shim.mjs", import.meta.url).href, context);
|
|
270
|
+
}
|
|
271
|
+
if (specifier === "nish:unsafe") {
|
|
272
|
+
return { url: `data:text/javascript,${encodeURIComponent(unsafeSource)}`, shortCircuit: true };
|
|
273
|
+
}
|
|
274
|
+
return specifier.startsWith("nish/")
|
|
212
275
|
? next(new URL(`../std/${specifier.slice("nish/".length)}.ts`, import.meta.url).href, context)
|
|
213
|
-
: next(specifier, context)
|
|
276
|
+
: next(specifier, context);
|
|
277
|
+
},
|
|
214
278
|
});
|