@yabgu_gm/tensionscript 0.0.0-stage → 0.11.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 (117) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +97 -0
  3. package/README.md +156 -2
  4. package/bin/tensionscript.js +35 -0
  5. package/dist/asc.d.ts +4 -0
  6. package/dist/asc.generated.d.ts +12007 -0
  7. package/dist/asc.js +23474 -0
  8. package/dist/asc.js.map +7 -0
  9. package/dist/assemblyscript.d.ts +4 -0
  10. package/dist/assemblyscript.generated.d.ts +13221 -0
  11. package/dist/assemblyscript.js +337 -0
  12. package/dist/assemblyscript.js.map +7 -0
  13. package/dist/importmap.json +9 -0
  14. package/dist/transform.cjs +1 -0
  15. package/dist/transform.d.ts +1 -0
  16. package/dist/transform.js +1 -0
  17. package/dist/web.js +22 -0
  18. package/lib/binaryen.d.ts +2 -0
  19. package/lib/binaryen.js +2 -0
  20. package/package.json +116 -4
  21. package/std/README.md +6 -0
  22. package/std/assembly/array.ts +526 -0
  23. package/std/assembly/arraybuffer.ts +77 -0
  24. package/std/assembly/atomics.ts +127 -0
  25. package/std/assembly/bindings/asyncify.ts +16 -0
  26. package/std/assembly/bindings/dom.ts +291 -0
  27. package/std/assembly/bindings/node.ts +6 -0
  28. package/std/assembly/builtins.ts +2642 -0
  29. package/std/assembly/closures.ts +22 -0
  30. package/std/assembly/compat.ts +2 -0
  31. package/std/assembly/console.ts +42 -0
  32. package/std/assembly/crypto.ts +9 -0
  33. package/std/assembly/dataview.ts +181 -0
  34. package/std/assembly/date.ts +373 -0
  35. package/std/assembly/diagnostics.ts +11 -0
  36. package/std/assembly/error.ts +44 -0
  37. package/std/assembly/function.ts +38 -0
  38. package/std/assembly/index.d.ts +2721 -0
  39. package/std/assembly/iterator.ts +35 -0
  40. package/std/assembly/map.ts +260 -0
  41. package/std/assembly/math.ts +3289 -0
  42. package/std/assembly/memory.ts +123 -0
  43. package/std/assembly/number.ts +388 -0
  44. package/std/assembly/object.ts +36 -0
  45. package/std/assembly/performance.ts +9 -0
  46. package/std/assembly/polyfills.ts +27 -0
  47. package/std/assembly/process.ts +50 -0
  48. package/std/assembly/reference.ts +48 -0
  49. package/std/assembly/regexp.ts +12 -0
  50. package/std/assembly/rt/README.md +81 -0
  51. package/std/assembly/rt/common.ts +81 -0
  52. package/std/assembly/rt/index-incremental.ts +2 -0
  53. package/std/assembly/rt/index-minimal.ts +2 -0
  54. package/std/assembly/rt/index-stub.ts +1 -0
  55. package/std/assembly/rt/index.d.ts +37 -0
  56. package/std/assembly/rt/itcms.ts +419 -0
  57. package/std/assembly/rt/rtrace.ts +15 -0
  58. package/std/assembly/rt/stub.ts +133 -0
  59. package/std/assembly/rt/tcms.ts +254 -0
  60. package/std/assembly/rt/tlsf.ts +592 -0
  61. package/std/assembly/rt.ts +87 -0
  62. package/std/assembly/set.ts +220 -0
  63. package/std/assembly/shared/feature.ts +65 -0
  64. package/std/assembly/shared/runtime.ts +11 -0
  65. package/std/assembly/shared/target.ts +11 -0
  66. package/std/assembly/shared/tsconfig.json +11 -0
  67. package/std/assembly/shared/typeinfo.ts +72 -0
  68. package/std/assembly/staticarray.ts +401 -0
  69. package/std/assembly/string.ts +847 -0
  70. package/std/assembly/symbol.ts +114 -0
  71. package/std/assembly/table.ts +16 -0
  72. package/std/assembly/task-rt.ts +609 -0
  73. package/std/assembly/task.ts +90 -0
  74. package/std/assembly/tsconfig.json +6 -0
  75. package/std/assembly/typedarray.ts +1945 -0
  76. package/std/assembly/uri.ts +17 -0
  77. package/std/assembly/util/bytes.ts +135 -0
  78. package/std/assembly/util/casemap.ts +497 -0
  79. package/std/assembly/util/error.ts +54 -0
  80. package/std/assembly/util/hash.ts +117 -0
  81. package/std/assembly/util/math.ts +1922 -0
  82. package/std/assembly/util/memory.ts +290 -0
  83. package/std/assembly/util/number.ts +873 -0
  84. package/std/assembly/util/sort.ts +313 -0
  85. package/std/assembly/util/string.ts +1202 -0
  86. package/std/assembly/util/uri.ts +275 -0
  87. package/std/assembly/vector.ts +4 -0
  88. package/std/assembly.json +16 -0
  89. package/std/portable/index.d.ts +462 -0
  90. package/std/portable/index.js +416 -0
  91. package/std/portable.json +11 -0
  92. package/std/types/assembly/index.d.ts +1 -0
  93. package/std/types/assembly/package.json +3 -0
  94. package/std/types/portable/index.d.ts +1 -0
  95. package/std/types/portable/package.json +3 -0
  96. package/tsconfig-base.json +13 -0
  97. package/util/README.md +23 -0
  98. package/util/browser/fs.js +1 -0
  99. package/util/browser/module.js +5 -0
  100. package/util/browser/path.js +520 -0
  101. package/util/browser/process.js +59 -0
  102. package/util/browser/url.js +23 -0
  103. package/util/cpu.d.ts +9 -0
  104. package/util/cpu.js +42 -0
  105. package/util/find.d.ts +6 -0
  106. package/util/find.js +20 -0
  107. package/util/node.d.ts +21 -0
  108. package/util/node.js +34 -0
  109. package/util/options.d.ts +70 -0
  110. package/util/options.js +262 -0
  111. package/util/terminal.d.ts +52 -0
  112. package/util/terminal.js +35 -0
  113. package/util/text.d.ts +26 -0
  114. package/util/text.js +114 -0
  115. package/util/tsconfig.json +9 -0
  116. package/util/web.d.ts +11 -0
  117. package/util/web.js +33 -0
