@amritk/nish-x86_64-linux 0.17.0 → 0.20.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 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +231 -20
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- 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 +245 -53
- package/std/crypto/p256.ts +1376 -42
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1412 -70
- 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 +417 -55
- 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 +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- 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 +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
package/std/text.ts
CHANGED
|
@@ -191,3 +191,80 @@ export const firstDifference = (left: string[], right: string[]): i32 => {
|
|
|
191
191
|
}
|
|
192
192
|
return leftLength === rightLength ? -1 : i
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** The most bytes `indexOfAny` takes in its set: what one 16-byte vector of the runtime kernel compares. */
|
|
196
|
+
const INDEX_OF_ANY_MAX_SET: i32 = 16
|
|
197
|
+
|
|
198
|
+
/** The first byte that is not ASCII; `indexOfAny`'s set must stay below it. */
|
|
199
|
+
const NON_ASCII: i32 = 128
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The first index at or after `from` whose byte is one of `bytes`, or -1.
|
|
203
|
+
*
|
|
204
|
+
* `bytes` is a set of 1 to 16 ASCII bytes, and anything else panics: an empty
|
|
205
|
+
* set matches nothing, more than 16 is more than one vector of the runtime
|
|
206
|
+
* kernel compares, and an ASCII set is what keeps the answer the same under Node,
|
|
207
|
+
* where `charCodeAt` reads a UTF-16 unit rather than a byte. An ASCII byte
|
|
208
|
+
* never occurs inside a multi-byte UTF-8 sequence, so a non-ASCII `text`
|
|
209
|
+
* passes through and only its ASCII bytes can match — as in `splitLines`, the
|
|
210
|
+
* index is a byte offset natively. `from` is clamped to `[0, text.length]`,
|
|
211
|
+
* as `substring` clamps, so a negative `from` searches from the start and one
|
|
212
|
+
* past the end answers -1.
|
|
213
|
+
*
|
|
214
|
+
* Natively the search is the runtime's `nish_str_index_of_any`, which reads
|
|
215
|
+
* 16 or 32 bytes at a time (docs/LANGUAGE.md, "Byte search"). It is
|
|
216
|
+
* **variable-time**: how long it takes says where the first match is, so it is
|
|
217
|
+
* never for secret data.
|
|
218
|
+
*/
|
|
219
|
+
export const indexOfAny = (text: string, bytes: string, from: i32): i32 => {
|
|
220
|
+
const count: i32 = toI32(bytes.length)
|
|
221
|
+
if (count < 1 || count > INDEX_OF_ANY_MAX_SET) {
|
|
222
|
+
panic("indexOfAny: the set must hold 1 to 16 bytes")
|
|
223
|
+
}
|
|
224
|
+
let j: i32 = 0
|
|
225
|
+
while (j < count) {
|
|
226
|
+
if (toI32(bytes.charCodeAt(j)) >= NON_ASCII) {
|
|
227
|
+
panic("indexOfAny: every byte of the set must be ASCII")
|
|
228
|
+
}
|
|
229
|
+
j += 1
|
|
230
|
+
}
|
|
231
|
+
const length: i32 = toI32(text.length)
|
|
232
|
+
let start: i32 = from
|
|
233
|
+
if (start < 0) {
|
|
234
|
+
start = 0
|
|
235
|
+
}
|
|
236
|
+
if (start > length) {
|
|
237
|
+
start = length
|
|
238
|
+
}
|
|
239
|
+
return indexOfAnyFrom(text, bytes, start)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* `indexOfAny`'s walk, once its arguments are checked: the first index at or
|
|
244
|
+
* after `from` whose byte is in `bytes`, or -1.
|
|
245
|
+
*
|
|
246
|
+
* This loop is what runs under Node. Natively the compiler recognises the one
|
|
247
|
+
* call `indexOfAny` makes to it and calls the runtime kernel instead
|
|
248
|
+
* (`src/kernels.ts`), so the loop is the kernel's meaning written in the
|
|
249
|
+
* language rather than its implementation. The name is long for the reason
|
|
250
|
+
* `isTextBlankByte`'s is.
|
|
251
|
+
*/
|
|
252
|
+
const indexOfAnyFrom = (text: string, bytes: string, from: i32): i32 => {
|
|
253
|
+
const length: i32 = toI32(text.length)
|
|
254
|
+
const count: i32 = toI32(bytes.length)
|
|
255
|
+
let i: i32 = from
|
|
256
|
+
// `from` is never negative, because `indexOfAny` clamped it; the test is
|
|
257
|
+
// written because it is what proves `i` within `text`, as in `splitLines`.
|
|
258
|
+
while (i >= 0 && i < length) {
|
|
259
|
+
const code: i32 = toI32(text.charCodeAt(i))
|
|
260
|
+
let j: i32 = 0
|
|
261
|
+
while (j < count) {
|
|
262
|
+
if (toI32(bytes.charCodeAt(j)) === code) {
|
|
263
|
+
return i
|
|
264
|
+
}
|
|
265
|
+
j += 1
|
|
266
|
+
}
|
|
267
|
+
i += 1
|
|
268
|
+
}
|
|
269
|
+
return -1
|
|
270
|
+
}
|
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
|
+
}
|