@swf2es/codegen 0.0.0-stage → 0.1.1

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/src/index.ts ADDED
@@ -0,0 +1,738 @@
1
+ /**
2
+ * Compiles ABC and AVM1 bytecode to ES modules.
3
+ *
4
+ * The compiler itself is AssemblyScript (assembly/), built to codegen.wasm.
5
+ * This wrapper instantiates it and gives its exports their types. The same wasm runs as the browser JIT
6
+ * (a module per ABC as its SWF loads) and as the ahead-of-time compiler,
7
+ * so both produce identical output. See docs/architecture/overview.md.
8
+ */
9
+ import { AIR_API_VERSION, API_VERSION, checkApiVersion } from "@swf2es/format";
10
+ import { instantiate } from "./codegen.js";
11
+
12
+ /** Bumped whenever generated code or the runtime ABI it calls changes. */
13
+ export const COMPILER_VERSION = "0.0.0";
14
+
15
+ // The API versions the player and the swf2es command reset the compiler
16
+ // with, which module cache keys name: Flash Player's, and AIR's, whose
17
+ // series has the names Flash Player's leaves out. format's, which the
18
+ // runtime takes too.
19
+ export { AIR_API_VERSION, API_VERSION };
20
+
21
+ /** The ABCs added to a compiler, by their indices: each one's hash, and whether it was added as a library. */
22
+ export interface AddedAbcs {
23
+ readonly hashes: readonly string[];
24
+ readonly builtins: readonly boolean[];
25
+ }
26
+
27
+ /**
28
+ * The log's entries made while only the first `count` ABCs, the libraries,
29
+ * were added, as a key names them: by the SHA-256 of libraryLog's text,
30
+ * taken once, since they are the same in every later module's context.
31
+ */
32
+ export interface LibraryDigest {
33
+ count: number;
34
+ digest: string;
35
+ }
36
+
37
+ /** The text whose SHA-256 is a LibraryDigest's, for ABC `index`, any added after the first `count`. */
38
+ export function libraryLog(codegen: Codegen, index: number, count: number): string {
39
+ return codegen.context(index, 0, count)?.log ?? "";
40
+ }
41
+
42
+ /**
43
+ * The text of ABC `index`'s module cache key, whose UTF-8's SHA-256 is the
44
+ * key: what the module depends on, the compiler's identity, the API
45
+ * version, every ABC its domain sees, by hash and whether it is a library,
46
+ * those added after it included, its own place among them, and the
47
+ * compiler's log about them (see Codegen.context), the libraries' part by
48
+ * its digest once taken. The player's module cache and the swf2es command
49
+ * both key by it, so that the command's modules are the cache's. Null
50
+ * where there is no context.
51
+ */
52
+ export function moduleKey(
53
+ codegen: Codegen,
54
+ index: number,
55
+ abcs: AddedAbcs,
56
+ libraries: LibraryDigest | null,
57
+ ): string | null {
58
+ const context = codegen.context(index, libraries?.count ?? 0);
59
+ if (!context) {
60
+ return null;
61
+ }
62
+
63
+ return JSON.stringify({
64
+ compiler: codegen.identity,
65
+ api: codegen.apiVersion,
66
+ abcs: context.abcs.map((i) => `${abcs.builtins[i] ? "builtin " : ""}${abcs.hashes[i]}`),
67
+ own: context.own,
68
+ libraries,
69
+ log: context.log,
70
+ });
71
+ }
72
+
73
+ /**
74
+ * A SWF's AVM1 action blocks, as format's readActions lists them: the SWF's
75
+ * version and each block's range in its decompressed bytes.
76
+ */
77
+ export interface ActionBlocks {
78
+ readonly version: number;
79
+ readonly blocks: readonly { readonly offset: number; readonly length: number }[];
80
+ }
81
+
82
+ /** What a SWF's action blocks compile to: one module, and each block's entry in it. */
83
+ export interface CompiledActions {
84
+ module: string;
85
+ /** By block index: the block's entry in the module, as it compiles alone. */
86
+ entries: string[];
87
+ /** By key, each unit in the module's units, as it compiles alone. */
88
+ units: Map<string, ModuleUnit>;
89
+ }
90
+
91
+ /**
92
+ * Where control goes on in a unit, as V.far is called: an offset in the
93
+ * SWF's decompressed bytes, where the region it goes on in ends, where its
94
+ * actions' bytes end, and whether it runs as a SWF 5's function. The
95
+ * module's units are keyed by these, joined with commas.
96
+ */
97
+ export interface ActionUnit {
98
+ readonly at: number;
99
+ readonly end: number;
100
+ readonly bound: number;
101
+ readonly five: boolean;
102
+ }
103
+
104
+ /**
105
+ * A unit's code, and what it depends on: it runs as well for any region
106
+ * that ends at or past `high` whose actions' bytes end at or past `reach`,
107
+ * -1 for only the one it was compiled for.
108
+ */
109
+ export interface CompiledUnit {
110
+ code: string;
111
+ high: number;
112
+ reach: number;
113
+ }
114
+
115
+ /** A unit of a module's units, and what it was compiled for. */
116
+ export interface ModuleUnit extends CompiledUnit {
117
+ unit: ActionUnit;
118
+ }
119
+
120
+ /**
121
+ * The text of the module cache key of a SWF's AVM1 module, whose UTF-8's
122
+ * SHA-256 is the key: the compiler's identity, the SWF's version, which
123
+ * picks the operations its code calls, and `hash`, the SHA-256 of the
124
+ * SWF's decompressed bytes, which hold its blocks. An AVM1 module links to
125
+ * nothing, so nothing else goes in.
126
+ */
127
+ export function actionsKey(codegen: Codegen, hash: string, version: number): string {
128
+ return JSON.stringify({ compiler: codegen.identity, avm1: version, swf: hash });
129
+ }
130
+
131
+ /**
132
+ * A definition an application domain has found by name, or as a type,
133
+ * which is not the first one from the root down, as the runtime reports it:
134
+ * what the domain's later ABCs compile against (see Codegen.found).
135
+ */
136
+ export interface FoundDefinition {
137
+ /** The application domain that found it. */
138
+ domain: number;
139
+ /** The name's namespace: its kind (0 public, 1 package-internal, ...) and URI. */
140
+ nsKind: number;
141
+ uri: string;
142
+ name: string;
143
+ /** The ABC that defines it, by its position among those added. */
144
+ abc: number;
145
+ /** Found as a type (avmplus' cached traits), else by name (cached scripts). */
146
+ asType: boolean;
147
+ }
148
+
149
+ export interface AbcVersion {
150
+ major: number;
151
+ minor: number;
152
+ }
153
+
154
+ /** What an ABC compiles to: its module, the module's source map, and each compiled method body's entry in it. */
155
+ export interface Compiled {
156
+ module: string;
157
+ /** JSON: the module's lines mapped to the AS3 lines debugline names. */
158
+ sourceMap: string;
159
+ /** By method body index: the body's entry in the module's F, as the JIT compiles it alone. */
160
+ entries: Map<number, string>;
161
+ }
162
+
163
+ /**
164
+ * What ABC `index`'s module depends on in the domain beyond the ABCs'
165
+ * bytes (see Codegen.context).
166
+ */
167
+ export interface ModuleContext {
168
+ /** The ABCs its application domain sees, by their positions among those added, in load order. */
169
+ abcs: number[];
170
+ /** Its own place in `abcs`. */
171
+ own: number;
172
+ /**
173
+ * The domain's log about those ABCs, in order: the lazy answers fixed so
174
+ * far and the findings recorded, each with how many of `abcs` there were.
175
+ */
176
+ log: string;
177
+ }
178
+
179
+ /** A module compiled, and what compiling it fixed in the domain, to replay in its place (Codegen.replay); null where it cannot be. */
180
+ export interface LoggedModule {
181
+ module: string;
182
+ log: string | null;
183
+ }
184
+
185
+ /**
186
+ * The compiler. ABCs are added to a domain in the order the SWF loads them
187
+ * (the builtins first), and the last one added compiles, whole or a method
188
+ * at a time, to code that is byte for byte the same either way.
189
+ */
190
+ export interface Codegen {
191
+ /**
192
+ * The identity of this codegen.wasm, a hash of its bytes that its build
193
+ * stamps it with; null for one built without it.
194
+ */
195
+ readonly identity: string | null;
196
+ /** The version at the start of an ABC block, or null if it is too short. */
197
+ abcVersion(abc: Uint8Array): AbcVersion | null;
198
+ /**
199
+ * Start a domain whose user ABCs have API version `apiVersion`: Flash
200
+ * Player's, API_VERSION, by default, or AIR's, AIR_API_VERSION; one
201
+ * that is neither series' (checkApiVersion) is a RangeError.
202
+ */
203
+ reset(apiVersion?: number): void;
204
+ /** The API version of the last reset. */
205
+ readonly apiVersion: number;
206
+ /**
207
+ * Add an ABC, loaded into application domain `appDomain` (0, the root, by
208
+ * default), linking it against what that domain sees of those before it;
209
+ * 0, or the VerifyError it was rejected with (then it is not added).
210
+ * Throws for an application domain never made, or dropped.
211
+ */
212
+ add(abc: Uint8Array, builtin?: boolean, appDomain?: number): number;
213
+ /** A new application domain, a child of `parent`: its number. Throws for a `parent` never made, or dropped. */
214
+ childDomain(parent: number): number;
215
+ /**
216
+ * Drop application domain `appDomain` and its descendants once nothing
217
+ * can run their code: no other sees their ABCs, which compile no more,
218
+ * and compact reuses the memory they took. The indices of the ABCs added
219
+ * after them do not change, and neither does what those compile to.
220
+ * Nothing, if `epoch` is given and the domain was reset since.
221
+ */
222
+ dropDomain(appDomain: number, epoch?: number): void;
223
+ /**
224
+ * How many times `reset` has started a domain: a host that keeps the
225
+ * number a domain had passes it to dropDomain, which then drops nothing
226
+ * if the domain was reset since, as when another player took the Codegen
227
+ * over and its numbers are another domain's.
228
+ */
229
+ readonly epoch: number;
230
+ /**
231
+ * Rebuild the domain without the ABCs of dropped and evicted domains, if
232
+ * memory would otherwise grow and they hold enough to keep it from
233
+ * growing: whether it did. A rebuild links every live ABC again, tens of
234
+ * milliseconds for a large application, so a host calls this when it has
235
+ * time, as when idle; nothing else does.
236
+ */
237
+ compact(): boolean;
238
+ /**
239
+ * What the ABCs hold of codegen's memory, roughly, in bytes: the live
240
+ * ones', and the dropped and evicted domains' that compact would free;
241
+ * and the size of codegen's memory, which never shrinks.
242
+ */
243
+ usage(): { live: number; dead: number; memory: number };
244
+ /**
245
+ * Evict application domain `appDomain` and its descendants while the host
246
+ * has no use for them, as once their ABCs are compiled: as dropped, but
247
+ * reviveDomain makes one live again. compact reuses what they took
248
+ * meanwhile, all but their findings, what was first resolved of them,
249
+ * and their ABCs' places.
250
+ */
251
+ evictDomain(appDomain: number): void;
252
+ /** Whether application domain `appDomain` is live: made, and neither evicted nor dropped. */
253
+ isLive(appDomain: number): boolean;
254
+ /**
255
+ * Make evicted application domain `appDomain` live again, with its evicted
256
+ * ancestors, the first of them a child of a live domain: given all their
257
+ * ABCs again by their indices, as they were added, their ABCs link as
258
+ * they did, by one rebuild at most, and what they found holds. Throws if
259
+ * they cannot be.
260
+ */
261
+ reviveDomain(appDomain: number, abcs: Map<number, Uint8Array>): void;
262
+ /** Record a definition an application domain has found; it holds for the ABCs added after. */
263
+ found(definition: FoundDefinition): void;
264
+ /**
265
+ * ABC `index` compiled whole, the last added by default, against every ABC
266
+ * added so far, later ones included, so a SWF's DoABCs may all be added
267
+ * before any compiles; `hashes` are the ABCs' hashes in load order, as
268
+ * the cache key names them.
269
+ */
270
+ compile(hashes?: string[], index?: number): Compiled;
271
+ /**
272
+ * ABC `index`'s module alone, as `compile` writes it, for a host that
273
+ * loads it and needs neither its source map nor its entries: writing
274
+ * those too would add a large ABC's worth to codegen's memory, which
275
+ * never shrinks.
276
+ */
277
+ compileModule(hashes?: string[], index?: number): string;
278
+ /**
279
+ * An opt-in JIT module's tables and lazy factories. Its default export
280
+ * takes (rt, jit); jit(body, bindings) returns that body's factory.
281
+ * Verification still runs now to fix scopes against the current domain.
282
+ * This module is not an AOT module and must not enter the module cache.
283
+ */
284
+ compileLazyModule(hashes?: string[], index?: number): string;
285
+ /**
286
+ * Method bodies of ABC `index` (the last added by default) compiled
287
+ * alone, as the optional lazy JIT compiles each on first factory use;
288
+ * a native, unverified or unknown one is left out. Each is byte for byte
289
+ * its entry in the whole module.
290
+ */
291
+ compileMethods(bodies: number[], index?: number): Map<number, string>;
292
+ /**
293
+ * What ABC `index`'s module depends on beyond the ABCs' bytes, for a
294
+ * cache key: besides the ABCs its domain sees, what the domain has
295
+ * fixed of what resolves lazily, which a later ABC in an ancestor would
296
+ * change were it first asked now. Its log only of the entries made with
297
+ * more than `after` ABCs added, and `upTo` at most unless negative. Null
298
+ * for an ABC never added.
299
+ */
300
+ context(index?: number, after?: number, upTo?: number): ModuleContext | null;
301
+ /**
302
+ * A string that changes whenever a context might: a host that took one
303
+ * and finds this the same since need not take it again.
304
+ */
305
+ revision(): string;
306
+ /** compileModule, with what the compile fixed in the domain (see replay). */
307
+ compileModuleLogged(hashes?: string[], index?: number): LoggedModule;
308
+ /**
309
+ * Fix in the domain what compiling ABC `index` did, from the log
310
+ * compileModuleLogged gave with its module, in place of compiling it:
311
+ * the domain is then as the compile would have left it, if its context
312
+ * is the one that compile had. False if the log is malformed or an
313
+ * entry fails, after doing those before it.
314
+ */
315
+ replay(log: string, index?: number): boolean;
316
+ /**
317
+ * A SWF's AVM1 action blocks compiled to one module, `bytes` the SWF's
318
+ * decompressed bytes, which the blocks' ranges are of (format's
319
+ * `Swf.bytes`), with each block's entry in it. Needs no domain: AVM1 code
320
+ * links to nothing.
321
+ */
322
+ compileActions(bytes: Uint8Array, actions: ActionBlocks): CompiledActions;
323
+ /** compileActions' module alone. */
324
+ compileActionsModule(bytes: Uint8Array, actions: ActionBlocks): string;
325
+ /**
326
+ * Blocks of a SWF compiled alone, by index, as a JIT may compile each
327
+ * when it first runs: each byte for byte its entry in the SWF's module.
328
+ * An index with no block is left out.
329
+ */
330
+ compileActionBlocks(
331
+ bytes: Uint8Array,
332
+ actions: ActionBlocks,
333
+ blocks: number[],
334
+ ): Map<number, string>;
335
+ /**
336
+ * A unit of a SWF compiled alone, as V.far asks for it when control first
337
+ * goes on there: byte for byte its entry in the module's units.
338
+ */
339
+ compileActionUnit(bytes: Uint8Array, actions: ActionBlocks, unit: ActionUnit): CompiledUnit;
340
+ /**
341
+ * Let go of the copy of a SWF compileActionBlocks and compileActionUnit
342
+ * keep, as the host lets the SWF go; it is let go too once `bytes` are
343
+ * collected, and when blocks or units of other SWFs are asked for since.
344
+ */
345
+ releaseActions(bytes: Uint8Array): void;
346
+ /** How many SWFs compileActionBlocks and compileActionUnit keep a copy of, and how many times they have copied one in. */
347
+ heldActions(): { held: number; copies: number };
348
+ }
349
+
350
+ /**
351
+ * Instantiate the compiler from a compiled codegen.wasm module. Loading the
352
+ * bytes is left to the caller (fetch in browsers, fs in node), which keeps
353
+ * this package free of I/O. The file ships as `@swf2es/codegen/codegen.wasm`.
354
+ */
355
+ export async function createCodegen(module: WebAssembly.Module): Promise<Codegen> {
356
+ const wasm = await instantiate(module, { env: {} });
357
+ const version = wasm.compilerVersion();
358
+ if (version !== COMPILER_VERSION) {
359
+ throw new Error(
360
+ `codegen.wasm is version ${version} but the wrapper expects ${COMPILER_VERSION}`,
361
+ );
362
+ }
363
+
364
+ // Of the same version, but built before its modules were handed out in
365
+ // place: its *Bytes exports return copies, which this wrapper cannot read.
366
+ // Asked of the module, as the bindings define every export they know of.
367
+ if (!WebAssembly.Module.exports(module).some((e) => e.name === "outputLength")) {
368
+ throw new Error(
369
+ "codegen.wasm has no outputLength export, which the wrapper expects: it is older than the wrapper",
370
+ );
371
+ }
372
+
373
+ // codegen.wasm uses AssemblyScript's minimal runtime, which collects
374
+ // garbage only when asked. Nothing runs in wasm between calls, so that is
375
+ // when: every COLLECT_EVERY calls, and after a call that grew memory.
376
+ // Memory never shrinks, so collecting only on growth would let each
377
+ // cycle's garbage grow it a little further.
378
+ let size = wasm.memory.buffer.byteLength;
379
+ let calls = 0;
380
+ let methodBytes = 0;
381
+ // Whether the domain has an ABC to compile: asking before is a mistake worth a message, not a trap.
382
+ let added = 0;
383
+ const last = (what: string) => {
384
+ if (added === 0) {
385
+ throw new Error(`${what}: no ABC has been added to the domain`);
386
+ }
387
+ };
388
+ // An ABC by its position among those added, -1 for the last.
389
+ const indexOf = (index: number) => {
390
+ if (index >= 0 && (!Number.isInteger(index) || index >= added)) {
391
+ throw new Error(`ABC ${index}: only ${added} have been added to the domain`);
392
+ }
393
+
394
+ return index < 0 ? -1 : index;
395
+ };
396
+ // ABC `index`'s module, its source map and entries kept for `keep`. A
397
+ // module is never empty: the ABC was of a dropped application domain.
398
+ const moduleOf = (hashes: string[], index: number, keep: boolean, lazy = false) => {
399
+ // Only the hashes the module names: joining every ABC's for each
400
+ // compile took megabytes of codegen's memory a few thousand loads on.
401
+ const at = indexOf(index);
402
+ const own = at < 0 ? added - 1 : at;
403
+ const linked = collected(wasm.domainLinked(at));
404
+ const named = [hashes[own] ?? ""];
405
+ for (const i of linked ? linked.split(",") : []) {
406
+ named.push(hashes[Number(i)] ?? "");
407
+ }
408
+
409
+ // Its bytes decoded here: lifted as a string, a module of megabytes took
410
+ // longer than compiling it. The emitter writes only well-formed UTF-8,
411
+ // which decodes alike either way.
412
+ const address = lazy
413
+ ? wasm.domainLazyModuleBytes(named.join("\n"), at)
414
+ : wasm.domainModuleBytes(named.join("\n"), at, keep, true);
415
+ const module = collected(handed(address, wasm.outputLength()), lazy);
416
+ if (module === "") {
417
+ throw new Error(`ABC ${index}: its application domain was dropped`);
418
+ }
419
+
420
+ return module;
421
+ };
422
+ const ranges = (actions: ActionBlocks) =>
423
+ [
424
+ Int32Array.from(actions.blocks, (b) => b.offset),
425
+ Int32Array.from(actions.blocks, (b) => b.length),
426
+ ] as const;
427
+ // The SWFs whose blocks or units compileActionBlocks and compileActionUnit
428
+ // compile, which wasm holds by a handle each: the few used last, so that
429
+ // blocks and units of SWFs that alternate do not copy them in again, each
430
+ // let go as its bytes are collected.
431
+ const holding = new WeakMap<Uint8Array, { handle: number; actions: ActionBlocks }>();
432
+ const holds: number[] = [];
433
+ let handles = 0;
434
+ let copies = 0;
435
+ const release = (handle: number) => {
436
+ const at = holds.indexOf(handle);
437
+ if (at >= 0) {
438
+ holds.splice(at, 1);
439
+ wasm.actionsRelease(handle);
440
+ }
441
+ };
442
+ const collectedSwfs = new FinalizationRegistry<number>(release);
443
+ const hold = (bytes: Uint8Array, actions: ActionBlocks): number => {
444
+ const known = holding.get(bytes);
445
+ if (known !== undefined && known.actions === actions && holds.includes(known.handle)) {
446
+ holds.splice(holds.indexOf(known.handle), 1);
447
+ holds.push(known.handle);
448
+ return known.handle;
449
+ }
450
+
451
+ if (known !== undefined) {
452
+ release(known.handle);
453
+ collectedSwfs.unregister(known);
454
+ }
455
+
456
+ const handle = ++handles;
457
+ const [offsets, lengths] = ranges(actions);
458
+ wasm.actionsHold(handle, bytes, offsets, lengths);
459
+ copies++;
460
+ const record = { handle, actions };
461
+ holding.set(bytes, record);
462
+ collectedSwfs.register(bytes, handle, record);
463
+ holds.push(handle);
464
+ while (holds.length > HELD_SWFS) {
465
+ release(holds[0]);
466
+ }
467
+
468
+ return handle;
469
+ };
470
+ const actionsModule = (bytes: Uint8Array, actions: ActionBlocks, keep: boolean) => {
471
+ const [offsets, lengths] = ranges(actions);
472
+ const address = wasm.actionsModuleBytes(bytes, offsets, lengths, actions.version, keep);
473
+ return collected(handed(address, wasm.outputLength()));
474
+ };
475
+ // UTF-8 a call handed out in codegen's memory, not copied (see handOut in
476
+ // assembly/output.ts), decoded before anything else calls in: the next
477
+ // call may grow memory or move them, and a collection may free them.
478
+ const handed = (address: number, length: number) =>
479
+ length === 0 ? "" : utf8.decode(new Uint8Array(wasm.memory.buffer, address, length));
480
+ const stamps = WebAssembly.Module.customSections(module, "swf2es.build");
481
+ const identity = stamps.length ? String.fromCharCode(...new Uint8Array(stamps[0])) : null;
482
+ let epoch = 0;
483
+ let apiVersion = API_VERSION;
484
+ const collected = <T>(result: T, reclaim = false): T => {
485
+ const grown = wasm.memory.buffer.byteLength;
486
+ if (reclaim || grown > size || ++calls >= COLLECT_EVERY) {
487
+ wasm.__collect();
488
+ size = grown;
489
+ calls = 0;
490
+ methodBytes = 0;
491
+ }
492
+
493
+ return result;
494
+ };
495
+
496
+ return {
497
+ identity,
498
+ abcVersion(abc) {
499
+ const packed = collected(wasm.abcVersion(abc));
500
+ return packed < 0 ? null : { major: packed >>> 16, minor: packed & 0xffff };
501
+ },
502
+ get epoch() {
503
+ return epoch;
504
+ },
505
+ reset(version = API_VERSION) {
506
+ checkApiVersion(version);
507
+ added = 0;
508
+ epoch++;
509
+ apiVersion = version;
510
+ collected(wasm.domainReset(version));
511
+ },
512
+ get apiVersion() {
513
+ return apiVersion;
514
+ },
515
+ add(abc, builtin = false, appDomain = 0) {
516
+ const error = collected(wasm.domainAdd(abc, builtin, appDomain));
517
+ if (error < 0) {
518
+ throw new Error(`application domain ${appDomain}: never made, or dropped`);
519
+ }
520
+
521
+ if (error === 0) {
522
+ added++;
523
+ }
524
+
525
+ return error;
526
+ },
527
+ childDomain(parent) {
528
+ const domain = collected(wasm.domainChild(parent));
529
+ if (domain < 0) {
530
+ throw new Error(`application domain ${parent}: never made, or dropped`);
531
+ }
532
+
533
+ return domain;
534
+ },
535
+ dropDomain(appDomain, of = epoch) {
536
+ if (of === epoch) {
537
+ collected(wasm.domainDrop(appDomain));
538
+ }
539
+ },
540
+ evictDomain(appDomain) {
541
+ collected(wasm.domainEvict(appDomain));
542
+ },
543
+ usage() {
544
+ const [live, dead, memory] = collected(wasm.domainUsage()).split(" ").map(Number);
545
+ return { live, dead, memory };
546
+ },
547
+ compact() {
548
+ // A rebuild makes new tables while the old are live: what earlier
549
+ // calls left is collected first, and the old tables once it is done,
550
+ // before the next call's allocations would grow memory past them.
551
+ if (!collected(wasm.domainWantsCompact())) {
552
+ return false;
553
+ }
554
+
555
+ wasm.__collect();
556
+ calls = 0;
557
+ if (!wasm.domainCompact()) {
558
+ return false;
559
+ }
560
+
561
+ wasm.__collect();
562
+ size = wasm.memory.buffer.byteLength;
563
+ calls = 0;
564
+ return true;
565
+ },
566
+ isLive(appDomain) {
567
+ return collected(wasm.domainState(appDomain)) === 0;
568
+ },
569
+ reviveDomain(appDomain, abcs) {
570
+ // Its ABCs' bytes are copied into the compiler only if a rebuild let go of them.
571
+ let revived = collected(wasm.domainRevive(appDomain));
572
+ if (revived < 0) {
573
+ for (const [index, bytes] of abcs) {
574
+ const error = collected(wasm.domainRestore(index, bytes));
575
+ if (error !== 0) {
576
+ throw new Error(`ABC ${index}: not of an evicted domain, or rejected (${error})`);
577
+ }
578
+ }
579
+
580
+ revived = collected(wasm.domainRevive(appDomain));
581
+ }
582
+
583
+ if (revived < 0) {
584
+ throw new Error(
585
+ `application domain ${appDomain}: not evicted, its parent not live, or an ABC of it not given`,
586
+ );
587
+ }
588
+
589
+ if (revived > 0) {
590
+ wasm.__collect();
591
+ size = wasm.memory.buffer.byteLength;
592
+ calls = 0;
593
+ }
594
+ },
595
+ found(d) {
596
+ collected(wasm.domainFound(d.domain, d.nsKind, d.uri, d.name, d.abc, d.asType));
597
+ },
598
+ compile(hashes = [], index = -1) {
599
+ last("compile");
600
+ // Each part a call of its own, collected after it: the module's garbage
601
+ // is gone before its entries are written.
602
+ const module = moduleOf(hashes, index, true);
603
+ const sourceMap = collected(wasm.domainSourceMap());
604
+ const entries = parseEntries(collected(wasm.domainModuleEntries()));
605
+ return { module, sourceMap, entries };
606
+ },
607
+ compileModule(hashes = [], index = -1) {
608
+ last("compileModule");
609
+ return moduleOf(hashes, index, false);
610
+ },
611
+ compileLazyModule(hashes = [], index = -1) {
612
+ last("compileLazyModule");
613
+ return moduleOf(hashes, index, false, true);
614
+ },
615
+ context(index = -1, after = 0, upTo = -1) {
616
+ if (added === 0) {
617
+ return null;
618
+ }
619
+
620
+ const text = collected(wasm.domainContext(indexOf(index), after, upTo));
621
+ if (text === "") {
622
+ return null;
623
+ }
624
+
625
+ const first = text.indexOf("\n");
626
+ const second = text.indexOf("\n", first + 1);
627
+ const abcs = text.slice(0, first);
628
+ return {
629
+ abcs: abcs ? abcs.split(",").map(Number) : [],
630
+ own: Number(text.slice(first + 1, second < 0 ? text.length : second)),
631
+ log: second < 0 ? "" : text.slice(second + 1),
632
+ };
633
+ },
634
+ revision() {
635
+ return `${epoch} ${wasm.domainRevision()}`;
636
+ },
637
+ compileModuleLogged(hashes = [], index = -1) {
638
+ last("compileModuleLogged");
639
+ const mark = wasm.domainLogMark();
640
+ const module = moduleOf(hashes, index, false);
641
+ const log = collected(wasm.domainLogSince(mark, indexOf(index)));
642
+ return { module, log: log === "!" ? null : log };
643
+ },
644
+ replay(log, index = -1) {
645
+ last("replay");
646
+ return collected(wasm.domainReplay(log, indexOf(index)));
647
+ },
648
+ compileMethods(bodies, index = -1) {
649
+ last("compileMethods");
650
+ const indices = bodies.filter((b) => Number.isInteger(b) && b >= 0);
651
+ if (indices.length === 0) {
652
+ return new Map();
653
+ }
654
+
655
+ const address = wasm.domainEmitEachBytes(indices.join(","), true, indexOf(index));
656
+ const length = wasm.outputLength();
657
+ methodBytes += length;
658
+ const text = handed(address, length);
659
+ return parseEntries(collected(text, methodBytes >= COLLECT_METHOD_BYTES));
660
+ },
661
+ compileActions(bytes, actions) {
662
+ const module = actionsModule(bytes, actions, true);
663
+ const entries = parseEntries(collected(wasm.actionsModuleEntries()));
664
+ const units = new Map<string, ModuleUnit>();
665
+ for (const item of collected(wasm.actionsModuleUnits()).split("\u0002")) {
666
+ const [key, numbers, limits, code] = item.split("\u0001");
667
+ if (code !== undefined) {
668
+ const [at, end, bound, five] = numbers.split(",").map(Number);
669
+ const [high, reach] = limits.split(",").map(Number);
670
+ units.set(key, { code, high, reach, unit: { at, end, bound, five: five !== 0 } });
671
+ }
672
+ }
673
+
674
+ return { module, entries: actions.blocks.map((_, k) => entries.get(k) ?? ""), units };
675
+ },
676
+ compileActionsModule(bytes, actions) {
677
+ return actionsModule(bytes, actions, false);
678
+ },
679
+ compileActionBlocks(bytes, actions, blocks) {
680
+ const indices = blocks.filter((b) => Number.isInteger(b) && b >= 0);
681
+ if (indices.length === 0) {
682
+ return new Map();
683
+ }
684
+
685
+ // The SWF held, as for units: a block needs it whole, and copying it
686
+ // in for each block cost a copy of the SWF in codegen's memory
687
+ // beside the one held for units, and the time to copy it.
688
+ const handle = hold(bytes, actions);
689
+ const address = wasm.actionsHeldEmitEachBytes(handle, actions.version, indices.join(","));
690
+ const text = handed(address, wasm.outputLength());
691
+ // A block's analysis can go before the next block runs.
692
+ return parseEntries(collected(text, true));
693
+ },
694
+ compileActionUnit(bytes, actions, { at, end, bound, five }) {
695
+ const handle = hold(bytes, actions);
696
+ const address = wasm.actionsHeldUnitBytes(handle, actions.version, at, end, bound, five);
697
+ const code = handed(address, wasm.outputLength());
698
+ const [high, reach] = wasm.actionsUnitLimits();
699
+ return collected({ code, high, reach });
700
+ },
701
+ releaseActions(bytes) {
702
+ const known = holding.get(bytes);
703
+ if (known !== undefined) {
704
+ release(known.handle);
705
+ holding.delete(bytes);
706
+ collectedSwfs.unregister(known);
707
+ }
708
+ },
709
+ heldActions() {
710
+ return { held: collected(wasm.actionsHeldCount()), copies };
711
+ },
712
+ };
713
+ }
714
+
715
+ /** Entries as the wasm writes them: for each, the body's index, U+0001, its entry, U+0002. */
716
+ function parseEntries(text: string): Map<number, string> {
717
+ const entries = new Map<number, string>();
718
+ for (const item of text.split("\u0002")) {
719
+ const at = item.indexOf("\u0001");
720
+ if (at > 0) {
721
+ entries.set(Number(item.slice(0, at)), item.slice(at + 1));
722
+ }
723
+ }
724
+
725
+ return entries;
726
+ }
727
+
728
+ const COLLECT_EVERY = 64;
729
+ /** How many SWFs whose blocks or units are compiled codegen holds a copy of at most. */
730
+ const HELD_SWFS = 4;
731
+ // Large method entries should not accumulate sixty-four calls of garbage.
732
+ const COLLECT_METHOD_BYTES = 256 * 1024;
733
+
734
+ // TextDecoder is the web's and node's alike, though not ECMAScript's,
735
+ // whose library alone the compiler's wrapper is typed against.
736
+ declare const TextDecoder: new () => { decode(bytes: Uint8Array): string };
737
+
738
+ const utf8 = new TextDecoder();