@amritk/nish-aarch64-darwin 0.34.0 → 0.35.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
@@ -54,6 +54,8 @@ export PATH="$(brew --prefix llvm@18)/bin:$PATH" # add to your shell profile
54
54
  Xcode's `clang` (`xcode-select --install`) also works for native binaries;
55
55
  the Homebrew LLVM is needed for the `wasm` and `wasi` profiles (`wasm-ld`).
56
56
 
57
+ <a id="wasi-optional-for---profile-wasi" style="scroll-margin-top: 65px"></a>
58
+
57
59
  ### WASI (optional, for `--profile wasi`)
58
60
 
59
61
  The `wasi` profile links the runtime against wasi-libc so that whole programs
@@ -105,7 +107,7 @@ of its own architecture by the release workflow before the release existed, and
105
107
  installing is a download and an unpack.
106
108
 
107
109
  Through npm, which is also how an Nish *program* resolves its dependencies
108
- ([wp21-packages.md](wp21-packages.md)), so this is the one to pick if you want
110
+ ([wp21-packages.md](https://github.com/amritk/nish/blob/main/docs/wp21-packages.md)), so this is the one to pick if you want
109
111
  the compiler pinned per project in a `package.json`:
110
112
 
111
113
  ```bash
@@ -169,7 +171,7 @@ release also attaches `nish-<version>.spdx.json`, an SPDX 2.3 software bill of
169
171
  materials: every attached file with its SHA-256, the clang, linker, Node.js
170
172
  and seed compiler that built each tarball (recorded by the build row itself),
171
173
  and the vendored code from
172
- [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) that each of them contains.
174
+ [THIRD_PARTY_NOTICES.md](https://github.com/amritk/nish/blob/main/THIRD_PARTY_NOTICES.md) that each of them contains.
173
175
  How these fit the rest of the release's defences is
174
176
  [docs/security/supply-chain.md](security/supply-chain.md).
175
177
 
@@ -198,13 +200,13 @@ never fails over it.
198
200
  > installers above handle it by running the binary through a one-line `exec` of
199
201
  > an absolute path. If you unpack a tarball by hand, invoke it by path
200
202
  > (`nish-<version>-<asset>/bin/nish`) or write that wrapper yourself. The
201
- > underlying defect is [wp19 §5a](wp19-stage0-retirement.md#5a-what-r6-is-waiting-on)
203
+ > underlying defect is [wp19 §5a](https://github.com/amritk/nish/blob/main/docs/wp19-stage0-retirement.md#5a-what-r6-is-waiting-on)
202
204
  > item 4.
203
205
 
204
206
  **The package is `nish` from 0.20.0.** Releases up to 0.18.0 were published
205
207
  as `@amritk/nish` (0.19.0's publish to npm failed), which is deprecated and
206
208
  points here; `npm install -g nish`
207
- replaces it ([docs/wp12-release.md](wp12-release.md#the-npm-name)). Versions
209
+ replaces it ([docs/wp12-release.md](https://github.com/amritk/nish/blob/main/docs/wp12-release.md#the-npm-name)). Versions
208
210
  0.0.0 and 0.0.1 of `nish` are an unrelated 2014 project that held the name
209
211
  before it, so pin `nish@^0.20.0` or later rather than an older range.
210
212
 
@@ -233,9 +235,9 @@ nish: no prebuilt compiler for freebsd/x64
233
235
  Until 0.6.0 it ran the TypeScript compiler that shipped in the same package
234
236
  instead — the same compiler by every test here, about eight times slower, and
235
237
  no C toolchain needed. That compiler was stage0, which was deleted in R6
236
- ([wp19](wp19-stage0-retirement.md)), so there is nothing left in the package to
238
+ ([wp19](https://github.com/amritk/nish/blob/main/docs/wp19-stage0-retirement.md)), so there is nothing left in the package to
237
239
  fall back to. The cost is stated where the rest of that deletion's costs are,
238
- in [wp19 §6](wp19-stage0-retirement.md#6-what-retirement-costs-stated-plainly),
240
+ in [wp19 §6](https://github.com/amritk/nish/blob/main/docs/wp19-stage0-retirement.md#6-what-retirement-costs-stated-plainly),
239
241
  and it is a real one: **musl, FreeBSD and 32-bit platforms have no `npm
240
242
  install` route to a compiler any more.**
241
243
 
@@ -276,7 +278,7 @@ to have on the issue tracker rather than a workaround to discover here.
276
278
  `nish` from 0.20.0; 0.19.0 is on GitHub only, because its npm publish
277
279
  failed. Every release publishes the main package and its platform packages to
278
280
  npm from `release.yml`, by trusted publishing
279
- ([docs/wp12-release.md](wp12-release.md#release-procedure) step 4), so
281
+ ([docs/wp12-release.md](https://github.com/amritk/nish/blob/main/docs/wp12-release.md#release-procedure) step 4), so
280
282
  `npm install -g nish` is the whole install. The rest of this section is
281
283
  for a machine that cannot reach the registry, or a release before 0.10.0.
282
284
 
@@ -358,7 +360,7 @@ v0.16.0; v0.1.1 was the first release with a binary attached.
358
360
  The `v0.1.0` tag exists but has no release behind it and no assets, so every
359
361
  `v0.1.0` download URL is a 404 — the tag was pushed by a workflow, and GitHub
360
362
  raises no event for that, so nothing ever built it
361
- ([docs/wp12-release.md](wp12-release.md#release-procedure) step 2). When a newer
363
+ ([docs/wp12-release.md](https://github.com/amritk/nish/blob/main/docs/wp12-release.md#release-procedure) step 2). When a newer
362
364
  release exists, take its version from
363
365
  [the releases page](https://github.com/amritk/nish/releases/latest).
364
366
 
@@ -435,7 +437,7 @@ over, `runtime-checksum.c` the CRC-32 kernels of `nish/compress/checksum`,
435
437
  `runtime-sandbox.c` the confinement a `--sandbox` build applies, and
436
438
  `runtime-shm.c` the shared-memory regions of `nish/sync/shm`.
437
439
  They are separate so that each carries its own measured size ceiling
438
- ([docs/wp7-runtime.md](wp7-runtime.md)); nothing in the core calls into the
440
+ ([docs/wp7-runtime.md](https://github.com/amritk/nish/blob/main/docs/wp7-runtime.md)); nothing in the core calls into the
439
441
  others, so a line that names only `runtime.c` and `runtime-os.c` still links a
440
442
  program that uses none of the other nine. `scripts/build.sh` compiles the other
441
443
  nine beside any
@@ -451,7 +453,7 @@ beside `-DNISH_THREADS=1`).
451
453
  ## 2a. What `npm run build` does
452
454
 
453
455
  `src/` is the compiler, written in Nish, and it compiles itself
454
- ([docs/wp14-selfhost.md](wp14-selfhost.md)). From a checkout, with clang on
456
+ ([docs/wp14-selfhost.md](https://github.com/amritk/nish/blob/main/docs/wp14-selfhost.md)). From a checkout, with clang on
455
457
  `PATH`, `npm run build` runs `scripts/bootstrap.sh` with the seed from §2:
456
458
 
457
459
  ```bash
@@ -486,7 +488,7 @@ in `src/` and becomes usable inside `src/` from the next release on; CI's
486
488
  `bootstrap` job is what checks that the released seed still builds stage1.
487
489
  Until R6 the seed was a TypeScript compiler, stage0, run under Node; it was
488
490
  deleted once the native one answered every flag it did
489
- ([wp19](wp19-stage0-retirement.md)).
491
+ ([wp19](https://github.com/amritk/nish/blob/main/docs/wp19-stage0-retirement.md)).
490
492
 
491
493
  ## 3. Hello world
492
494
 
@@ -518,7 +520,7 @@ nish hello.ts -o hello.ll
518
520
 
519
521
  Other build profiles: `--profile size` (smallest binary), `--profile debug`
520
522
  (no optimisation, symbols kept). Run `nish --help` for every flag, and
521
- see the [README](../README.md) for the language subset.
523
+ see the [README](https://github.com/amritk/nish/blob/main/README.md) for the language subset.
522
524
 
523
525
  To use a program as a script rather than keep the binary, run it:
524
526
 
@@ -549,6 +551,8 @@ it cannot make private, compiles the runtime itself as it always did; the
549
551
  wasi profile and `scripts/build.sh` called directly never use the cache.
550
552
  Removing the directory is always safe.
551
553
 
554
+ <a id="4-exit-codes" style="scroll-margin-top: 65px"></a>
555
+
552
556
  ## 4. Exit codes
553
557
 
554
558
  | Code | Meaning |
package/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -108,7 +108,9 @@ the difference is a row of [the user guide's table](guide/using-nish/to-typescri
108
108
  or a bug in `nish` worth reporting. A program on `nish:net`, the signal
109
109
  descriptor or a C declaration (NL4010, NL4011), a compound assignment whose
110
110
  index would run twice (NL4012) or a generic whose element type decides its
111
- code (NL4013, NL4014) is refused and has no TypeScript to run.
111
+ code (NL4013, NL4014) is refused and has no TypeScript to run. An `async`
112
+ program is written as Node async/await, whose spawned tasks may print in
113
+ another order than native.
112
114
 
113
115
  Two more surfaces worth knowing: `nish --emit-ast f.ts` prints what was parsed
114
116
  and `nish --emit-checked f.ts` prints the side tables the emitter reads. Both
@@ -140,7 +142,11 @@ everywhere, a template literal instead of `String(n)`.
140
142
  - A module is one `.ts` file. At the top level it may hold **only**
141
143
  declarations: `const` arrows (functions), `class`, `interface`, module
142
144
  `const`s, `type` aliases, numeric `enum`s, and `import`s. There is no
143
- top-level code, so there is no initialisation order to think about.
145
+ top-level code, so there is no initialisation order to think about. The
146
+ one exception is an entry module with no `main`: its top-level statements
147
+ run as `main` (`docs/LANGUAGE.md` → "Top-level code"). Declare every
148
+ function above the first statement, and pass a function what it needs
149
+ rather than naming a variable of the code.
144
150
  - `export const main` lives in the entry module only, takes no parameters, and
145
151
  returns `void` or an `i32`-lowered `number` — the process exit code. Under
146
152
  `--number-mode f64`, declare it `main(): i32`.
@@ -175,12 +181,12 @@ rejects. This table is the highest-value part of the page.
175
181
  | `h.get<i32>(7)` (type argument at a method call) | `Type arguments are not written at a call site in Nish` | `h.get(7)` — a generic method infers like a generic function |
176
182
  | `<T>(p: T) => p.x` (a member of a type parameter) | `` Cannot read `x` of `T`: an unconstrained type parameter has no members `` | `<T extends Point>(p: T) => p.x`, where `Point` is a class or interface that declares `x` |
177
183
  | `identity<i32>(7)` (type argument at a call) | `Type arguments are not written at a call site in Nish` | `identity(7)` — `T` is inferred from the arguments |
178
- | `async` / `await` / `Promise` | forbidden (no event loop) | the I/O builtins are synchronous |
184
+ | a promise in a variable, `.then`, `Promise.all` | forbidden: a promise is never a value | `await` each call where it is made; see [Async functions](#async-functions) |
179
185
  | `namespace N { }`, `declare global { }`, `@decorator` | forbidden | one module per file; a plain function in place of a decorator |
180
186
  | `keyof T`, `<T = i32>` (a default type argument) | `` Unsupported type `keyof T` (Phase 1 supports …) `` / `a default type argument … is not supported` | name the type; a type argument is always inferred |
181
187
  | `class B extends A` | `` `extends` is not supported: Nish has no inheritance `` | repeat the fields and `implements` an interface |
182
188
  | `static` members, `get x()` / `set x(v)` | `` … `static` members are not supported `` / `` Getters and setters are not supported in class `C` (use a method) `` | module `const`s and plain methods |
183
- | `const [a, b] = xs`, `({ x }: Point) =>`, `(n?: i32)`, `(n = 1)`, `f(...xs)` | `Destructuring is not supported` / `Destructured parameters are not supported` / `Optional parameters are not supported` / `` Parameter `n` has a default and no type annotation `` / `Spread arguments are not supported` | one name per binding; `n: i32 = 1` (a constant or an earlier parameter) for a default; `...ns: i32[]` as the last parameter, or pass the array itself |
189
+ | `const [a, b] = xs`, `({ x }: Point) =>`, `(n?: i32)`, `(n = 1)`, `f(...xs)` | `Destructuring is not supported` / `Destructured parameters are not supported` / `Optional parameters are not supported` / `` Parameter `n` has a default and no type annotation `` / `Spread arguments are not supported` | one name per binding; `n: i32 = 1` (a constant or an earlier parameter) for a default, `o: Opts | null = null` for an options argument; `...ns: i32[]` as the last parameter, or pass the array itself |
184
190
  | `function f(): void;` (an overload), `export default function ()` | `Functions must have a body` / `Functions must be named` | one named function, with its body |
185
191
  | a method in an `interface` | `` Interface `I` cannot declare methods (interfaces describe layout only) `` | fields only; a top-level function over the interface |
186
192
  | `abstract class`, `declare class`, `constructor(public x: i32)`, `interface B extends A`, `[k: string]: T` | `Abstract classes are not supported` / `` `declare class` is not supported `` / `Parameter properties … are not supported; declare the field and assign it` / ``Interface inheritance (`extends`) is not supported; list every field`` / `Index signatures are not supported …` | a plain class with its fields declared and assigned in the constructor; every field listed; a `Map` for a keyed table |
@@ -583,8 +589,11 @@ const double = (n: i32): i32 => n * 2; // a concise body is that one `return`
583
589
  - **Parameters are immutable**: `p = 1`, `p++`, `p += 1` are all rejected. Copy
584
590
  into a `let` first.
585
591
  - A non-`void` function must return on every path.
586
- - Rejected: generators, `async`, destructured / rest / optional / default
587
- parameters, overloads, nested function declarations. Type parameters *are*
592
+ - Rejected: generators, a method that is `async`, destructured and optional
593
+ (`x?: T`) parameters, overloads, nested function declarations. A default
594
+ (`n: i32 = 1`) or a rest parameter (`...ns: i32[]`) is allowed on a
595
+ function, a method, a constructor or a module `const` arrow — see
596
+ "Optional parameters" in [Recipes for what is missing](#recipes-for-what-is-missing). Type parameters *are*
588
597
  allowed — see [Generic functions](#generic-functions).
589
598
 
590
599
  ### Generic functions
@@ -850,8 +859,8 @@ export const main = (): i32 => {
850
859
  (`(x) => { if (x > limit) { hits.push(x) } }`); each capture becomes one more
851
860
  argument of the direct call. It may not assign to one, read a `let`
852
861
  (`` The arrow reads `k`, a `let` of the function it is written in: ... bind the value to a `const` before the call ``),
853
- read `this` (bind `const self = this`), or read a function parameter of its
854
- function. An arrow inside another arrow, or given to a method, a
862
+ read `this` (bind `const self = this`), or read a function-typed parameter
863
+ of its function. An arrow inside another arrow, or given to a method, a
855
864
  constructor, `expose` or `exposeWith`, captures nothing; `xs.sort(cmp)`'s
856
865
  comparator is the exception, and captures as an arrow argument does.
857
866
  - **The argument's type must match exactly**: `(x: i32) => i64` for a
@@ -873,6 +882,52 @@ export const main = (): i32 => {
873
882
  };
874
883
  ```
875
884
 
885
+ ### Async functions
886
+
887
+ `async` compiles natively: a function that awaits becomes a frame and a resume
888
+ switch, one that awaits nothing a plain function, and `export async function
889
+ main` runs to its end, then finishes every task it spawned. Nothing waits on
890
+ sockets or timers in this release, so every task runs to completion.
891
+ `--emit-ts` writes it as Node async/await, where `spawn`ed tasks resume in
892
+ Node's order, not the native one. A call
893
+ that can fail answers `Promise<Result<T, E>>`: test the `Result` after the
894
+ `await`. The rules:
895
+
896
+ - `async` goes on a top-level `function` or an arrow, never a method
897
+ (`NL1063`). Its return type is `Promise<T>` (`NL2655`), and `Promise<T>`
898
+ appears nowhere else — not on a parameter, field, local or alias (`NL2654`).
899
+ - **`await` takes a direct call to an `async` function** (`NL2643` for a value,
900
+ `NL2644` for a plain function's call), **at the top of a statement**: alone,
901
+ as a `const`/`let` initializer, after `return`, or right of a plain `=`.
902
+ Anywhere inside a larger expression is `NL2642`, so bind first:
903
+ `const n = await load(1); console.log(n);`.
904
+ - An `async` call that is not awaited is `NL2645`: a promise is never a value.
905
+ No `.then` (`NL2650`), no `Promise.all` (`NL2649`), no `for await`
906
+ (`NL2133`).
907
+ - Several at once: `import { awaitAll, spawn } from "nish:async"`, then
908
+ `const [a, b] = await awaitAll(f(x), g(y))` (two to eight calls, one name
909
+ each, `NL2647`). `spawn(f(x))` starts a task without waiting; `f` answers
910
+ `void` or a `Result` (`NL2646`).
911
+ - No recursion through `await`, direct or mutual (`NL2648`): write a loop.
912
+ - An `async` arrow goes only to a parameter whose function type answers
913
+ `Promise<T>` (`NL2639`), and only an `async` one goes there (`NL2640`).
914
+ - No `async` under a wasm target or `--profile wasi` (`NL2652`).
915
+ - `spawn(f(x))` runs `f` at once, until it first waits on the outside world,
916
+ before it returns; Node runs `f` only to its first `await` and the rest
917
+ after the spawning task yields. Do not rely on either order.
918
+
919
+ ```ts nish:err NL2642
920
+ async function load(id: i32): Promise<i32> {
921
+ return id;
922
+ }
923
+
924
+ async function run(): Promise<void> {
925
+ console.log(await load(1));
926
+ }
927
+
928
+ export const main = (): i32 => 0;
929
+ ```
930
+
876
931
  ### Data parallelism: `nish/threads`
877
932
 
878
933
  `parallelMapInto(src, dst, f)` writes `f(src[i])` into `dst[i]`, and
@@ -2243,7 +2298,8 @@ backlog)`, `tcpAccept(fd, peer)`, `netRead(fd, buf, off, len)`,
2243
2298
  `i32`: **a negative number is a failure, not an exception** — `-11` means "would
2244
2299
  block, call again", `-32` a peer that has gone (never SIGPIPE), `-98` the port
2245
2300
  is in use, `-111` refused, `-22` a bad argument. Every socket is non-blocking,
2246
- and there is no `async` and no callback: your `main` is the loop. A client calls
2301
+ and there is no callback, and an `async` function cannot wait on a socket yet
2302
+ (its tasks run on an in-process queue): your `main` is the loop. A client calls
2247
2303
  `tcpConnect(addr)`, which answers the descriptor while it is still connecting;
2248
2304
  wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffers
2249
2305
  are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
@@ -2254,6 +2310,9 @@ To fetch a URL from a CLI, skip all of this: `nish/net/fetch`'s
2254
2310
  response's head is in and answers it with `body.readAll(limit)`, or null on a
2255
2311
  failure (a `FetchSyncClient` keeps the connection and says why in `error`).
2256
2312
  `netRead` / `netWrite` panic on a range outside the buffer.
2313
+ `netWrite2(fd, a, aOff, aLen, b, bOff, bLen)` writes two ranges in one
2314
+ `sendmsg` (a header and a body without a copy) and answers the bytes written,
2315
+ which may stop inside either range; it checks both ranges, the first one first.
2257
2316
  `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2258
2317
  `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
2259
2318
  spreads the connections), and flag 2 `TCP_NODELAY` on every socket `tcpAccept`
@@ -2796,13 +2855,16 @@ or `PG_LOST` to `fail(run)` (on a spent handle these do nothing and count in
2796
2855
  `stray`), and after each call `next(run)` until
2797
2856
  `PG_BATCH_NONE`: `PG_BATCH_ROW` (the connection's row answers request `tag`),
2798
2857
  then one of `PG_BATCH_DONE`, `PG_BATCH_MISSING` or `PG_BATCH_FAILED` per
2799
- request. It reads no clock: when to take is the loop's choice, or a `PgBatchClock`'s (`PgBatchClockConfig`: `minCount`, `factor` × the moving average of batch sizes, `maxWaitUs`, the idle bypass `lightCount`; defaults are a fixed window), which `takeDue(conn, clock, nowUs, pool.inFlight())` consults after `clock.arrived(nowUs)` per add; `PgBatchTurns` wakes one collecting connection only when its batch is due. (No block;
2858
+ request. `sorted = false` drops `q.ord` from the statement's `order by` (and
2859
+ its Sort node, with `orderBy` empty): rows route as they come, and every last
2860
+ event waits for `end`. The batcher reads no clock: when to take is the loop's choice, or a `PgBatchClock`'s (`PgBatchClockConfig`: `minCount`, `factor` × the moving average of batch sizes, `maxWaitUs`, the idle bypass `lightCount`; defaults are a fixed window), which `takeDue(conn, clock, nowUs, pool.inFlight())` consults after `clock.arrived(nowUs)` per add; `PgBatchTurns` wakes one collecting connection only when its batch is due. (No block;
2800
2861
  `tests/postgres/live`'s `batch` scenario runs it against a server, and the user
2801
2862
  guide's [PostgreSQL page](guide/using-nish/postgres.md#batching-keyed-reads)
2802
2863
  its walk-through.)
2803
2864
 
2804
2865
  **Call another service from a route handler** — there is no `await fetch()`
2805
- in a handler and no event-loop callback: `nish/net/upstream`'s `Upstream(h,
2866
+ in a handler (an `async` task does not wait on a socket yet) and no event-loop
2867
+ callback: `nish/net/upstream`'s `Upstream(h,
2806
2868
  origin, limits)` is a pool of keep-alive connections to one `http:` origin on
2807
2869
  the `HttpHandler`'s own loop. `users.get(req, path)` returns at once — test it
2808
2870
  for `UPSTREAM_E_FULL`, which holds nothing — and keeps `req` held; after each
@@ -2857,9 +2919,10 @@ export const main = (): i32 => {
2857
2919
 
2858
2920
  **Optional parameters** — write a default instead: `(n: i32 = 1)`. A default
2859
2921
  is a literal, an enum member or an earlier parameter, and the call site fills
2860
- it in. `x?: T` is refused. A variable count of one type is a rest parameter,
2861
- `(...ns: i32[])`, last in the list; a call never spreads (`f(...xs)`), it passes
2862
- `xs` to an array parameter.
2922
+ it in. An options argument is `(opts: Options | null = null)`: `null` is the
2923
+ one default of a `T | null` parameter. `x?: T` is refused. A variable count
2924
+ of one type is a rest parameter, `(...ns: i32[])`, last in the list; a call
2925
+ never spreads (`f(...xs)`), it passes `xs` to an array parameter.
2863
2926
 
2864
2927
  **A calendar, regex, JSON, threads of your own** — not in the language (the
2865
2928
  wall clock is `Date.now()`, and that is all of `Date`). Say so rather than
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-aarch64-darwin",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "The nish native compiler for macOS on ARM64",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -0,0 +1,49 @@
1
+ // The declarations of `runtime/async.mjs`, `nish:async` under Node.
2
+
3
+ /** Starts `task` without waiting for it; the launcher waits before it exits. */
4
+ export declare function spawn(task: Promise<void>): void;
5
+ /** Every task's answer, in the order the calls are written, once all of them finish. */
6
+ export declare function awaitAll<A, B>(a: Promise<A>, b: Promise<B>): Promise<[A, B]>;
7
+ export declare function awaitAll<A, B, C>(a: Promise<A>, b: Promise<B>, c: Promise<C>): Promise<[A, B, C]>;
8
+ export declare function awaitAll<A, B, C, D>(
9
+ a: Promise<A>,
10
+ b: Promise<B>,
11
+ c: Promise<C>,
12
+ d: Promise<D>,
13
+ ): Promise<[A, B, C, D]>;
14
+ export declare function awaitAll<A, B, C, D, E>(
15
+ a: Promise<A>,
16
+ b: Promise<B>,
17
+ c: Promise<C>,
18
+ d: Promise<D>,
19
+ e: Promise<E>,
20
+ ): Promise<[A, B, C, D, E]>;
21
+ export declare function awaitAll<A, B, C, D, E, F>(
22
+ a: Promise<A>,
23
+ b: Promise<B>,
24
+ c: Promise<C>,
25
+ d: Promise<D>,
26
+ e: Promise<E>,
27
+ f: Promise<F>,
28
+ ): Promise<[A, B, C, D, E, F]>;
29
+ export declare function awaitAll<A, B, C, D, E, F, G>(
30
+ a: Promise<A>,
31
+ b: Promise<B>,
32
+ c: Promise<C>,
33
+ d: Promise<D>,
34
+ e: Promise<E>,
35
+ f: Promise<F>,
36
+ g: Promise<G>,
37
+ ): Promise<[A, B, C, D, E, F, G]>;
38
+ export declare function awaitAll<A, B, C, D, E, F, G, H>(
39
+ a: Promise<A>,
40
+ b: Promise<B>,
41
+ c: Promise<C>,
42
+ d: Promise<D>,
43
+ e: Promise<E>,
44
+ f: Promise<F>,
45
+ g: Promise<G>,
46
+ h: Promise<H>,
47
+ ): Promise<[A, B, C, D, E, F, G, H]>;
48
+ /** Waits for every task `spawn` started, the tasks those tasks start included. */
49
+ export declare function settleTasks(): Promise<void>;
@@ -0,0 +1,27 @@
1
+ // `nish:async` under Node (WP49): what `nish --emit-ts` writes an async
2
+ // program against, and what `runtime/nish.mjs` answers `nish:async` with. A
3
+ // file of its own, not `nish/runtime`, because `nish:process` has a `spawn`
4
+ // there already.
5
+ //
6
+ // `awaitAll` is `Promise.all` over its two to eight tasks. `spawn` starts a
7
+ // task without awaiting it and keeps it, so the launcher's `settleTasks` waits
8
+ // for every task started before the process exits, as the native driver
9
+ // drains its queue after `main` answers. Node runs a task only until its first
10
+ // `await` where native runs it until it first waits, so spawned tasks may print
11
+ // in another order (`docs/RUN_UNDER_NODE.md`). A task that throws is Node's
12
+ // unhandled rejection, which exits 1 as a native panic does.
13
+
14
+ const pending = [];
15
+
16
+ export const spawn = (task) => {
17
+ pending.push(task);
18
+ };
19
+
20
+ export const awaitAll = (...tasks) => Promise.all(tasks);
21
+
22
+ export const settleTasks = async () => {
23
+ for (let i = 0; i < pending.length; i++) {
24
+ await pending[i];
25
+ }
26
+ pending.length = 0;
27
+ };
package/runtime/nish.d.ts CHANGED
@@ -444,6 +444,16 @@ declare function tcpAccept(fd: i32, peer: u8[]): i32;
444
444
  declare function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
445
445
  /** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
446
446
  declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
447
+ /** `netWrite` of `a`'s range then `b`'s, in one system call: the count of both, at most `aLen + bLen`. */
448
+ declare function netWrite2(
449
+ fd: i32,
450
+ a: readonly u8[],
451
+ aOff: i32,
452
+ aLen: i32,
453
+ b: readonly u8[],
454
+ bOff: i32,
455
+ bLen: i32
456
+ ): i32;
447
457
  /** Shut the read side (0), the write side (1) or both (2). */
448
458
  declare function netShutdown(fd: i32, how: i32): i32;
449
459
  /** Close the descriptor. */
@@ -723,6 +733,16 @@ declare module "nish:net" {
723
733
  export function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
724
734
  /** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
725
735
  export function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
736
+ /** `netWrite` of `a`'s range then `b`'s, in one system call: the count of both, at most `aLen + bLen`. */
737
+ export function netWrite2(
738
+ fd: i32,
739
+ a: readonly u8[],
740
+ aOff: i32,
741
+ aLen: i32,
742
+ b: readonly u8[],
743
+ bOff: i32,
744
+ bLen: i32
745
+ ): i32;
726
746
  /** Shut the read side (0), the write side (1) or both (2). */
727
747
  export function netShutdown(fd: i32, how: i32): i32;
728
748
  export function netClose(fd: i32): i32;
@@ -1059,6 +1079,58 @@ declare module "nish:region" {
1059
1079
  export function free<T>(r: Region<T>): void;
1060
1080
  }
1061
1081
 
1082
+ // `nish:async` (WP49, docs/LANGUAGE.md -> Async functions). `tsc` sees ordinary
1083
+ // promises; what `nish` adds is that each argument is a direct call to an
1084
+ // `async` function, written where it is made, which the checker holds it to
1085
+ // (`src/async-check.ts`), and that `awaitAll` takes two to eight of them.
1086
+ declare module "nish:async" {
1087
+ /** Start `task` and go on without its answer; it runs until it first waits. */
1088
+ export function spawn(task: Promise<void>): void;
1089
+ /** Await every call at once; the answers come in the order the calls are written. */
1090
+ export function awaitAll<A, B>(a: Promise<A>, b: Promise<B>): Promise<[A, B]>;
1091
+ export function awaitAll<A, B, C>(a: Promise<A>, b: Promise<B>, c: Promise<C>): Promise<[A, B, C]>;
1092
+ export function awaitAll<A, B, C, D>(
1093
+ a: Promise<A>,
1094
+ b: Promise<B>,
1095
+ c: Promise<C>,
1096
+ d: Promise<D>,
1097
+ ): Promise<[A, B, C, D]>;
1098
+ export function awaitAll<A, B, C, D, E>(
1099
+ a: Promise<A>,
1100
+ b: Promise<B>,
1101
+ c: Promise<C>,
1102
+ d: Promise<D>,
1103
+ e: Promise<E>,
1104
+ ): Promise<[A, B, C, D, E]>;
1105
+ export function awaitAll<A, B, C, D, E, F>(
1106
+ a: Promise<A>,
1107
+ b: Promise<B>,
1108
+ c: Promise<C>,
1109
+ d: Promise<D>,
1110
+ e: Promise<E>,
1111
+ f: Promise<F>,
1112
+ ): Promise<[A, B, C, D, E, F]>;
1113
+ export function awaitAll<A, B, C, D, E, F, G>(
1114
+ a: Promise<A>,
1115
+ b: Promise<B>,
1116
+ c: Promise<C>,
1117
+ d: Promise<D>,
1118
+ e: Promise<E>,
1119
+ f: Promise<F>,
1120
+ g: Promise<G>,
1121
+ ): Promise<[A, B, C, D, E, F, G]>;
1122
+ export function awaitAll<A, B, C, D, E, F, G, H>(
1123
+ a: Promise<A>,
1124
+ b: Promise<B>,
1125
+ c: Promise<C>,
1126
+ d: Promise<D>,
1127
+ e: Promise<E>,
1128
+ f: Promise<F>,
1129
+ g: Promise<G>,
1130
+ h: Promise<H>,
1131
+ ): Promise<[A, B, C, D, E, F, G, H]>;
1132
+ }
1133
+
1062
1134
  // ---- Arena (docs/LANGUAGE.md -> Arena) ---------------------------------------
1063
1135
 
1064
1136
  declare const Arena: {
package/runtime/nish.h CHANGED
@@ -714,8 +714,10 @@ bool nish_is_executable(const nish_str *path);
714
714
  * `nish_tcp_accept(fd, peer)`: a new descriptor and the peer's address.
715
715
  * `nish_net_read` / `nish_net_write(fd, buf, off, len)`: bytes `[off, off +
716
716
  * len)` of `buf`, which the compiled call has range-checked; a write to a
717
- * gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
718
- * write, 2 both. `nish_net_close(fd)`.
717
+ * gone peer is -32, never SIGPIPE. `nish_net_write2(fd, a, aoff, alen, b,
718
+ * boff, blen)`: both ranges in one `sendmsg`, `a`'s bytes first, answering the
719
+ * count written (short when the socket buffer fills) or the same errors.
720
+ * `nish_net_shutdown(fd, how)`: 0 read, 1 write, 2 both. `nish_net_close(fd)`.
719
721
  * `nish_net_set_option(fd, option, value)`: one socket option from a
720
722
  * whitelist, by the runtime's own id (1 `TCP_NODELAY`, 2 `TCP_QUICKACK`, 3
721
723
  * `SO_KEEPALIVE`, 4 `SO_SNDBUF`, 5 `SO_RCVBUF`); any other id is -22, and
@@ -784,6 +786,8 @@ int32_t nish_tcp_listen_flags(const nish_str *host, int32_t port, int32_t backlo
784
786
  int32_t nish_tcp_accept(int32_t fd, nish_array *peer);
785
787
  int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
786
788
  int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
789
+ int32_t nish_net_write2(int32_t fd, const nish_array *a, int64_t aoff, int64_t alen, const nish_array *b,
790
+ int64_t boff, int64_t blen);
787
791
  int32_t nish_net_shutdown(int32_t fd, int32_t how);
788
792
  int32_t nish_net_close(int32_t fd);
789
793
  int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value);
package/runtime/nish.mjs CHANGED
@@ -139,6 +139,7 @@ for (const name of [
139
139
  "tcpAccept",
140
140
  "netRead",
141
141
  "netWrite",
142
+ "netWrite2",
142
143
  "netShutdown",
143
144
  "netClose",
144
145
  "tcpConnect",
@@ -379,6 +380,9 @@ registerHooks({
379
380
  if (specifier === "nish:simd") {
380
381
  return next(new URL("./core.mjs", import.meta.url).href, context);
381
382
  }
383
+ if (specifier === "nish:async") {
384
+ return next(new URL("./async.mjs", import.meta.url).href, context);
385
+ }
382
386
  if (specifier === "nish:region") {
383
387
  return { url: `data:text/javascript,${encodeURIComponent(regionSource)}`, shortCircuit: true };
384
388
  }
@@ -417,6 +417,28 @@ int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t l
417
417
  return n < 0 ? nish_net_fail() : (int32_t)n;
418
418
  }
419
419
 
420
+ /* `netWrite2(fd, a, aoff, alen, b, boff, blen)`: `netWrite` of two ranges in
421
+ one `sendmsg`, so a response head and its body leave in one system call
422
+ without being copied together first. Both ranges are checked by the
423
+ compiled call. The count is at most `alen + blen`; a sum past `INT32_MAX`
424
+ is offered only up to it, so the answer always fits, and a short write is a
425
+ count, as `netWrite`'s is. */
426
+ int32_t nish_net_write2(int32_t fd, const nish_array *a, int64_t aoff, int64_t alen, const nish_array *b,
427
+ int64_t boff, int64_t blen) {
428
+ struct iovec iov[2];
429
+ struct msghdr msg;
430
+ if (blen > (int64_t)INT32_MAX - alen) blen = (int64_t)INT32_MAX - alen;
431
+ iov[0].iov_base = a->data + aoff;
432
+ iov[0].iov_len = (size_t)alen;
433
+ iov[1].iov_base = b->data + boff;
434
+ iov[1].iov_len = (size_t)blen;
435
+ memset(&msg, 0, sizeof msg);
436
+ msg.msg_iov = iov;
437
+ msg.msg_iovlen = 2;
438
+ ssize_t n = sendmsg(fd, &msg, NISH_NOSIGNAL);
439
+ return n < 0 ? nish_net_fail() : (int32_t)n;
440
+ }
441
+
420
442
  /* `netShutdown(fd, how)`: 0 the read side, 1 the write side, 2 both, which
421
443
  are `SHUT_RD`, `SHUT_WR` and `SHUT_RDWR` on both platforms. */
422
444
  int32_t nish_net_shutdown(int32_t fd, int32_t how) {