@@ -0,0 +1,609 @@
1
+ /// <reference path="./rt/index.d.ts" />
2
+
3
+ import { TaskRecord } from "./task";
4
+
5
+ /**
6
+ * The guest async runtime's seam — and, since Phase 4, the machine behind it.
7
+ *
8
+ * `docs/async-await-plan-v2.md` §4.7 is the specification, §3.2 the record
9
+ * layout (`std/assembly/task.ts`), §6 the scheduler, §6.4 the dropped-completion
10
+ * rule. The split it pins: **std carries the machine**, the framework connects
11
+ * it to the host's completion stream. The framework's halves are the record
12
+ * subclass (when a capability wants one), the class→family map for its
13
+ * capabilities, and the two call sites — `onBatch`/`onEvent` decoding a
14
+ * delivered record into `__taskDeliver`, and the frame loop calling
15
+ * `__taskTick` (§6.1-§6.3).
16
+ *
17
+ * Why a *top-level* std file (`~lib/task-rt.ts`) and not a subdirectory module:
18
+ * top-level std files are parsed by every build (`cli/index.js:471-479`), and
19
+ * their exports resolve as bare identifiers from any source through the
20
+ * program's global element index (`resolver.ts:1301` — the same fallback that
21
+ * makes `Task` itself resolvable, `program.ts:559-567`). A subdirectory module
22
+ * is parsed only on demand, and the lowering transform cannot create that
23
+ * demand: an import it injects into an already-parsed source is not queued, and
24
+ * the CLI's second `parseBacklog` drains only what the parser queued
25
+ * (`cli/index.js:703-706`). Measured: an injected import naming an unparsed
26
+ * `~lib/` module does not resolve.
27
+ *
28
+ * ## What is implemented, and what Phase 4a does not yet do
29
+ *
30
+ * Implemented (4a): registration, the settled check, parking (which no longer
31
+ * traps), delivery's payload copy, the ready queue and `__taskTick`, the two
32
+ * startup hooks, and `__taskHasRunnable`.
33
+ *
34
+ * Not yet: **§6.4's dropped-completion rule** (4e), and **the status a failure
35
+ * class derives** (§3.4) — both need the framework's class bindings, which 4b
36
+ * adds. A delivered record's `status` is therefore 0 even for a failure class
37
+ * until then; nothing in 4a delivers, so no program can observe it.
38
+ */
39
+
40
+ /**
41
+ * The base class of a task's environment — the object that holds a captured
42
+ * binding's single storage (§4.4: "a captured binding's storage *is* the
43
+ * environment").
44
+ *
45
+ * It declares no fields: the fields belong to whatever the closures transform
46
+ * decided the environment looks like (`src/closure/lower.ts`), and std must not
47
+ * pre-empt that. What std fixes is the *type*, so the state struct's `$env`
48
+ * field has a nameable type to point at, and so the shape does not change when
49
+ * closures land. Being a class-typed field, it is traced by the generated
50
+ * visitor, which is what lets the collector follow state → environment for a
51
+ * parked task (§4.4).
52
+ */
53
+ export class TaskEnv {}
54
+
55
+ /** The base class of every lowered state machine (§4.7.3). */
56
+ export class TaskState {
57
+ /**
58
+ * The task's own record. Assigned by `__taskRegister`; the
59
+ * definite-assignment assertion is what keeps it non-nullable without a dummy
60
+ * initializer (`common.ts:42-43`).
61
+ */
62
+ record!: TaskRecord;
63
+
64
+ /**
65
+ * The function that resumes this task — `f$resume`, assigned by the entry
66
+ * immediately before the synchronous prefix runs (Phase 4 ruling, plan
67
+ * §4.7.3).
68
+ *
69
+ * Without it a parked task has no way back into its own body: the entry calls
70
+ * `f$resume` directly and returns, and nothing else holds a reference to it.
71
+ * The type is the *base* shape `(state: TaskState) => void` because the field
72
+ * is declared once for every state struct; the entry assigns
73
+ * `f$resume` through a `changetype`, which is the same one cast §8.3 already
74
+ * uses for a non-nullable read of a carrier field. A managed function value
75
+ * (every `f$resume` is a top-level synthesized function, so the value is a
76
+ * static `Function<T>` and allocates nothing), traced by the generated
77
+ * visitor like every other reference in §4.4's chain.
78
+ */
79
+ resume!: (state: TaskState) => void;
80
+
81
+ /**
82
+ * The task-table slot this suspension holds, or `-1` when it holds none
83
+ * (0.6.0's push path; `-1` for a task that never parked, or one already
84
+ * resumed).
85
+ *
86
+ * A plain `i32` on the state rather than a `Map<TaskState, i32>`: the state
87
+ * already *is* the key, the value is one word, and a map would pull the whole
88
+ * `Map` machinery into every guest that reaches `__taskTick` — measured at
89
+ * +522 bytes of code in a guest that never parks. The field costs four bytes
90
+ * per state struct instead.
91
+ */
92
+ slot: i32 = -1;
93
+ }
94
+
95
+ // --- the machine's state ----------------------------------------------------
96
+
97
+ /**
98
+ * Creates records: the default allocates the base class, and
99
+ * `__taskBindRecordFactory` replaces it with a capability's subclass (§3.2).
100
+ */
101
+ let recordFactory: (() => TaskRecord) | null = null;
102
+
103
+ /** `class_ → family` (§3.1). Unbound classes are their own family. */
104
+ let families: Map<i32, i32> | null = null;
105
+
106
+ /** `record → the states parked on it`. The rendezvous for a task handle: an
107
+ * `async function`'s own record is awaited through this map alone, and `wake`
108
+ * is what resumes it when that task completes (0.7.0). */
109
+ let waiters: Map<TaskRecord, Array<TaskState>> | null = null;
110
+
111
+ /** States a completion has released, drained at the next host entry
112
+ * (`drainReady`). The guest-side half of the wake, for waiters on a guest
113
+ * task's own record — the host has no key for those. */
114
+ let ready: Array<TaskState> | null = null;
115
+
116
+ /**
117
+ * §3.2's `payload` offset — the record's integer and `bool` result slot.
118
+ *
119
+ * **Derived from the declaration**, not hand-copied: `TaskRecord`
120
+ * (`./task.ts:41-55`) is the layout, and `offsetof` reports it as a compile-time
121
+ * constant (measured: 8, and 32 for the record's size). A field that moved
122
+ * would move this with it; a literal would not.
123
+ *
124
+ * A constant rather than `record.payload = …` at the use sites because the
125
+ * 64-bit path below is generic: `R` is one of `i64`/`u64`/`f64`, which lands
126
+ * across `f0`/`f1` — two declared `f32` fields — so that store is necessarily a
127
+ * raw 8-byte write at `f0`'s offset. `payload` keeps the same spelling as its
128
+ * sibling for symmetry, not because it needs the offset form.
129
+ */
130
+ const RECORD_PAYLOAD: usize = offsetof<TaskRecord>("payload");
131
+ /** §3.2's `f0` offset — `f32` results, and the low half of the 64-bit ones. */
132
+ const RECORD_F0: usize = offsetof<TaskRecord>("f0");
133
+
134
+ // ── the push path (the only path, 0.7.0) ────────────────────────────────────
135
+ //
136
+ // `docs/scheduler-contract.md` §2.1-§2.2 is the specification. What stood beside
137
+ // it — `__taskDeliver`, `__taskTick`, `__taskReportDrops`, the early-completion
138
+ // buffer and the `waitingByKey` index they fed — is gone: the host publishes,
139
+ // matches and wakes, and it attributes drops itself (`0.7.0c`). What remains
140
+ // guest-side is the one rendezvous the host cannot own: two guest tasks, one
141
+ // awaiting the other.
142
+
143
+ /** `scheduler::park(task_id, class, key)` — one call per suspension (§2.1).
144
+ * `void`: a refusal comes back as a completion, never as a return value. */
145
+ @external("scheduler", "park")
146
+ declare function hostPark(taskId: i64, class_: i32, key: i32): void;
147
+
148
+ /**
149
+ * One entry of the guest's task table.
150
+ *
151
+ * **One managed field, and that is load-bearing.** `state` is a class-typed
152
+ * reference, so the compiler-generated visitor traces it, exactly as it traces
153
+ * `Task<T>._record` (`task.ts:30-45`). An opaque `usize` would not be traced,
154
+ * and a parked task's state struct — with its environment and its lifted
155
+ * locals — could be collected while the task was still parked. The record and
156
+ * the resume function are read *through* this reference (`state.record`,
157
+ * `state.resume`), so the slot needs no fields of its own.
158
+ */
159
+ class TaskSlot {
160
+ state: TaskState | null = null;
161
+ /**
162
+ * The record this task parked on (0.7.0c). `__taskFail` needs it: a refused
163
+ * park or a lost completion resumes the task with a *status*, and the status
164
+ * lives on the awaited record — the one the transform's continuation checks
165
+ * and `rt.classify` reads — not on the task's own result record.
166
+ */
167
+ record: TaskRecord | null = null;
168
+ }
169
+
170
+ /** The table: `task_id` is a slot index (§2.1). Allocated at bind time. */
171
+ let slots: Array<TaskSlot> | null = null;
172
+ /** Free slot indices, as a stack. Empty means the table is exhausted. */
173
+ let freeSlots: Array<i32> | null = null;
174
+
175
+ /**
176
+ * Binds the task table: the guest allocates it once at startup and the host
177
+ * never allocates on the guest's behalf (§2.1's "the executor maintains a task
178
+ * table keyed by task_id" — in this implementation the executor's table is the
179
+ * guest's, because the continuations live in guest memory).
180
+ *
181
+ * `capacity` is the number of concurrently parked tasks the guest will allow;
182
+ * a park past it is the **scheduler refusal**, `-ENOSPC` (§5.1). Idempotent: a
183
+ * second bind replaces the table, which discards whatever was parked — so the
184
+ * framework calls it once, at startup.
185
+ */
186
+ export function __taskBindTable(capacity: i32): void {
187
+ if (capacity <= 0) return;
188
+ const table = new Array<TaskSlot>(capacity);
189
+ const free = new Array<i32>(capacity);
190
+ for (let i = 0; i < capacity; ++i) {
191
+ table[i] = new TaskSlot();
192
+ // Descending, so the free stack hands out index 0 first.
193
+ free[i] = capacity - 1 - i;
194
+ }
195
+ slots = table;
196
+ freeSlots = free;
197
+ }
198
+
199
+ /**
200
+ * The push half of a park: allocate a slot, record the state, tell the host.
201
+ *
202
+ * Called from `__taskPark` **after** the pull-path registration, so a guest
203
+ * that binds no table (or a host with no `scheduler::park`) behaves exactly as
204
+ * it did before this release — the function returns early and the pull path is
205
+ * all there is.
206
+ */
207
+ function parkState(state: TaskState, awaited: TaskRecord): void {
208
+ const table = slots;
209
+ if (table == null) return; // no table bound: pull path only
210
+ // A record with no producer stamp has no rendezvous to post: a guest task's
211
+ // own record is resolved by the handle, not by a key.
212
+ if (awaited.class_ == 0) return;
213
+
214
+ // **The slot is already this task's** (0.7.0d-pre). `__taskRegister` claimed it
215
+ // when the task was created and `__taskComplete*` frees it; a park only records
216
+ // the rendezvous. That is what makes -ENOSPC a creation-time answer: by the
217
+ // time a task can park, its slot exists, so the refusal has an id to fail and
218
+ // no task can strand with nowhere to be woken.
219
+ const id = state.slot;
220
+ if (id < 0) return; // registration refused: not runnable
221
+ const slot = table[id];
222
+ slot.state = state;
223
+ slot.record = awaited;
224
+ hostPark(<i64>id, awaited.class_, awaited.key);
225
+ }
226
+
227
+ /**
228
+ * `__taskFail(idsPtr, statusesPtr, count)` — the host names tasks to resume
229
+ * with a status and no payload (§4.4's lost completion, §5.1's refused park).
230
+ *
231
+ * `idsPtr` points at `count` packed `u64` task ids; `statusesPtr` at `count`
232
+ * packed `i32` statuses (a negative errno each). The status goes on the record
233
+ * the task *awaited*: that is the one the transform's settled check reads and
234
+ * the one `rt.classify` reports, so a lost stream signal returns `-EIO` rather
235
+ * than the `0` a bare wake would look like.
236
+ *
237
+ * This is the mechanism `scheduler::park`'s refusal already described in
238
+ * `scheduler.rs` — "the next `wake(ids)` carries the id" — split out so the
239
+ * host has one call for "resume with a status" and another for "resume with a
240
+ * completion", rather than a wake that sometimes means both.
241
+ *
242
+ * An id whose slot is empty is skipped, exactly as `__taskWake` skips one: a
243
+ * stale id must never resume anything twice.
244
+ */
245
+ export function __taskFail(idsPtr: usize, statusesPtr: usize, count: i32): void {
246
+ const table = slots;
247
+ if (table == null || count <= 0) return;
248
+ for (let i = 0; i < count; ++i) {
249
+ const id = <i32>load<u64>(idsPtr + <usize>i * 8);
250
+ if (id < 0 || id >= table.length) continue;
251
+ const slot = table[id];
252
+ const state = slot.state;
253
+ if (state == null) continue; // already resumed: a stale id
254
+ const record = slot.record;
255
+ // Clear the park, keep the slot: the task is still live and will free its
256
+ // slot when its body returns (0.7.0d-pre). Freeing here would let a second
257
+ // task claim the id while this one is still running, and the host's next
258
+ // wake for that id would resume the wrong task.
259
+ slot.state = null;
260
+ slot.record = null;
261
+ if (record != null) {
262
+ // §3.4: a lost completion is a status, not an exception. The payload
263
+ // stays 0 — nothing was delivered — and the guest reads the capability's
264
+ // table to learn what really happened.
265
+ record.status = <i32>load<i32>(statusesPtr + <usize>i * 4);
266
+ record.done = 1;
267
+ }
268
+ state.resume(state);
269
+ }
270
+ drainReady();
271
+ }
272
+
273
+ /**
274
+ * `__taskResume(idsPtr, classesPtr, payloadsPtr, count)` — resume tasks with the
275
+ * completion that woke them (0.7.0d-pre).
276
+ *
277
+ * `__taskWake` names ids and nothing else, which was enough while the guest's
278
+ * own `__taskDeliver` was still filling records from the ring. Under the push
279
+ * path the host is the only thing that knows what arrived, so the record has to
280
+ * come with the wake: `classesPtr` points at `count` packed `i32` class ids and
281
+ * `payloadsPtr` at `count` packed `i32` payload words, both written by the host.
282
+ *
283
+ * Three parallel arrays rather than one packed record, so a chunk costs no more
284
+ * scratch than the ids already do.
285
+ *
286
+ * An id whose slot is empty is skipped: a stale id must never resume a task
287
+ * twice (§1.2).
288
+ */
289
+ export function __taskResume(
290
+ idsPtr: usize,
291
+ classesPtr: usize,
292
+ payloadsPtr: usize,
293
+ count: i32
294
+ ): void {
295
+ const table = slots;
296
+ if (table == null || count <= 0) return;
297
+ for (let i = 0; i < count; ++i) {
298
+ const id = <i32>load<u64>(idsPtr + <usize>i * 8);
299
+ if (id < 0 || id >= table.length) continue;
300
+ const slot = table[id];
301
+ const state = slot.state;
302
+ if (state == null) continue; // already resumed: a stale id
303
+ const record = slot.record;
304
+ // Clear the park, keep the slot (0.7.0d-pre).
305
+ slot.state = null;
306
+ slot.record = null;
307
+ if (record != null) {
308
+ record.class_ = <i32>load<i32>(classesPtr + <usize>i * 4);
309
+ record.payload = <i32>load<i32>(payloadsPtr + <usize>i * 4);
310
+ record.status = 0;
311
+ record.done = 1;
312
+ }
313
+ state.resume(state);
314
+ }
315
+ drainReady();
316
+ }
317
+
318
+ /** Release a slot: the task it held has resumed, and the id may be reused. */
319
+ function releaseSlot(id: i32): void {
320
+ const table = slots;
321
+ const free = freeSlots;
322
+ if (table == null || free == null) return;
323
+ if (id < 0 || id >= table.length) return;
324
+ table[id].state = null;
325
+ table[id].record = null;
326
+ free.push(id);
327
+ }
328
+
329
+ /** Release whatever slot `state` holds, if it holds one. */
330
+ function releaseSlotOf(state: TaskState): void {
331
+ const id = state.slot;
332
+ if (id < 0) return;
333
+ state.slot = -1;
334
+ releaseSlot(id);
335
+ }
336
+
337
+ /**
338
+ * `__taskWake(idsPtr, count)` — the host names the tasks to resume (§2.2).
339
+ *
340
+ * `idsPtr` points at `count` packed little-endian `u64` task ids in guest
341
+ * memory; the host wrote them before the call. Each id is resolved through the
342
+ * table and its continuation invoked **exactly once** (§1.2, §4.1), then the
343
+ * slot is freed so the id can be reused.
344
+ *
345
+ * An id whose slot is empty is skipped, not an error: a stale id can only
346
+ * arrive from a host index that outlived its park (see `scheduler.rs`'s note on
347
+ * overwrite), and waking nothing is the correct behaviour — resuming twice
348
+ * never is.
349
+ */
350
+ export function __taskWake(idsPtr: usize, count: i32): void {
351
+ const table = slots;
352
+ if (table == null || count <= 0) return;
353
+ for (let i = 0; i < count; ++i) {
354
+ const id = <i32>load<u64>(idsPtr + <usize>i * 8);
355
+ if (id < 0 || id >= table.length) continue;
356
+ const slot = table[id];
357
+ const state = slot.state;
358
+ if (state == null) continue; // already resumed: a stale id
359
+ // Clear the park, keep the slot (0.7.0d-pre): the task is live until its
360
+ // body returns, and `__taskComplete*` frees the id then.
361
+ slot.state = null;
362
+ slot.record = null;
363
+ state.resume(state);
364
+ }
365
+ drainReady();
366
+ }
367
+
368
+ /** `(family << 32) | (a & 0xffffffff)`, the correlation key (§3.1). */
369
+ function keyOf(family: i32, a: i32): i64 {
370
+ return (<i64>family << 32) | <i64>(<u32>a);
371
+ }
372
+
373
+ /** The family a class belongs to; an unbound class is its own family (§3.1). */
374
+ function familyOf(class_: i32): i32 {
375
+ let map = families;
376
+ if (map != null && map.has(class_)) {
377
+ return map.get(class_);
378
+ }
379
+ return class_;
380
+ }
381
+
382
+ /** Creates the task record for `state`, roots it, and answers it. */
383
+ export function __taskRegister(state: TaskState): TaskRecord {
384
+ let make = recordFactory;
385
+ let record = make != null ? make() : new TaskRecord();
386
+ state.record = record;
387
+
388
+ // **The slot is claimed here, at creation** (0.7.0d-pre): the table's capacity
389
+ // is a bound on *live* tasks, not on parked ones, and the id is the task's
390
+ // handle from creation to completion. §5.1's refusal therefore has a moment
391
+ // where it can be answered: a task that cannot get an id is settled with
392
+ // -ENOSPC before its body runs, and its caller's `await` falls through on the
393
+ // settled record instead of suspending forever.
394
+ const table = slots;
395
+ const free = freeSlots;
396
+ if (table == null || free == null) return record; // no table bound: pull path
397
+ if (free.length == 0) {
398
+ record.status = -28; // -ENOSPC
399
+ record.done = 1;
400
+ state.slot = -1;
401
+ return record;
402
+ }
403
+ const id = free.pop();
404
+ table[id].state = state;
405
+ table[id].record = null;
406
+ state.slot = id;
407
+ return record;
408
+ }
409
+
410
+ /** Whether this task got a slot — the entry's guard (0.7.0d-pre). A task whose
411
+ * registration was refused must not run its body: its record is already settled
412
+ * with the refusal, and the caller's await has already been answered. */
413
+ export function __taskRegistered(state: TaskState): bool {
414
+ // "May run", not "has a slot" (0.7.0d-pre). A guest that binds no table is on
415
+ // the pull path: `__taskRegister` is a no-op there and every task runs. Only a
416
+ // *refused* registration — a table is bound and no slot was free — is false.
417
+ return state.slot >= 0 || slots == null;
418
+ }
419
+
420
+ /** Frees a task's slot when its body returns. Idempotent. */
421
+ function finishTask(state: TaskState): void {
422
+ const id = state.slot;
423
+ if (id < 0) return;
424
+ state.slot = -1;
425
+ releaseSlot(id);
426
+ }
427
+
428
+ /**
429
+ * Whether `record`'s task is already settled — §1.2 rule 4's check, so a
430
+ * settled `await` returns immediately and does not suspend.
431
+ *
432
+ * `done`, not `status`: §3.2 defines `done` as the settled word (0 = parked,
433
+ * 1 = settled) and `status` as the outcome (0 = success, otherwise the errno).
434
+ * Reading `status` would make every *successful* completion look unsettled.
435
+ */
436
+ export function __taskSettled(record: TaskRecord): bool {
437
+ return record.done != 0;
438
+ }
439
+
440
+ /**
441
+ * Parks `state` on `awaited` until the runtime delivers a completion for it
442
+ * (§3.1, §6.5). Never traps: a task that parks and is never delivered stays
443
+ * parked, which is exactly what the machine must do when no producer exists
444
+ * yet.
445
+ *
446
+ * Called at every suspension, *after* `state.resumePoint` has been set, so a
447
+ * delivery that arrives before the park would still find a valid resume point.
448
+ */
449
+ export function __taskPark(state: TaskState, awaited: TaskRecord): void {
450
+ // The push half is the whole park (0.7.0). `parkState` records the state on
451
+ // the slot the task has held since creation and tells the host, which owns the
452
+ // rendezvous. A record with no producer stamp (`class_ == 0`) is not the host's
453
+ // business — its rendezvous is the handle — so `parkState` returns early and
454
+ // `wake` resumes it when the owning task completes.
455
+ //
456
+ // What the retirement removed: the guest-side early-completion buffer and the
457
+ // `waitingByKey` registration that fed `__taskDeliver`/`__taskTick`. Early
458
+ // completions are the host's now (`scheduler::RecentCompletions`), delivered
459
+ // through `__taskResume`; the pull delivery they existed for is gone.
460
+ let byRecord = waiters;
461
+ if (byRecord == null) {
462
+ byRecord = new Map();
463
+ waiters = byRecord;
464
+ }
465
+ let list: Array<TaskState> | null = null;
466
+ if (byRecord.has(awaited)) list = byRecord.get(awaited);
467
+ if (list == null) {
468
+ list = new Array<TaskState>();
469
+ byRecord.set(awaited, list);
470
+ }
471
+ list.push(state);
472
+ parkState(state, awaited);
473
+ }
474
+
475
+ /**
476
+ * Move the states parked on `record` to the ready list.
477
+ *
478
+ * This is the one rendezvous the host cannot own: `record` is a **guest task's
479
+ * own record** (class 0), so there is no producer key to match and the host was
480
+ * never told. The list is cleared so a second completion for the same record (a
481
+ * re-used slot) does not wake the same task twice.
482
+ *
483
+ * Since 0.7.0 there is no `slot >= 0` filter here. That guard read "the host
484
+ * owns this one", which was true when a slot was allocated at park; pre-park
485
+ * registration gives every task a slot at creation, so the filter dropped every
486
+ * waiter and a task awaiting a *suspending guest task* was resumed by nobody —
487
+ * measured as `guest-chain` hanging.
488
+ */
489
+ function wake(record: TaskRecord): void {
490
+ let byRecord = waiters;
491
+ if (byRecord == null) return;
492
+ let list: Array<TaskState> | null = null;
493
+ if (byRecord.has(record)) list = byRecord.get(record);
494
+ if (list != null && list.length != 0) {
495
+ let queue = ready;
496
+ if (queue == null) {
497
+ queue = new Array<TaskState>();
498
+ ready = queue;
499
+ }
500
+ for (let i = 0, k = list.length; i < k; ++i) queue.push(list[i]);
501
+ list.length = 0;
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Resume the states queued by `wake` — at the executor's entry, never from
507
+ * inside a completing task's own body.
508
+ *
509
+ * Under the pull path `__taskTick` drained this from the guest's frame loop; a
510
+ * host-driven guest never ticks, so the drain moved to the host's own entry
511
+ * points — `__taskWake`, `__taskResume` and `__taskFail` all end here. That keeps
512
+ * every resume at the top of a host call, which is the property the list existed
513
+ * for.
514
+ *
515
+ * Loops until empty: resuming one state can complete another task, whose own
516
+ * waiters are then queued. Each entry is resumed once and the list is emptied
517
+ * before the first resume, so this terminates.
518
+ */
519
+ function drainReady(): void {
520
+ let guard: i32 = 0;
521
+ let pending = ready;
522
+ while (pending != null && pending.length != 0 && guard < 1024) {
523
+ let queue = pending;
524
+ ready = null;
525
+ for (let i = 0, k = queue.length; i < k; ++i) {
526
+ let state = queue[i];
527
+ state.resume(state);
528
+ }
529
+ pending = ready;
530
+ guard += 1;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * The zero of `T`, for the state struct's lifted-local initializers.
536
+ *
537
+ * `changetype<T>(0)` is the only spelling the lowering can emit without knowing
538
+ * whether `T` is a float, an integer or a reference — and it does not compile
539
+ * for a float (measured: AS202, an i32 literal changed to f64). The branches
540
+ * here are compile-time constants per instantiation, so exactly one line of
541
+ * each is emitted.
542
+ */
543
+ export function __asyncZero<T>(): T {
544
+ if (isFloat<T>()) return <T>0.0;
545
+ if (isInteger<T>()) return <T>0;
546
+ return changetype<T>(0);
547
+ }
548
+
549
+ /**
550
+ * Completes a task from inside the guest: `done`, `status = 0`, and the states
551
+ * parked on its record are queued for resume (§4.7.3, Phase 4b).
552
+ *
553
+ * The value half is {@link __taskCompleteResult}; this is what a `void` body's
554
+ * terminal calls. Split because `T` has to be named at the call site.
555
+ */
556
+ export function __taskComplete(state: TaskState): void {
557
+ let record = state.record;
558
+ record.status = 0;
559
+ record.done = 1;
560
+ finishTask(state);
561
+ wake(record);
562
+ }
563
+
564
+ /**
565
+ * Completes a task with a value: stores it into the record at §3.2's offset for
566
+ * `R`, then {@link __taskComplete}.
567
+ *
568
+ * The offset mapping is codegen's read, mirrored exactly (`compiler.ts:8025-8027`):
569
+ * `f32` and the 64-bit kinds live at `f0` (16), everything narrower at `payload`
570
+ * (8). It is decided by `sizeof`/`isFloat` rather than by a name here, so a
571
+ * type alias resolves the same way it does at the read — the reason this is
572
+ * generic rather than a store the transform emits itself.
573
+ */
574
+ export function __taskCompleteResult<R>(state: TaskState, value: R): void {
575
+ let record = state.record;
576
+ let base = changetype<usize>(record);
577
+ if (sizeof<R>() == 4 && isFloat<R>()) {
578
+ store<f32>(base + RECORD_F0, <f32>value);
579
+ } else if (sizeof<R>() > 4) {
580
+ store<R>(base + RECORD_F0, value);
581
+ } else {
582
+ store<R>(base + RECORD_PAYLOAD, value);
583
+ }
584
+ __taskComplete(state);
585
+ }
586
+
587
+ /** Whether any task is ready to resume — the frame loop's `wait(0)` test (§6.3). */
588
+ export function __taskHasRunnable(): bool {
589
+ let queue = ready;
590
+ return queue != null && queue.length != 0;
591
+ }
592
+
593
+ /** The framework's startup hook: the record subclass `__taskRegister` allocates. */
594
+ export function __taskBindRecordFactory(factory: () => TaskRecord): void {
595
+ recordFactory = factory;
596
+ }
597
+
598
+ /**
599
+ * The framework's startup hook: which completion classes resolve one await
600
+ * (§3.1). `JOB_DONE`/`JOB_FAILED` are one family; an unbound class is its own.
601
+ */
602
+ export function __taskBindFamily(class_: i32, family: i32): void {
603
+ let map = families;
604
+ if (map == null) {
605
+ map = new Map();
606
+ families = map;
607
+ }
608
+ map.set(class_, family);
609
+ }