@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.
Files changed (104) hide show
  1. package/INSTALL.md +58 -28
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +3 -2
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +99 -5
  22. package/runtime/nish.h +289 -19
  23. package/runtime/nish.mjs +39 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-aes.c +490 -0
  26. package/runtime/runtime-bytes.c +401 -0
  27. package/runtime/runtime-cross.c +366 -0
  28. package/runtime/runtime-host.c +8 -7
  29. package/runtime/runtime-net.c +93 -34
  30. package/runtime/runtime-os.c +214 -16
  31. package/runtime/runtime-parallel.c +590 -107
  32. package/runtime/runtime-simd.c +235 -11
  33. package/runtime/runtime-term.c +504 -0
  34. package/runtime/runtime-wasm.c +5 -14
  35. package/runtime/runtime.c +257 -17
  36. package/runtime/shim.mjs +6 -915
  37. package/runtime/sqlite/LICENSE.md +85 -0
  38. package/runtime/sqlite/README.md +45 -0
  39. package/runtime/sqlite/nish-sqlite.c +359 -0
  40. package/runtime/sqlite/sqlite3.c +271131 -0
  41. package/runtime/sqlite/sqlite3.h +14447 -0
  42. package/scripts/build.sh +437 -28
  43. package/std/LICENSE-fast-float +23 -0
  44. package/std/LICENSE-fdlibm +8 -0
  45. package/std/README.md +108 -58
  46. package/std/base64.ts +116 -0
  47. package/std/bytes.ts +419 -0
  48. package/std/cli.ts +328 -0
  49. package/std/collections.ts +6 -5
  50. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  51. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  52. package/std/crypto/aes.ts +238 -76
  53. package/std/crypto/base64url.ts +125 -0
  54. package/std/crypto/bcrypt.ts +760 -0
  55. package/std/crypto/chacha20poly1305.ts +43 -9
  56. package/std/crypto/ed25519.ts +840 -0
  57. package/std/crypto/hkdf.ts +304 -12
  58. package/std/crypto/hmac.ts +382 -54
  59. package/std/crypto/p256.ts +932 -61
  60. package/std/crypto/x25519.ts +55 -37
  61. package/std/crypto/x509-random.ts +71 -0
  62. package/std/crypto/x509.ts +20 -131
  63. package/std/json-tokenizer.ts +4237 -0
  64. package/std/json-typed.ts +1014 -0
  65. package/std/json-value.ts +1182 -0
  66. package/std/json.ts +1202 -34
  67. package/std/math.ts +608 -0
  68. package/std/net/buffer-pool.ts +151 -0
  69. package/std/net/hpack.ts +416 -112
  70. package/std/net/http-fields.ts +93 -0
  71. package/std/net/http1-server.ts +501 -62
  72. package/std/net/http1.ts +31 -6
  73. package/std/net/http2-tls.ts +112 -24
  74. package/std/net/http2.ts +333 -75
  75. package/std/net/http3-server.ts +261 -43
  76. package/std/net/http3.ts +187 -28
  77. package/std/net/postgres.ts +1875 -0
  78. package/std/net/prefork.ts +412 -0
  79. package/std/net/quic-listener.ts +108 -11
  80. package/std/net/quic-packet.ts +243 -82
  81. package/std/net/quic-recovery.ts +361 -30
  82. package/std/net/quic-stream.ts +424 -24
  83. package/std/net/quic.ts +519 -167
  84. package/std/net/router.ts +676 -0
  85. package/std/net/tls/codec.ts +426 -27
  86. package/std/net/tls/record-server.ts +478 -132
  87. package/std/net/tls/record.ts +133 -42
  88. package/std/net/tls/schedule.ts +464 -55
  89. package/std/net/tls/ticket.ts +610 -0
  90. package/std/net/tls-tcp.ts +196 -53
  91. package/std/net/tls.ts +1018 -260
  92. package/std/net/url.ts +571 -0
  93. package/std/random.ts +94 -0
  94. package/std/regex.ts +4274 -0
  95. package/std/sqlite.ts +391 -0
  96. package/std/term-io.ts +978 -0
  97. package/std/term-pixels.ts +874 -0
  98. package/std/term-probe.ts +459 -0
  99. package/std/term-rate.ts +993 -0
  100. package/std/term.ts +2453 -0
  101. package/std/text.ts +90 -7
  102. package/std/threads.ts +438 -14
  103. package/std/yaml-lines.ts +141 -0
  104. 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, and that
7
- * is a decision rather than a gap: each of those allocates, and several of them
8
- * need a locale or a character table that this runtime has no room for
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 inspects
14
- * characters at all — a UTF-8 string passes through `splitLines` and `split`
15
- * untouched, because a newline and a space cannot appear inside a multi-byte
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, and a scope for heterogeneous tasks
3
- * (docs/wp29-thread-surface.md §4.1 and §4.2).
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 two exported templates by module and name
19
- * and lowers an instance of either onto `nish_parallel_range`
20
- * (runtime/runtime-parallel.c): it replaces the one call that walks the whole
21
- * range — `mapRange` for a map, `reduceBlocks` for a reduce — with a region that
22
- * hands each thread a contiguous piece of it, and emits the rest of the body as
23
- * written. So the length check, its message and the order a reduce combines in
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 or a
31
- * `boolean`. Importing this module compiles the program with `--threads`,
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. Each length is read in the loop condition and `dst`'s again
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
+ }