@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/text.ts
CHANGED
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The language gives a string `length`, `charCodeAt`, `substring`, `indexOf`,
|
|
5
5
|
* `startsWith`, `endsWith` and `===` by content, and an array `join`. It does
|
|
6
|
-
* **not** give `split`, `trim`, `toLowerCase` or a regular expression
|
|
7
|
-
* is a decision rather than a gap: each
|
|
8
|
-
* need a locale or a character table
|
|
6
|
+
* **not** give `split`, `trim`, `toLowerCase` or a regular expression (that is
|
|
7
|
+
* `std/regex`), and that is a decision rather than a gap: each allocates, and
|
|
8
|
+
* several need a locale or a character table this runtime has no room for
|
|
9
9
|
* (`docs/wp7-runtime.md` keeps the runtime under a measured byte budget).
|
|
10
10
|
*
|
|
11
11
|
* So a program that reads a file and wants its lines writes the loop. This
|
|
12
12
|
* module is that loop, written once. Every offset here is a **byte** offset,
|
|
13
|
-
* like `s.length` itself, and every function is ASCII-only where it
|
|
14
|
-
* characters at all — a UTF-8 string passes through `splitLines` and
|
|
15
|
-
* untouched, because a newline and a space cannot appear inside a
|
|
16
|
-
* sequence.
|
|
13
|
+
* like `s.length` itself, and every function but one is ASCII-only where it
|
|
14
|
+
* inspects characters at all — a UTF-8 string passes through `splitLines` and
|
|
15
|
+
* `split` untouched, because a newline and a space cannot appear inside a
|
|
16
|
+
* multi-byte sequence. The one is `codePointLength`, which counts code points
|
|
17
|
+
* rather than bytes and answers the same natively and under Node; it says how.
|
|
17
18
|
*
|
|
18
19
|
* Every width this module declares is spelled (`i32`, never `number`) *and*
|
|
19
20
|
* every value a builtin hands it is converted at the read: `s.length`,
|
|
@@ -268,3 +269,85 @@ const indexOfAnyFrom = (text: string, bytes: string, from: i32): i32 => {
|
|
|
268
269
|
}
|
|
269
270
|
return -1
|
|
270
271
|
}
|
|
272
|
+
|
|
273
|
+
/** The top two bits of a UTF-8 byte, which say whether it starts a code point. */
|
|
274
|
+
const UTF8_TOP_BITS: i32 = 0xc0
|
|
275
|
+
|
|
276
|
+
/** What those two bits are on a continuation byte: `10xxxxxx`, never a code point's first byte. */
|
|
277
|
+
const UTF8_CONTINUATION: i32 = 0x80
|
|
278
|
+
|
|
279
|
+
/** The first high surrogate, the unit that opens a UTF-16 pair. */
|
|
280
|
+
const HIGH_SURROGATE: i32 = 0xd800
|
|
281
|
+
|
|
282
|
+
/** The first low surrogate, the unit that closes a pair; the high ones end just below it. */
|
|
283
|
+
const LOW_SURROGATE: i32 = 0xdc00
|
|
284
|
+
|
|
285
|
+
/** One past the last low surrogate. */
|
|
286
|
+
const SURROGATE_END: i32 = 0xe000
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Whether a string's units are UTF-8 bytes, as they are natively, rather than
|
|
290
|
+
* UTF-16 units, as they are under Node.
|
|
291
|
+
*
|
|
292
|
+
* `é` is two bytes and one UTF-16 unit, so its length says which host is
|
|
293
|
+
* running. It is written as an escape so that how this file is saved, `é` as
|
|
294
|
+
* one code point or as `e` and a combining accent, cannot change the answer.
|
|
295
|
+
* Natively the length of a literal is a load from a constant, and the
|
|
296
|
+
* optimiser folds this call to `true` and drops the other walk in
|
|
297
|
+
* `codePointLength`. The name is long for the reason `isTextBlankByte`'s is.
|
|
298
|
+
*/
|
|
299
|
+
const textUnitsAreBytes = (): boolean => toI32("\u00e9".length) === 2
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The number of Unicode code points in `text`: what JSON Schema's
|
|
303
|
+
* `minLength` and `maxLength` count, and not what `text.length` counts.
|
|
304
|
+
* `codePointLength("é")` is 1 where `"é".length` is 2, and
|
|
305
|
+
* `codePointLength("😀")` is 1 where its length is 4.
|
|
306
|
+
*
|
|
307
|
+
* Natively a string is its UTF-8 bytes, and every code point has exactly one
|
|
308
|
+
* byte that is not a continuation byte (`10xxxxxx`), so that is what is
|
|
309
|
+
* counted. Under Node the same `charCodeAt` reads UTF-16 units, where a byte
|
|
310
|
+
* rule would miscount: `"©®±"` is three units in `0x80`–`0xBF`, which are
|
|
311
|
+
* continuation bytes in UTF-8 but whole characters here. So the function asks
|
|
312
|
+
* which host it is on (`textUnitsAreBytes`) and, under Node, counts every unit
|
|
313
|
+
* except the low half of a surrogate pair. Both walks answer the same for any
|
|
314
|
+
* string literal, a lone surrogate escape included: it is three WTF-8 bytes
|
|
315
|
+
* with one leading byte natively, and one unit under Node. They differ for
|
|
316
|
+
* text built from bytes: `String.fromBytes` and `String.fromCharCode` give one
|
|
317
|
+
* unit per byte under Node, so a string decoded that way, as `nish/json`
|
|
318
|
+
* decodes a `\u` escape, counts its UTF-8 bytes there. They also differ for text
|
|
319
|
+
* that is not well-formed — bytes that are not UTF-8 natively, where each
|
|
320
|
+
* stray continuation byte counts nothing, or the two halves of a pair joined
|
|
321
|
+
* at run time, which are one string under Node and two lone surrogates
|
|
322
|
+
* natively.
|
|
323
|
+
*/
|
|
324
|
+
export const codePointLength = (text: string): i32 => {
|
|
325
|
+
const length: i32 = toI32(text.length)
|
|
326
|
+
// `count` never passes `i`, which stays below `length`, so it never wraps.
|
|
327
|
+
// It is a `u32` because unsigned addition carries no overflow check: an
|
|
328
|
+
// `i32` would need a panic branch, or a `count < length` guard to prove it
|
|
329
|
+
// away, and either one keeps the byte walk from vectorizing.
|
|
330
|
+
let count: u32 = 0
|
|
331
|
+
let i: i32 = 0
|
|
332
|
+
if (textUnitsAreBytes()) {
|
|
333
|
+
while (i < length) {
|
|
334
|
+
if ((toI32(text.charCodeAt(i)) & UTF8_TOP_BITS) !== UTF8_CONTINUATION) {
|
|
335
|
+
count += 1
|
|
336
|
+
}
|
|
337
|
+
i += 1
|
|
338
|
+
}
|
|
339
|
+
return toI32(count)
|
|
340
|
+
}
|
|
341
|
+
// A low surrogate right after a high one closes a pair the high one already
|
|
342
|
+
// counted. Any other unit, a lone surrogate included, is a code point.
|
|
343
|
+
let afterHigh: boolean = false
|
|
344
|
+
while (i < length) {
|
|
345
|
+
const unit: i32 = toI32(text.charCodeAt(i))
|
|
346
|
+
if (!(afterHigh && unit >= LOW_SURROGATE && unit < SURROGATE_END)) {
|
|
347
|
+
count += 1
|
|
348
|
+
}
|
|
349
|
+
afterHigh = unit >= HIGH_SURROGATE && unit < LOW_SURROGATE
|
|
350
|
+
i += 1
|
|
351
|
+
}
|
|
352
|
+
return toI32(count)
|
|
353
|
+
}
|
package/std/threads.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `std/threads` — data parallelism,
|
|
3
|
-
* (docs/wp29-thread-surface.md §4.1 and §4.
|
|
2
|
+
* `std/threads` — data parallelism, a scope for heterogeneous tasks, and a
|
|
3
|
+
* lock that owns its data (docs/wp29-thread-surface.md §4.1, §4.2 and §4.3).
|
|
4
4
|
*
|
|
5
|
-
* import { parallelMapInto, parallelReduce, scope } from "nish/threads";
|
|
5
|
+
* import { AtomicI32, Mutex, parallelFrom, parallelMap, parallelMapInto, parallelReduce, scope } from "nish/threads";
|
|
6
6
|
*
|
|
7
7
|
* parallelMapInto(src, dst, (x) => x * 3);
|
|
8
|
+
* const ys = parallelMap(src, (x) => x * k + b); // k, b: `const` locals, read by the body
|
|
9
|
+
* const sq = parallelFrom(n, (i) => i * i);
|
|
8
10
|
* const total = parallelReduce(src, (a, b) => a + b, 0);
|
|
9
11
|
* {
|
|
10
12
|
* using s = scope();
|
|
@@ -15,20 +17,25 @@
|
|
|
15
17
|
* **What is written here is the meaning, not the implementation.** Each body
|
|
16
18
|
* below is the sequential program, and it is what runs under Node, what
|
|
17
19
|
* `npm run check` type-checks, and what the compiler checks the call against.
|
|
18
|
-
* The compiler then recognises the
|
|
19
|
-
* and lowers an instance of
|
|
20
|
-
* (runtime/runtime-parallel.c): it
|
|
21
|
-
* range — `mapRange` for a map,
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* The compiler then recognises the exported templates by module and name
|
|
21
|
+
* and lowers an instance of `parallelMapInto`, `parallelFrom` or
|
|
22
|
+
* `parallelReduce` onto `nish_parallel_range` (runtime/runtime-parallel.c): it
|
|
23
|
+
* replaces the one call that walks the whole range — `mapRange` for a map,
|
|
24
|
+
* `fromRange` for a `parallelFrom`, `reduceBlocks` for a reduce — with a region
|
|
25
|
+
* that hands each thread a contiguous piece of it, and emits the rest of the
|
|
26
|
+
* body as written. `parallelMap` is `parallelMapInto` into an array it
|
|
27
|
+
* allocates first. So the length check, its message and the order a reduce combines in
|
|
24
28
|
* are this file's, whichever way the program is compiled.
|
|
25
29
|
*
|
|
26
30
|
* What makes the region safe is checked at the call, not trusted
|
|
27
31
|
* (docs/LANGUAGE.md, "Data parallelism"): the function passed as `f` may write
|
|
28
32
|
* nothing its caller could observe, may allocate only temporaries it drops
|
|
29
33
|
* before it returns — they are given back after every element — `dst` may not
|
|
30
|
-
* be reachable from an element of `src`, and the result type is a number
|
|
31
|
-
* `boolean
|
|
34
|
+
* be reachable from an element of `src`, and the result type is a number, a
|
|
35
|
+
* `boolean` or an enum — or, for a map, a `string` or an array of those
|
|
36
|
+
* scalars, which the join copies out of the worker's arena. An arrow written at the call may read the `const` locals and
|
|
37
|
+
* the parameters of the function it is written in: each one is handed to the
|
|
38
|
+
* body as a parameter of its own, which no body may write through. Importing this module compiles the program with `--threads`,
|
|
32
39
|
* because every worker needs an arena of its own.
|
|
33
40
|
*
|
|
34
41
|
* A reduce is deterministic. `src` is split into `min(64, ceil(n / BLOCK))`
|
|
@@ -40,6 +47,8 @@
|
|
|
40
47
|
* parameters and cannot see through a named function.
|
|
41
48
|
*/
|
|
42
49
|
|
|
50
|
+
import { wrappingAdd, wrappingSub } from "nish:unsafe"
|
|
51
|
+
|
|
43
52
|
/**
|
|
44
53
|
* Elements per block of a reduce: 2^20, about a millisecond of simple work
|
|
45
54
|
* (docs/wp20-threads.md §8e). It decides the blocking, and so the answer's
|
|
@@ -55,14 +64,17 @@ const MAX_BLOCKS: i32 = 64
|
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
66
|
* Writes `f(src[i])` into `dst[i]` for every `i` in `[lo, hi)`: one thread's
|
|
58
|
-
* share of a map.
|
|
67
|
+
* share of a map. Both lengths are read in the loop condition and `dst`'s again
|
|
59
68
|
* after the call, because a call ends every length fact the bounds proof holds
|
|
60
69
|
* (docs/LANGUAGE.md, "Arrays") and this is what keeps both accesses unchecked.
|
|
61
70
|
* The caller has already checked that the range is inside both arrays, so
|
|
62
|
-
* neither test is ever the one that stops the loop.
|
|
71
|
+
* neither test is ever the one that stops the loop. `dst`'s in the condition
|
|
72
|
+
* lets the loop vectorise (#619) when `f` inlines with no panic path: LLVM
|
|
73
|
+
* sees the test after the call repeat one the condition made, drops it, and
|
|
74
|
+
* the store is unconditional. `fromRange` has the same shape.
|
|
63
75
|
*/
|
|
64
76
|
const mapRange = <T, U>(src: T[], dst: U[], f: (x: T) => U, lo: i32, hi: i32): void => {
|
|
65
|
-
for (let i: i32 = lo; i >= 0 && i < hi && i < toI32(src.length); i++) {
|
|
77
|
+
for (let i: i32 = lo; i >= 0 && i < hi && i < toI32(src.length) && i < toI32(dst.length); i++) {
|
|
66
78
|
const y = f(src[i])
|
|
67
79
|
if (i < toI32(dst.length)) {
|
|
68
80
|
dst[i] = y
|
|
@@ -172,6 +184,42 @@ export const parallelReduce = <T>(src: T[], f: (acc: T, x: T) => T, identity: T)
|
|
|
172
184
|
return acc
|
|
173
185
|
}
|
|
174
186
|
|
|
187
|
+
/**
|
|
188
|
+
* A new array of `f(src[i])` for every index of `src`: `parallelMapInto` into
|
|
189
|
+
* an array this call allocates, before any element is computed, so nothing
|
|
190
|
+
* but the map itself can hold it while the region runs.
|
|
191
|
+
*/
|
|
192
|
+
export const parallelMap = <T, U>(src: T[], f: (x: T) => U): U[] => {
|
|
193
|
+
const dst = new Array<U>(toI32(src.length))
|
|
194
|
+
parallelMapInto(src, dst, f)
|
|
195
|
+
return dst
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Writes `f(i)` into `dst[i]` for every `i` in `[lo, hi)`: one thread's share
|
|
200
|
+
* of a `parallelFrom`. The length is read in the loop condition and again
|
|
201
|
+
* after the call, for the reason `mapRange` gives.
|
|
202
|
+
*/
|
|
203
|
+
const fromRange = <U>(dst: U[], f: (i: i32) => U, lo: i32, hi: i32): void => {
|
|
204
|
+
for (let i: i32 = lo; i >= 0 && i < hi && i < toI32(dst.length); i++) {
|
|
205
|
+
const y = f(i)
|
|
206
|
+
if (i < toI32(dst.length)) {
|
|
207
|
+
dst[i] = y
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A new array of `n` elements, `f(i)` at index `i`, computed on as many
|
|
214
|
+
* threads as the machine has and the length is worth: `Array.from({ length:
|
|
215
|
+
* n }, (_, i) => f(i))`, with the array allocated before any element is.
|
|
216
|
+
*/
|
|
217
|
+
export const parallelFrom = <U>(n: i32, f: (i: i32) => U): U[] => {
|
|
218
|
+
const dst = new Array<U>(n)
|
|
219
|
+
fromRange(dst, f, 0, toI32(dst.length))
|
|
220
|
+
return dst
|
|
221
|
+
}
|
|
222
|
+
|
|
175
223
|
/**
|
|
176
224
|
* The panic of a task whose destination slot is not in its array. A function of
|
|
177
225
|
* its own for the reason `dstTooShort` is one: the message is its allocation.
|
|
@@ -199,6 +247,31 @@ const storeResult = <R>(dst: R[], at: i32, r: R): void => {
|
|
|
199
247
|
const runTask = <A, R>(entry: (arg: A) => R, arg: A, dst: R[], at: i32): void =>
|
|
200
248
|
storeResult(dst, at, entry(arg))
|
|
201
249
|
|
|
250
|
+
/**
|
|
251
|
+
* One task with no destination: `entry(arg)`, whose answer, if it has one,
|
|
252
|
+
* nothing stores — the checker holds such an entry to `void`. Natively the
|
|
253
|
+
* compiler files this call as a task as it files `runTask`'s, with no store
|
|
254
|
+
* at the join; nothing under Node reaches it (see `spawn`).
|
|
255
|
+
*/
|
|
256
|
+
const runWith = <A, R>(entry: (arg: A) => R, arg: A): void => {
|
|
257
|
+
entry(arg)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** `runWith` for an entry of no parameters, such as `() => work(xs)`, whose captures are its arguments. */
|
|
261
|
+
const runThunk = <R>(entry: () => R): void => {
|
|
262
|
+
entry()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Whether a call of `spawn` wrote a destination slot. Natively every call
|
|
267
|
+
* that reaches `spawn` did, so this is always true there: the compiler gives
|
|
268
|
+
* `s.spawn(entry, arg)` and `s.spawn(() => ...)` the methods written for
|
|
269
|
+
* them below, `spawnWith` and `spawnThunk`. Under Node the source runs as
|
|
270
|
+
* it is written and every form reaches `spawn`, where a slot the call left
|
|
271
|
+
* out is `undefined`, which is neither below zero nor at or above it.
|
|
272
|
+
*/
|
|
273
|
+
const slotWritten = (at: i32): boolean => at < 0 || at >= 0
|
|
274
|
+
|
|
202
275
|
/**
|
|
203
276
|
* A scope of tasks, joined when the block that declares it ends
|
|
204
277
|
* (docs/wp29-thread-surface.md §4.2). It is only ever made by `scope()` in a
|
|
@@ -218,12 +291,40 @@ export class ThreadScope {
|
|
|
218
291
|
* again when its answer is stored.
|
|
219
292
|
*/
|
|
220
293
|
spawn<A, R>(entry: (arg: A) => R, arg: A, dst: R[], at: i32): void {
|
|
294
|
+
if (!slotWritten(at)) {
|
|
295
|
+
// Under Node only: `s.spawn(entry, arg)` or `s.spawn(() => ...)`, run
|
|
296
|
+
// at the spawn with nothing to store (`slotWritten`).
|
|
297
|
+
entry(arg)
|
|
298
|
+
return
|
|
299
|
+
}
|
|
221
300
|
if (at < 0 || at >= toI32(dst.length)) {
|
|
222
301
|
slotOutOfRange(at, toI32(dst.length))
|
|
223
302
|
}
|
|
224
303
|
runTask(entry, arg, dst, at)
|
|
225
304
|
}
|
|
226
305
|
|
|
306
|
+
/**
|
|
307
|
+
* `s.spawn(entry, arg)` as the compiler reads it: run `entry(arg)` on a
|
|
308
|
+
* thread of its own when the scope closes, and store nothing. `entry`
|
|
309
|
+
* answers `void`. Only the compiler names this method (see `slotWritten`):
|
|
310
|
+
* a scope is only ever the receiver of a `spawn` statement, so `s.spawnWith(...)`
|
|
311
|
+
* written in a program is refused by that rule (NL2389), as passing the
|
|
312
|
+
* scope would be.
|
|
313
|
+
*/
|
|
314
|
+
spawnWith<A, R>(entry: (arg: A) => R, arg: A): void {
|
|
315
|
+
runWith(entry, arg)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* `s.spawn(() => work(xs))` as the compiler reads it: run the arrow, whose
|
|
320
|
+
* captured `const` locals are its arguments, on a thread of its own when the
|
|
321
|
+
* scope closes, and store nothing. Only the compiler names this method, and
|
|
322
|
+
* a program that names it is refused as `spawnWith`'s is (NL2389).
|
|
323
|
+
*/
|
|
324
|
+
spawnThunk<R>(entry: () => R): void {
|
|
325
|
+
runThunk(entry)
|
|
326
|
+
}
|
|
327
|
+
|
|
227
328
|
/**
|
|
228
329
|
* What `using` calls when the block ends. Natively the compiler emits the
|
|
229
330
|
* join itself at every exit of the block, and under Node every task has
|
|
@@ -236,3 +337,326 @@ export class ThreadScope {
|
|
|
236
337
|
|
|
237
338
|
/** A new scope, for a `using` declaration: `using s = scope()`. */
|
|
238
339
|
export const scope = (): ThreadScope => new ThreadScope()
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* What a `Mutex` hands out: its data, as `value`, for as long as the block of
|
|
343
|
+
* the `using` declaration that took the lock (docs/LANGUAGE.md, "A lock that
|
|
344
|
+
* owns its data"). A guard is only ever named as `g.value`, and `g.value`
|
|
345
|
+
* only as the base of a field or element access, so nothing read through it
|
|
346
|
+
* outlives the lock.
|
|
347
|
+
*
|
|
348
|
+
* Each `Mutex` makes exactly one guard, when it is made, and every lock hands
|
|
349
|
+
* out that one: taking the lock allocates nothing.
|
|
350
|
+
*/
|
|
351
|
+
export class MutexGuard<T> {
|
|
352
|
+
/** The data the lock owns. */
|
|
353
|
+
value: T
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The lock word: 0 when the lock is free, 1 while a guard's block holds it.
|
|
357
|
+
* Nothing written here reads or writes it. Natively the compiler takes it
|
|
358
|
+
* with an acquire compare-and-swap in `lock()` and gives it back with a
|
|
359
|
+
* release store at every exit of the guard's block (`src/emit-parallel.ts`);
|
|
360
|
+
* under Node one task runs at a time and there is nothing to exclude.
|
|
361
|
+
*/
|
|
362
|
+
word: i32 = 0
|
|
363
|
+
|
|
364
|
+
constructor(value: T) {
|
|
365
|
+
this.value = value
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* What `using` calls when the guard's block ends. Natively the compiler
|
|
370
|
+
* emits the release itself at every exit of the block, and under Node
|
|
371
|
+
* nothing was taken, so there is nothing to give back.
|
|
372
|
+
*/
|
|
373
|
+
[Symbol.dispose](): void {
|
|
374
|
+
// Nothing to release: see above.
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* A lock that owns its data (docs/wp29-thread-surface.md §4.3). The data is
|
|
380
|
+
* reachable only through the guard `lock()` answers, bound by `using`, so no
|
|
381
|
+
* path to it skips the lock: `new Mutex<Hist>(new Hist())` takes a fresh
|
|
382
|
+
* value, and `using g = m.lock()` is the only way to read or write it.
|
|
383
|
+
*
|
|
384
|
+
* Under Node a scope's tasks run one at a time, at their spawn, so `lock()`
|
|
385
|
+
* answers the guard and waits for nothing: that is one of the orders the
|
|
386
|
+
* native program can run its critical sections in, and a program whose
|
|
387
|
+
* guarded updates commute prints the same either way.
|
|
388
|
+
*/
|
|
389
|
+
export class Mutex<T> {
|
|
390
|
+
/** The one guard, which holds the data; only `lock()` hands it out. */
|
|
391
|
+
guard: MutexGuard<T>
|
|
392
|
+
|
|
393
|
+
constructor(init: T) {
|
|
394
|
+
this.guard = new MutexGuard<T>(init)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The guard over the data, for a `using` declaration: `using g = m.lock()`.
|
|
399
|
+
* Natively the call waits until the lock is free and takes it.
|
|
400
|
+
*/
|
|
401
|
+
lock(): MutexGuard<T> {
|
|
402
|
+
return this.guard
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* An `i32` that a scope's tasks and a parallel body may update together
|
|
408
|
+
* (docs/LANGUAGE.md, "Atomics"). Each method is one indivisible operation:
|
|
409
|
+
* natively the compiler emits each body itself, as a sequentially consistent
|
|
410
|
+
* `load atomic`, `store atomic`, `atomicrmw` or `cmpxchg` on `value`
|
|
411
|
+
* (docs/IR_COOKBOOK.md, "Atomics"), and what is written below is the meaning,
|
|
412
|
+
* which is what runs under Node. `add` and `sub` wrap at 32 bits, as the
|
|
413
|
+
* native instruction does; `add`, `sub`, `min`, `max` and `exchange` answer
|
|
414
|
+
* the value before the update, and `compareExchange` the value it saw.
|
|
415
|
+
*
|
|
416
|
+
* It reaches a task by the routes a `Mutex` does, and a parallel body through
|
|
417
|
+
* a capture; its value is named only through these methods. Results that do
|
|
418
|
+
* not depend on the order the updates land in need the updates to commute,
|
|
419
|
+
* which the checker does not prove.
|
|
420
|
+
*/
|
|
421
|
+
export class AtomicI32 {
|
|
422
|
+
/** The value; named only by this class's methods, which the compiler emits. */
|
|
423
|
+
value: i32
|
|
424
|
+
|
|
425
|
+
constructor(value: i32) {
|
|
426
|
+
this.value = value
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** The value. */
|
|
430
|
+
load(): i32 {
|
|
431
|
+
return this.value
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Sets the value to `value`. */
|
|
435
|
+
store(value: i32): void {
|
|
436
|
+
this.value = value
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Adds `delta`, wrapping, and answers the value before. */
|
|
440
|
+
add(delta: i32): i32 {
|
|
441
|
+
const old = this.value
|
|
442
|
+
this.value = wrappingAdd(old, delta)
|
|
443
|
+
return old
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Subtracts `delta`, wrapping, and answers the value before. */
|
|
447
|
+
sub(delta: i32): i32 {
|
|
448
|
+
const old = this.value
|
|
449
|
+
this.value = wrappingSub(old, delta)
|
|
450
|
+
return old
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Keeps the smaller of the value and `value`, and answers the value before. */
|
|
454
|
+
min(value: i32): i32 {
|
|
455
|
+
const old = this.value
|
|
456
|
+
if (value < old) {
|
|
457
|
+
this.value = value
|
|
458
|
+
}
|
|
459
|
+
return old
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Keeps the larger of the value and `value`, and answers the value before. */
|
|
463
|
+
max(value: i32): i32 {
|
|
464
|
+
const old = this.value
|
|
465
|
+
if (value > old) {
|
|
466
|
+
this.value = value
|
|
467
|
+
}
|
|
468
|
+
return old
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Sets the value to `value` and answers the value before. */
|
|
472
|
+
exchange(value: i32): i32 {
|
|
473
|
+
const old = this.value
|
|
474
|
+
this.value = value
|
|
475
|
+
return old
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Sets the value to `desired` if it is `expected`, and answers the value it saw. */
|
|
479
|
+
compareExchange(expected: i32, desired: i32): i32 {
|
|
480
|
+
const old = this.value
|
|
481
|
+
if (old === expected) {
|
|
482
|
+
this.value = desired
|
|
483
|
+
}
|
|
484
|
+
return old
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* An `i64` that a scope's tasks and a parallel body may update together
|
|
490
|
+
* (docs/LANGUAGE.md, "Atomics"). Each method is one indivisible operation:
|
|
491
|
+
* natively the compiler emits each body itself, as a sequentially consistent
|
|
492
|
+
* `load atomic`, `store atomic`, `atomicrmw` or `cmpxchg` on `value`
|
|
493
|
+
* (docs/IR_COOKBOOK.md, "Atomics"), and what is written below is the meaning,
|
|
494
|
+
* which is what runs under Node. Node holds it as a `number`, so `add` and
|
|
495
|
+
* `sub` are `+` and `-` there, and it agrees with native while values stay
|
|
496
|
+
* within ±2^53; wrapping at 2^63 is native-only, the same limit as every `i64`.
|
|
497
|
+
* `add`, `sub`, `min`, `max` and `exchange` answer the value before the
|
|
498
|
+
* update, and `compareExchange` the value it saw.
|
|
499
|
+
*
|
|
500
|
+
* It reaches a task by the routes a `Mutex` does, and a parallel body through
|
|
501
|
+
* a capture; its value is named only through these methods. Results that do
|
|
502
|
+
* not depend on the order the updates land in need the updates to commute,
|
|
503
|
+
* which the checker does not prove.
|
|
504
|
+
*/
|
|
505
|
+
export class AtomicI64 {
|
|
506
|
+
/** The value; named only by this class's methods, which the compiler emits. */
|
|
507
|
+
value: i64
|
|
508
|
+
|
|
509
|
+
constructor(value: i64) {
|
|
510
|
+
this.value = value
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** The value. */
|
|
514
|
+
load(): i64 {
|
|
515
|
+
return this.value
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/** Sets the value to `value`. */
|
|
519
|
+
store(value: i64): void {
|
|
520
|
+
this.value = value
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Adds `delta` and answers the value before; natively the sum wraps at 64 bits. */
|
|
524
|
+
add(delta: i64): i64 {
|
|
525
|
+
const old = this.value
|
|
526
|
+
this.value = old + delta
|
|
527
|
+
return old
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Subtracts `delta` and answers the value before; natively the difference wraps at 64 bits. */
|
|
531
|
+
sub(delta: i64): i64 {
|
|
532
|
+
const old = this.value
|
|
533
|
+
this.value = old - delta
|
|
534
|
+
return old
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Keeps the smaller of the value and `value`, and answers the value before. */
|
|
538
|
+
min(value: i64): i64 {
|
|
539
|
+
const old = this.value
|
|
540
|
+
if (value < old) {
|
|
541
|
+
this.value = value
|
|
542
|
+
}
|
|
543
|
+
return old
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Keeps the larger of the value and `value`, and answers the value before. */
|
|
547
|
+
max(value: i64): i64 {
|
|
548
|
+
const old = this.value
|
|
549
|
+
if (value > old) {
|
|
550
|
+
this.value = value
|
|
551
|
+
}
|
|
552
|
+
return old
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Sets the value to `value` and answers the value before. */
|
|
556
|
+
exchange(value: i64): i64 {
|
|
557
|
+
const old = this.value
|
|
558
|
+
this.value = value
|
|
559
|
+
return old
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Sets the value to `desired` if it is `expected`, and answers the value it saw. */
|
|
563
|
+
compareExchange(expected: i64, desired: i64): i64 {
|
|
564
|
+
const old = this.value
|
|
565
|
+
if (old === expected) {
|
|
566
|
+
this.value = desired
|
|
567
|
+
}
|
|
568
|
+
return old
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* A channel of scalars or strings between a scope's tasks (docs/wp29-thread-surface.md
|
|
574
|
+
* §4.4); a string is copied out of the sender's arena at `send` and into the
|
|
575
|
+
* receiver's at the loop: `send` from the tasks that produce, `for (const x of ch)` in the one
|
|
576
|
+
* that consumes, or in the parent once the scope has joined.
|
|
577
|
+
*
|
|
578
|
+
* const ch = new Channel<i32>()
|
|
579
|
+
* {
|
|
580
|
+
* using s = scope()
|
|
581
|
+
* s.spawn(produce, new Pipe(ch, 100), done, 0) // every sender first
|
|
582
|
+
* s.spawn(consume, new Pipe(ch, 0), sums, 0) // then the one receiver
|
|
583
|
+
* }
|
|
584
|
+
*
|
|
585
|
+
* Nobody closes a channel: it closes when its scope has started to join and
|
|
586
|
+
* every task that may send on it has returned (docs/LANGUAGE.md, "Channels").
|
|
587
|
+
*
|
|
588
|
+
* **What is written here is the meaning under Node**, where a scope's tasks run
|
|
589
|
+
* one at a time at their spawns: `send` pushes onto `queue`, and a loop takes
|
|
590
|
+
* values off its front until it is empty. That is correct because every
|
|
591
|
+
* sender is spawned before the receiver, so every sender has run by the time
|
|
592
|
+
* the receiver's loop starts. Natively the compiler writes `send` and the loop
|
|
593
|
+
* itself (`src/emit-parallel.ts`), onto a buffer the runtime keeps outside
|
|
594
|
+
* every arena behind `state`, and the loop waits while the channel is empty
|
|
595
|
+
* and still open.
|
|
596
|
+
*/
|
|
597
|
+
export class Channel<T> {
|
|
598
|
+
/** Under Node: every value sent, in the order it was sent. */
|
|
599
|
+
queue: T[]
|
|
600
|
+
|
|
601
|
+
/** Under Node: how many of `queue`'s values a loop has taken. */
|
|
602
|
+
head: i32 = 0
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Natively: the runtime's handle on the channel (runtime/runtime-parallel.c),
|
|
606
|
+
* 0 until the channel is first used. Nothing written here reads or writes it.
|
|
607
|
+
*/
|
|
608
|
+
state: i64 = 0
|
|
609
|
+
|
|
610
|
+
constructor() {
|
|
611
|
+
this.queue = []
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Hand `x` to the receiver. It never waits: a channel holds as many values as are sent. */
|
|
615
|
+
send(x: T): void {
|
|
616
|
+
this.queue.push(x)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The next value, for a loop that has found one waiting (`ChannelWalk`).
|
|
621
|
+
* Natively the loop is the compiler's, which takes a value with the runtime
|
|
622
|
+
* instead, and nothing calls this.
|
|
623
|
+
*/
|
|
624
|
+
take(): T {
|
|
625
|
+
const x = this.queue[this.head]
|
|
626
|
+
this.head = this.head + 1
|
|
627
|
+
return x
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** Whether a value is waiting, under Node, where nothing is sent while a loop runs. */
|
|
631
|
+
ready(): boolean {
|
|
632
|
+
return this.head < toI32(this.queue.length)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* What `for (const x of ch)` asks for under Node: a walk that takes each
|
|
637
|
+
* value as it reaches it, so a second loop over the channel finds it empty,
|
|
638
|
+
* as it does natively.
|
|
639
|
+
*/
|
|
640
|
+
[Symbol.iterator](): ChannelWalk<T> {
|
|
641
|
+
return new ChannelWalk<T>(this)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* The iterator a `for...of` over a `Channel` walks under Node. Natively it is
|
|
647
|
+
* never made: the compiler lowers the loop itself.
|
|
648
|
+
*/
|
|
649
|
+
class ChannelWalk<T> {
|
|
650
|
+
channel: Channel<T>
|
|
651
|
+
|
|
652
|
+
constructor(channel: Channel<T>) {
|
|
653
|
+
this.channel = channel
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
next(): IteratorResult<T> {
|
|
657
|
+
if (!this.channel.ready()) {
|
|
658
|
+
return { done: true, value: 0 }
|
|
659
|
+
}
|
|
660
|
+
return { done: false, value: this.channel.take() }
|
|
661
|
+
}
|
|
662
|
+
}
|