@amritk/nish-x86_64-linux 0.28.0 → 0.29.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 +8 -5
- package/bin/nish +0 -0
- package/docs/AI.md +99 -24
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/nish.d.ts +127 -0
- package/runtime/nish.h +72 -3
- package/runtime/nish.mjs +18 -4
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +60 -5
- package/runtime/runtime-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +42 -12
- package/std/README.md +10 -3
- package/std/collections.ts +5 -5
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-typed.ts +34 -0
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +54 -12
- package/std/net/http1-server.ts +77 -23
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/prefork.ts +344 -20
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/sync/shm.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/sync/shm` — memory that several processes map at once, and the
|
|
3
|
+
* atomics that order what they write to it.
|
|
4
|
+
*
|
|
5
|
+
* import { createRegion, openRegion, unlinkRegion, ACQUIRE, RELEASE } from "nish/sync/shm"
|
|
6
|
+
*
|
|
7
|
+
* // The process that owns it: a fresh, zeroed region of 4096 bytes.
|
|
8
|
+
* const stale = unlinkRegion("jobs") // a region a crashed run left, if any
|
|
9
|
+
* const region = createRegion("jobs", 4096).expect("create")
|
|
10
|
+
* region.storeI64(0, toI64(42), RELEASE)
|
|
11
|
+
*
|
|
12
|
+
* // Any other process of the same user: the same bytes.
|
|
13
|
+
* const view = openRegion("jobs").expect("open")
|
|
14
|
+
* const n: i64 = view.loadI64(0, ACQUIRE) // 42 once the store is seen
|
|
15
|
+
*
|
|
16
|
+
* **A region is a named POSIX shared-memory object** (`/dev/shm/<name>` on
|
|
17
|
+
* Linux, `shm_open("/<name>")` on Darwin) mapped shared, so every process that
|
|
18
|
+
* opens the name sees the same bytes. A name is 1 to 30 of `A-Z a-z 0-9 . _ -`,
|
|
19
|
+
* not starting with `.`. `createRegion` makes it with mode 0600 and refuses a
|
|
20
|
+
* name that exists (`SHM_EXISTS`), so a name another user planted is never
|
|
21
|
+
* adopted; `openRegion` refuses a region this user does not own, that anyone
|
|
22
|
+
* else may read or write, or (on Linux) that is not a regular file or has a
|
|
23
|
+
* second hard link (`SHM_ACCESS`). The name stays until `unlinkRegion`,
|
|
24
|
+
* even after every process has exited, which is how a process that starts later
|
|
25
|
+
* finds it; the mapping goes away with `close` or with the process.
|
|
26
|
+
*
|
|
27
|
+
* **Why named, and no anonymous region made before `fork`.** A Nish program
|
|
28
|
+
* has no `fork`: `spawn` and `nish/net/prefork` start a program with `exec`,
|
|
29
|
+
* and every descriptor is close-on-exec, so neither an anonymous mapping nor a
|
|
30
|
+
* `memfd` reaches the child. A name does. The supervisor creates the region
|
|
31
|
+
* before `preforkWith`, every worker opens it by name, and the supervisor
|
|
32
|
+
* unlinks it after (`examples/prefork-jobs.ts`, docs/wp47-shared-memory.md).
|
|
33
|
+
*
|
|
34
|
+
* **Every access is checked.** An offset is an `i32` byte offset into the
|
|
35
|
+
* region. A 32-bit slot sits at a multiple of 4 and a 64-bit one at a multiple
|
|
36
|
+
* of 8, wholly inside the region; a byte copy fits the region and the buffer.
|
|
37
|
+
* Anything else panics, naming the offset and the region's size, exactly as an
|
|
38
|
+
* array index out of range does. An ordering an access cannot take (a load
|
|
39
|
+
* with `RELEASE`) panics too. A region is never read or written past its end.
|
|
40
|
+
*
|
|
41
|
+
* **Orderings are C11's.** `RELAXED`, `ACQUIRE`, `RELEASE`, `ACQ_REL` and
|
|
42
|
+
* `SEQ_CST`; a load takes the first, second and last, a store the first, third
|
|
43
|
+
* and last, and a read-modify-write any of them. Each access is one LLVM
|
|
44
|
+
* atomic instruction on the speed and size profiles, where the runtime is
|
|
45
|
+
* inlined into the program; a compare-exchange that fails orders as C++
|
|
46
|
+
* derives it (`ACQ_REL` fails `ACQUIRE`, `RELEASE` fails `RELAXED`). Bytes
|
|
47
|
+
* copied by `read` and `write` are plain copies, ordered by the atomics around
|
|
48
|
+
* them, and a copy that races a writer in another process can be torn but is
|
|
49
|
+
* never read past its window.
|
|
50
|
+
*
|
|
51
|
+
* **Waiting.** `waitI32` sleeps while a 32-bit slot holds a value and `wake`
|
|
52
|
+
* wakes the processes sleeping on it: Linux's futex, shared between processes.
|
|
53
|
+
* Darwin and wasi answer `SHM_UNSUPPORTED` (-95) from both, as
|
|
54
|
+
* `setIdlePriority` does on Darwin, and a program there polls instead.
|
|
55
|
+
*
|
|
56
|
+
* **A region belongs to the thread that mapped it.** Under `--threads` the
|
|
57
|
+
* runtime's table of regions is thread-local, so a `SharedRegion` handed to
|
|
58
|
+
* another thread names nothing there and panics; that thread opens the name
|
|
59
|
+
* again. Native only: `--emit-ts` refuses a module that declares C functions,
|
|
60
|
+
* and creating a region on wasi answers `SHM_UNSUPPORTED`. Calling C is the
|
|
61
|
+
* `ffi` capability, so a program that imports this module is reported with
|
|
62
|
+
* `ffi` and `--deny ffi` refuses it.
|
|
63
|
+
*
|
|
64
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
65
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
// biome-ignore-all lint/style/useNamingConvention: the declarations name C symbols
|
|
69
|
+
// runtime/runtime-shm.c. The names are `nishshm_`, not `nish_shm_`: `nish_`
|
|
70
|
+
// names are the runtime's own (NL2202).
|
|
71
|
+
declare function nishshm_create(name: string, bytes: i32): i32
|
|
72
|
+
declare function nishshm_open(name: string): i32
|
|
73
|
+
declare function nishshm_unlink(name: string): i32
|
|
74
|
+
declare function nishshm_close(h: i32): i32
|
|
75
|
+
declare function nishshm_size(h: i32): i32
|
|
76
|
+
declare function nishshm_load_i32(h: i32, at: i32, order: i32): i32
|
|
77
|
+
declare function nishshm_store_i32(h: i32, at: i32, v: i32, order: i32): void
|
|
78
|
+
declare function nishshm_add_i32(h: i32, at: i32, delta: i32, order: i32): i32
|
|
79
|
+
declare function nishshm_exchange_i32(h: i32, at: i32, v: i32, order: i32): i32
|
|
80
|
+
declare function nishshm_cas_i32(h: i32, at: i32, expected: i32, desired: i32, order: i32): i32
|
|
81
|
+
declare function nishshm_load_i64(h: i32, at: i32, order: i32): i64
|
|
82
|
+
declare function nishshm_store_i64(h: i32, at: i32, v: i64, order: i32): void
|
|
83
|
+
declare function nishshm_add_i64(h: i32, at: i32, delta: i64, order: i32): i64
|
|
84
|
+
declare function nishshm_exchange_i64(h: i32, at: i32, v: i64, order: i32): i64
|
|
85
|
+
declare function nishshm_cas_i64(h: i32, at: i32, expected: i64, desired: i64, order: i32): i64
|
|
86
|
+
declare function nishshm_fence(order: i32): void
|
|
87
|
+
declare function nishshm_read(h: i32, at: i32, dst: u8[], n: u64, off: u64, len: u64): void
|
|
88
|
+
declare function nishshm_write(h: i32, at: i32, src: readonly u8[], n: u64, off: u64, len: u64): void
|
|
89
|
+
declare function nishshm_wait_i32(h: i32, at: i32, expected: i32, timeoutMs: i32): i32
|
|
90
|
+
declare function nishshm_wake(h: i32, at: i32, count: i32): i32
|
|
91
|
+
declare function strerror(errnum: i32): string
|
|
92
|
+
|
|
93
|
+
/** No ordering beyond the access itself being whole. */
|
|
94
|
+
export const RELAXED: i32 = 0
|
|
95
|
+
/** A load that sees everything written before the `RELEASE` store it reads. */
|
|
96
|
+
export const ACQUIRE: i32 = 2
|
|
97
|
+
/** A store after which everything written before it is seen by an `ACQUIRE` load of it. */
|
|
98
|
+
export const RELEASE: i32 = 3
|
|
99
|
+
/** A read-modify-write that is both. */
|
|
100
|
+
export const ACQ_REL: i32 = 4
|
|
101
|
+
/** One order every `SEQ_CST` access in every process agrees on. */
|
|
102
|
+
export const SEQ_CST: i32 = 5
|
|
103
|
+
|
|
104
|
+
/** `waitI32`: woken, or for no reason the caller can see; load the slot again. */
|
|
105
|
+
export const SHM_WOKEN: i32 = 0
|
|
106
|
+
/** `waitI32`: the slot did not hold the value, so it did not sleep. */
|
|
107
|
+
export const SHM_NOT_EQUAL: i32 = -11
|
|
108
|
+
/** `waitI32`: the time ran out. */
|
|
109
|
+
export const SHM_TIMED_OUT: i32 = -110
|
|
110
|
+
/** `waitI32` and `wake` on Darwin and wasi, and every region on wasi: not here. */
|
|
111
|
+
export const SHM_UNSUPPORTED: i32 = -95
|
|
112
|
+
|
|
113
|
+
/** `ShmError.code`: no region of that name. */
|
|
114
|
+
export const SHM_NOT_FOUND: i32 = 2
|
|
115
|
+
/** `ShmError.code`: another user's region, one others may use, or not one region under one name. */
|
|
116
|
+
export const SHM_ACCESS: i32 = 13
|
|
117
|
+
/** `ShmError.code`: a region of that name exists already. */
|
|
118
|
+
export const SHM_EXISTS: i32 = 17
|
|
119
|
+
/** `ShmError.code`: a name that is not 1 to 30 of `A-Za-z0-9._-`, or a size outside 1 byte to 1 GiB. */
|
|
120
|
+
export const SHM_INVALID: i32 = 22
|
|
121
|
+
/** `ShmError.code`: this thread has 63 regions open. */
|
|
122
|
+
export const SHM_TOO_MANY: i32 = 24
|
|
123
|
+
|
|
124
|
+
/** Why a region could not be created, opened, unlinked or closed. */
|
|
125
|
+
export class ShmError {
|
|
126
|
+
/** The errno, positive: `SHM_NOT_FOUND`, `SHM_EXISTS` and the rest. */
|
|
127
|
+
code: i32
|
|
128
|
+
/** What it means, such as `File exists`. */
|
|
129
|
+
message: string
|
|
130
|
+
|
|
131
|
+
constructor(code: i32) {
|
|
132
|
+
this.code = code
|
|
133
|
+
this.message = code === 95 ? "not supported on this platform" : strerror(code)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** `Ok()` for 0, the error for a negated errno. */
|
|
138
|
+
const shmDone = (rc: i32): Result<void, ShmError> => {
|
|
139
|
+
if (rc < 0) {
|
|
140
|
+
return Err(new ShmError(-rc))
|
|
141
|
+
}
|
|
142
|
+
return Ok()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Creates the region `name`, `bytes` long and zeroed, mode 0600, and maps it.
|
|
147
|
+
* `SHM_EXISTS` when the name is taken: unlink a stale one first.
|
|
148
|
+
*/
|
|
149
|
+
export const createRegion = (name: string, bytes: i32): Result<SharedRegion, ShmError> => {
|
|
150
|
+
const h: i32 = nishshm_create(name, bytes)
|
|
151
|
+
if (h < 0) {
|
|
152
|
+
return Err(new ShmError(-h))
|
|
153
|
+
}
|
|
154
|
+
return Ok(new SharedRegion(h))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Maps the region `name` that some process of this user created. Its size is
|
|
159
|
+
* the region's (on Darwin, rounded up to a page).
|
|
160
|
+
*/
|
|
161
|
+
export const openRegion = (name: string): Result<SharedRegion, ShmError> => {
|
|
162
|
+
const h: i32 = nishshm_open(name)
|
|
163
|
+
if (h < 0) {
|
|
164
|
+
return Err(new ShmError(-h))
|
|
165
|
+
}
|
|
166
|
+
return Ok(new SharedRegion(h))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Removes the name, so no process can open it again; every mapping of it
|
|
171
|
+
* stays valid until it is closed. `SHM_NOT_FOUND` when there is none.
|
|
172
|
+
*/
|
|
173
|
+
export const unlinkRegion = (name: string): Result<void, ShmError> => shmDone(nishshm_unlink(name))
|
|
174
|
+
|
|
175
|
+
/** A fence of `order`, for the accesses around it. */
|
|
176
|
+
export const fence = (order: i32): void => {
|
|
177
|
+
nishshm_fence(order)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A mapped region. Every slot offset is in bytes from its start; an offset
|
|
182
|
+
* outside it, a misaligned slot or an ordering the access cannot take panics.
|
|
183
|
+
*/
|
|
184
|
+
export class SharedRegion {
|
|
185
|
+
/** The runtime's handle; it names nothing after `close`. */
|
|
186
|
+
handle: i32
|
|
187
|
+
|
|
188
|
+
constructor(handle: i32) {
|
|
189
|
+
this.handle = handle
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The region's length in bytes. */
|
|
193
|
+
size(): i32 {
|
|
194
|
+
return nishshm_size(this.handle)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The 32-bit slot at `at`. */
|
|
198
|
+
loadI32(at: i32, order: i32): i32 {
|
|
199
|
+
return nishshm_load_i32(this.handle, at, order)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Sets the 32-bit slot at `at` to `value`. */
|
|
203
|
+
storeI32(at: i32, value: i32, order: i32): void {
|
|
204
|
+
nishshm_store_i32(this.handle, at, value, order)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Adds `delta` to the 32-bit slot at `at`, wrapping, and answers the value before. */
|
|
208
|
+
addI32(at: i32, delta: i32, order: i32): i32 {
|
|
209
|
+
return nishshm_add_i32(this.handle, at, delta, order)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Sets the 32-bit slot at `at` to `value` and answers the value before. */
|
|
213
|
+
exchangeI32(at: i32, value: i32, order: i32): i32 {
|
|
214
|
+
return nishshm_exchange_i32(this.handle, at, value, order)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Sets the 32-bit slot at `at` to `desired` if it holds `expected`, and
|
|
219
|
+
* answers what it held: the exchange happened when that is `expected`.
|
|
220
|
+
*/
|
|
221
|
+
compareExchangeI32(at: i32, expected: i32, desired: i32, order: i32): i32 {
|
|
222
|
+
return nishshm_cas_i32(this.handle, at, expected, desired, order)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The 64-bit slot at `at`. */
|
|
226
|
+
loadI64(at: i32, order: i32): i64 {
|
|
227
|
+
return nishshm_load_i64(this.handle, at, order)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Sets the 64-bit slot at `at` to `value`. */
|
|
231
|
+
storeI64(at: i32, value: i64, order: i32): void {
|
|
232
|
+
nishshm_store_i64(this.handle, at, value, order)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Adds `delta` to the 64-bit slot at `at`, wrapping, and answers the value before. */
|
|
236
|
+
addI64(at: i32, delta: i64, order: i32): i64 {
|
|
237
|
+
return nishshm_add_i64(this.handle, at, delta, order)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Sets the 64-bit slot at `at` to `value` and answers the value before. */
|
|
241
|
+
exchangeI64(at: i32, value: i64, order: i32): i64 {
|
|
242
|
+
return nishshm_exchange_i64(this.handle, at, value, order)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sets the 64-bit slot at `at` to `desired` if it holds `expected`, and
|
|
247
|
+
* answers what it held: the exchange happened when that is `expected`.
|
|
248
|
+
*/
|
|
249
|
+
compareExchangeI64(at: i32, expected: i64, desired: i64, order: i32): i64 {
|
|
250
|
+
return nishshm_cas_i64(this.handle, at, expected, desired, order)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Copies `len` bytes of the region at `at` into `dst[off, off + len)`. */
|
|
254
|
+
read(at: i32, dst: u8[], off: i32, len: i32): void {
|
|
255
|
+
nishshm_read(this.handle, at, dst, toU64(dst.length), toU64(off), toU64(len))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Copies `src[off, off + len)` into the region at `at`. */
|
|
259
|
+
write(at: i32, src: readonly u8[], off: i32, len: i32): void {
|
|
260
|
+
nishshm_write(this.handle, at, src, toU64(src.length), toU64(off), toU64(len))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Sleeps while the 32-bit slot at `at` holds `expected`, at most
|
|
265
|
+
* `timeoutMs` (negative: no limit): `SHM_WOKEN`, `SHM_NOT_EQUAL`,
|
|
266
|
+
* `SHM_TIMED_OUT`, or `SHM_UNSUPPORTED` off Linux.
|
|
267
|
+
*/
|
|
268
|
+
waitI32(at: i32, expected: i32, timeoutMs: i32): i32 {
|
|
269
|
+
return nishshm_wait_i32(this.handle, at, expected, timeoutMs)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Wakes at most `count` of the processes sleeping on the 32-bit slot at
|
|
274
|
+
* `at`: how many it woke, or `SHM_UNSUPPORTED` off Linux.
|
|
275
|
+
*/
|
|
276
|
+
wake(at: i32, count: i32): i32 {
|
|
277
|
+
return nishshm_wake(this.handle, at, count)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Unmaps the region; the handle names nothing after. The name stays until `unlinkRegion`. */
|
|
281
|
+
close(): Result<void, ShmError> {
|
|
282
|
+
return shmDone(nishshm_close(this.handle))
|
|
283
|
+
}
|
|
284
|
+
}
|
package/std/term-rate.ts
CHANGED
|
@@ -96,6 +96,13 @@
|
|
|
96
96
|
* next decision. Falling fast and rising slowly keeps a flickering link from
|
|
97
97
|
* flipping the rate.
|
|
98
98
|
*
|
|
99
|
+
* It starts at the best tier on a local terminal: the probe answered under
|
|
100
|
+
* 5 ms, or no `SSH_*` hint and no slow answer. Falling costs half a second and
|
|
101
|
+
* rising one tier costs three, so a terminal that keeps up is never made to
|
|
102
|
+
* climb. A probe that took 5 ms or more starts at 30, and a remote session
|
|
103
|
+
* (the hint, and no fast answer) at 15, as a backlog on a slow link takes far
|
|
104
|
+
* longer to drain than to avoid.
|
|
105
|
+
*
|
|
99
106
|
* Below the floor tier (the least tier at or above `minFps`, under
|
|
100
107
|
* `RATE_FIRST`) the controller cuts bytes instead of frames, one step at a
|
|
101
108
|
* time: half the resolution, a quarter, the backend capped at half blocks, 256
|
|
@@ -431,7 +438,12 @@ export class RateController {
|
|
|
431
438
|
this.floorFps = this.prefer === DETAIL_FIRST ? 5 : minTier
|
|
432
439
|
const remote: boolean =
|
|
433
440
|
options.remoteHint && !(options.probeRttMs > 0.0 && options.probeRttMs < rateLocalRttMs)
|
|
434
|
-
|
|
441
|
+
let want: i32 = this.tiers[0]
|
|
442
|
+
if (remote) {
|
|
443
|
+
want = 15
|
|
444
|
+
} else if (options.probeRttMs >= rateLocalRttMs) {
|
|
445
|
+
want = 30
|
|
446
|
+
}
|
|
435
447
|
let start: i32 = this.tiers[this.tiers.length - 1]
|
|
436
448
|
for (const t of this.tiers) {
|
|
437
449
|
if (t <= want && t > start) {
|
package/std/threads.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `std/threads` — data parallelism, a scope for heterogeneous tasks, and a
|
|
3
3
|
* lock that owns its data (docs/wp29-thread-surface.md §4.1, §4.2 and §4.3).
|
|
4
4
|
*
|
|
5
|
-
* import { AtomicI32, Mutex, parallelFrom, parallelMap, parallelMapInto, parallelReduce, scope } from "nish/threads";
|
|
5
|
+
* import { AtomicI32, Mutex, parallelFrom, parallelMap, parallelMapInto, parallelReduce, scope, scopeWithStack } from "nish/threads";
|
|
6
6
|
*
|
|
7
7
|
* parallelMapInto(src, dst, (x) => x * 3);
|
|
8
8
|
* const ys = parallelMap(src, (x) => x * k + b); // k, b: `const` locals, read by the body
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* s.spawn(sumOf, xs, out, 0);
|
|
14
14
|
* s.spawn(maxOf, ys, out, 1);
|
|
15
15
|
* } // both tasks have run, and `out` holds both answers
|
|
16
|
+
* {
|
|
17
|
+
* using s = scopeWithStack(64 * 1024 * 1024); // each task's thread has a 64 MiB stack
|
|
18
|
+
* s.spawn(checkModule, ast, out, 0);
|
|
19
|
+
* }
|
|
16
20
|
*
|
|
17
21
|
* **What is written here is the meaning, not the implementation.** Each body
|
|
18
22
|
* below is the sequential program, and it is what runs under Node, what
|
|
@@ -280,8 +284,11 @@ const slotWritten = (at: i32): boolean => at < 0 || at >= 0
|
|
|
280
284
|
*/
|
|
281
285
|
export class ThreadScope {
|
|
282
286
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
287
|
+
* The stack, in bytes, of each thread the scope's tasks run on, or 0 for the
|
|
288
|
+
* platform's default. Only `scopeWithStack` writes it, and only
|
|
289
|
+
* `nish_scope_join` (runtime/runtime-parallel.c) reads it, as the scope's
|
|
290
|
+
* first word. It also makes each scope an object of its own, whose address is
|
|
291
|
+
* what the runtime files the scope's tasks under.
|
|
285
292
|
*/
|
|
286
293
|
tag: i32 = 0
|
|
287
294
|
|
|
@@ -338,6 +345,31 @@ export class ThreadScope {
|
|
|
338
345
|
/** A new scope, for a `using` declaration: `using s = scope()`. */
|
|
339
346
|
export const scope = (): ThreadScope => new ThreadScope()
|
|
340
347
|
|
|
348
|
+
/**
|
|
349
|
+
* The smallest stack `scopeWithStack` takes: 128 KiB, the largest
|
|
350
|
+
* `PTHREAD_STACK_MIN` among the targets (aarch64 Linux). A constant below it
|
|
351
|
+
* is refused at compile time (NL2584); a computed one panics here, under Node
|
|
352
|
+
* as natively.
|
|
353
|
+
*/
|
|
354
|
+
const MIN_STACK: i32 = 131072
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* A new scope whose tasks each run on a thread with a stack of at least
|
|
358
|
+
* `bytes` bytes, for a `using` declaration: `using s = scopeWithStack(n)`.
|
|
359
|
+
* The tasks run on new threads, never on the one that joins. The runtime
|
|
360
|
+
* rounds `bytes` up to the page size and adds a guard page below it, so a task
|
|
361
|
+
* that overflows it faults instead of writing past it. Under Node, and on a
|
|
362
|
+
* target without threads, the size is checked and has no other effect.
|
|
363
|
+
*/
|
|
364
|
+
export const scopeWithStack = (bytes: i32): ThreadScope => {
|
|
365
|
+
if (bytes < MIN_STACK) {
|
|
366
|
+
panic(`scopeWithStack: a stack of ${bytes} bytes is below the minimum of ${MIN_STACK}`)
|
|
367
|
+
}
|
|
368
|
+
const s = scope()
|
|
369
|
+
s.tag = bytes
|
|
370
|
+
return s
|
|
371
|
+
}
|
|
372
|
+
|
|
341
373
|
/**
|
|
342
374
|
* What a `Mutex` hands out: its data, as `value`, for as long as the block of
|
|
343
375
|
* the `using` declaration that took the lock (docs/LANGUAGE.md, "A lock that
|
|
File without changes
|