@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.
@@ -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
- /** A multiname with its runtime namespace and name, from the stack. */
361
- runtimeName(mn: Multiname, ...parts: Value[]): Multiname {
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 k = 0;
393
+ let last = a;
365
394
  if (mn.runtimeNs) {
366
- const ns = parts[k++];
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 = parts[k];
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
- if (this.scopeHas(outer[i], mn, (outer.w >> i) & 1)) {
642
- return outer[i];
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 && i in a) {
1001
- return a[i];
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