@swf2es/runtime 0.1.12 → 0.1.14
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/dist/avm2/names.d.ts +1 -1
- package/dist/avm2/names.d.ts.map +1 -1
- package/dist/avm2/names.js +1 -1
- package/dist/avm2/names.js.map +1 -1
- package/dist/avm2/natives/bytearray.d.ts.map +1 -1
- package/dist/avm2/natives/bytearray.js +22 -0
- package/dist/avm2/natives/bytearray.js.map +1 -1
- package/dist/avm2/property-cache.d.ts +1 -1
- package/dist/avm2/property-cache.d.ts.map +1 -1
- package/dist/avm2/property-cache.js +1 -1
- package/dist/avm2/property-cache.js.map +1 -1
- package/dist/avm2/runtime.d.ts +22 -2
- package/dist/avm2/runtime.d.ts.map +1 -1
- package/dist/avm2/runtime.js +127 -10
- package/dist/avm2/runtime.js.map +1 -1
- package/package.json +2 -2
- package/src/avm2/names.ts +1 -1
- package/src/avm2/natives/bytearray.ts +26 -0
- package/src/avm2/property-cache.ts +1 -1
- package/src/avm2/runtime.ts +134 -10
package/src/avm2/runtime.ts
CHANGED
|
@@ -141,6 +141,18 @@ export { NOT_FOUND } from "./descriptors.js";
|
|
|
141
141
|
*/
|
|
142
142
|
export const SEALED_ELEMENTS: Value[] = Object.freeze([]) as unknown as Value[];
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* The scopes a contested function's chain has past those all its creators'
|
|
146
|
+
* chains share, in one entry of the chain (Runtime.packScope): `w` has a bit
|
|
147
|
+
* for each that is a with scope, as a Scope's has.
|
|
148
|
+
*/
|
|
149
|
+
class ScopePack {
|
|
150
|
+
constructor(
|
|
151
|
+
readonly scopes: Value[],
|
|
152
|
+
readonly w: number,
|
|
153
|
+
) {}
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
/** The value of `o`'s dynamic property `name`, or NOT_FOUND. */
|
|
145
157
|
function dynamicValue(o: AsObject, name: string): Value {
|
|
146
158
|
const d: Map<string, Value> | null | undefined = o.$d;
|
|
@@ -279,6 +291,8 @@ export class Runtime {
|
|
|
279
291
|
/** Empty the inline caches, so that they keep no traits or code of what is being let go of. */
|
|
280
292
|
forgetCaches(): void {
|
|
281
293
|
invalidate();
|
|
294
|
+
this.stringNames = new WeakMap();
|
|
295
|
+
this.stringNamesEpoch = epoch();
|
|
282
296
|
}
|
|
283
297
|
|
|
284
298
|
// Names.
|
|
@@ -357,15 +371,30 @@ export class Runtime {
|
|
|
357
371
|
return new TypeName(base, [param]);
|
|
358
372
|
}
|
|
359
373
|
|
|
360
|
-
/**
|
|
361
|
-
|
|
374
|
+
/**
|
|
375
|
+
* A multiname with its runtime namespace and name, from the stack: `a`
|
|
376
|
+
* the namespace where it has one at run time, and then `b` the name,
|
|
377
|
+
* else `a` the name. Two parameters, not a rest: a lookup by a name made
|
|
378
|
+
* at run time calls this each time, and a rest is an array each call.
|
|
379
|
+
*/
|
|
380
|
+
runtimeName(mn: Multiname, a?: Value, b?: Value): Multiname {
|
|
381
|
+
if (
|
|
382
|
+
!mn.runtimeNs &&
|
|
383
|
+
mn.runtimeName &&
|
|
384
|
+
mn.cache !== NO_CACHE &&
|
|
385
|
+
typeof a === "string" &&
|
|
386
|
+
a.length <= 128
|
|
387
|
+
) {
|
|
388
|
+
return this.stringName(mn, a);
|
|
389
|
+
}
|
|
390
|
+
|
|
362
391
|
let namespaces: readonly (Namespace | null)[] = mn.namespaces;
|
|
363
392
|
let versions = mn.versions;
|
|
364
|
-
let
|
|
393
|
+
let last = a;
|
|
365
394
|
if (mn.runtimeNs) {
|
|
366
|
-
|
|
367
|
-
namespaces = [this.namespaceOf(ns)];
|
|
395
|
+
namespaces = [this.namespaceOf(a)];
|
|
368
396
|
versions = [255];
|
|
397
|
+
last = b;
|
|
369
398
|
}
|
|
370
399
|
|
|
371
400
|
const kind =
|
|
@@ -377,7 +406,7 @@ export class Runtime {
|
|
|
377
406
|
: mn.kind;
|
|
378
407
|
let name = mn.name;
|
|
379
408
|
if (mn.runtimeName) {
|
|
380
|
-
const part =
|
|
409
|
+
const part = last;
|
|
381
410
|
if (part?.$local !== undefined && !(part instanceof Namespace)) {
|
|
382
411
|
// A Proxy's QName of a multiname names its namespaces still.
|
|
383
412
|
if (part.$mn) {
|
|
@@ -411,6 +440,49 @@ export class Runtime {
|
|
|
411
440
|
return named;
|
|
412
441
|
}
|
|
413
442
|
|
|
443
|
+
private stringNames = new WeakMap<Multiname, { names: Map<string, Multiname>; misses: number }>();
|
|
444
|
+
private stringNamesEpoch = -1;
|
|
445
|
+
|
|
446
|
+
/** Repeated string keys share lookup work, without retaining arbitrary keys or retired domains. */
|
|
447
|
+
private stringName(base: Multiname, name: string): Multiname {
|
|
448
|
+
const current = epoch();
|
|
449
|
+
if (this.stringNamesEpoch !== current) {
|
|
450
|
+
this.stringNames = new WeakMap();
|
|
451
|
+
this.stringNamesEpoch = current;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let cache = this.stringNames.get(base);
|
|
455
|
+
if (cache && cache.misses >= 0) {
|
|
456
|
+
const known = cache.names.get(name);
|
|
457
|
+
if (known) {
|
|
458
|
+
cache.misses = 0;
|
|
459
|
+
return known;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const named = new Multiname(base.kind, base.namespaces, base.versions, name, base.attribute);
|
|
464
|
+
named.domain = base.domain;
|
|
465
|
+
if (!cache) {
|
|
466
|
+
cache = { names: new Map(), misses: 0 };
|
|
467
|
+
this.stringNames.set(base, cache);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Replacing filled inline caches would retain their heads until the next invalidation.
|
|
471
|
+
if (cache.misses < 0) {
|
|
472
|
+
return named;
|
|
473
|
+
}
|
|
474
|
+
if (cache.names.size < 64) {
|
|
475
|
+
named.cache = UNFILLED;
|
|
476
|
+
cache.names.set(name, named);
|
|
477
|
+
} else if (++cache.misses >= 64) {
|
|
478
|
+
// A stream of unrelated keys should stop paying for a map lookup on every access.
|
|
479
|
+
cache.names.clear();
|
|
480
|
+
cache.misses = -1;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return named;
|
|
484
|
+
}
|
|
485
|
+
|
|
414
486
|
/** An object's string, as the name of an object that is not a Dictionary. */
|
|
415
487
|
private readonly keyName = (key: unknown): string => this.toString(key as Value);
|
|
416
488
|
|
|
@@ -602,6 +674,21 @@ export class Runtime {
|
|
|
602
674
|
return chain;
|
|
603
675
|
}
|
|
604
676
|
|
|
677
|
+
/**
|
|
678
|
+
* `chain` for a contested function whose creators' chains differ in
|
|
679
|
+
* length (the codegen's Scope.merged): its first `at` entries, which all
|
|
680
|
+
* of them have, then one entry holding the rest, a ScopePack, searched in
|
|
681
|
+
* its place. The function's chain is then as long whichever creator made
|
|
682
|
+
* it, and what it creates finds its own scopes where it was compiled to.
|
|
683
|
+
* Called by compiled code.
|
|
684
|
+
*/
|
|
685
|
+
packScope(chain: Scope, at: number): Scope {
|
|
686
|
+
const packed = chain.slice(0, at) as Scope;
|
|
687
|
+
packed.push(new ScopePack(chain.slice(at), chain.w >>> at) as never);
|
|
688
|
+
packed.w = (chain.w & ((1 << at) - 1)) | (1 << at);
|
|
689
|
+
return packed;
|
|
690
|
+
}
|
|
691
|
+
|
|
605
692
|
/**
|
|
606
693
|
* The global object of the script that defines `mn`, run first if it has
|
|
607
694
|
* not run. A module reads `mn.global` before calling this: once set it
|
|
@@ -638,8 +725,14 @@ export class Runtime {
|
|
|
638
725
|
}
|
|
639
726
|
|
|
640
727
|
for (let i = outer.length - 1; i >= 0; i--) {
|
|
641
|
-
|
|
642
|
-
|
|
728
|
+
const o = outer[i];
|
|
729
|
+
if (o instanceof ScopePack) {
|
|
730
|
+
const found = this.packHas(o, mn);
|
|
731
|
+
if (found !== undefined) {
|
|
732
|
+
return found;
|
|
733
|
+
}
|
|
734
|
+
} else if (this.scopeHas(o, mn, (outer.w >> i) & 1)) {
|
|
735
|
+
return o;
|
|
643
736
|
}
|
|
644
737
|
}
|
|
645
738
|
|
|
@@ -703,6 +796,23 @@ export class Runtime {
|
|
|
703
796
|
return global;
|
|
704
797
|
}
|
|
705
798
|
|
|
799
|
+
/** The innermost scope of a pack that has `mn`, a pack in it searched in turn; undefined if none has. */
|
|
800
|
+
private packHas(pack: ScopePack, mn: Multiname): AsObject | undefined {
|
|
801
|
+
for (let i = pack.scopes.length - 1; i >= 0; i--) {
|
|
802
|
+
const o = pack.scopes[i];
|
|
803
|
+
if (o instanceof ScopePack) {
|
|
804
|
+
const found = this.packHas(o, mn);
|
|
805
|
+
if (found !== undefined) {
|
|
806
|
+
return found;
|
|
807
|
+
}
|
|
808
|
+
} else if (this.scopeHas(o as AsObject, mn, (pack.w >> i) & 1)) {
|
|
809
|
+
return o as AsObject;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
706
816
|
private scopeHas(o: AsObject, mn: Multiname, isWith: number): boolean {
|
|
707
817
|
if (isWith) {
|
|
708
818
|
return this.withHas(o, mn);
|
|
@@ -997,8 +1107,22 @@ export class Runtime {
|
|
|
997
1107
|
}
|
|
998
1108
|
|
|
999
1109
|
const a: Value[] | undefined = o.$a;
|
|
1000
|
-
if (a !== undefined
|
|
1001
|
-
|
|
1110
|
+
if (a !== undefined) {
|
|
1111
|
+
if (i in a) {
|
|
1112
|
+
return a[i];
|
|
1113
|
+
}
|
|
1114
|
+
} else if (traits?.dynamic && traits.properties === null && !traits.refusesNames) {
|
|
1115
|
+
// As ScriptObject::getUintProperty: an object with no elements of its own looks the
|
|
1116
|
+
// number up among its dynamic properties first, with no binding asked for; a table
|
|
1117
|
+
// keyed by numbers, as a Dictionary of handles, is read so on every access. One not
|
|
1118
|
+
// there, or there as undefined, is looked up in full.
|
|
1119
|
+
const d: Map<string, Value> | null | undefined = o.$d;
|
|
1120
|
+
if (d) {
|
|
1121
|
+
const v = d.get(`${i}`);
|
|
1122
|
+
if (v !== undefined) {
|
|
1123
|
+
return v;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1002
1126
|
}
|
|
1003
1127
|
}
|
|
1004
1128
|
|