@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.
@@ -41,6 +41,19 @@ export { NOT_FOUND } from "./descriptors.js";
41
41
  * checks for it, and fails as a sealed object does.
42
42
  */
43
43
  export const SEALED_ELEMENTS = Object.freeze([]);
44
+ /**
45
+ * The scopes a contested function's chain has past those all its creators'
46
+ * chains share, in one entry of the chain (Runtime.packScope): `w` has a bit
47
+ * for each that is a with scope, as a Scope's has.
48
+ */
49
+ class ScopePack {
50
+ scopes;
51
+ w;
52
+ constructor(scopes, w) {
53
+ this.scopes = scopes;
54
+ this.w = w;
55
+ }
56
+ }
44
57
  /** The value of `o`'s dynamic property `name`, or NOT_FOUND. */
45
58
  function dynamicValue(o, name) {
46
59
  const d = o.$d;
@@ -171,6 +184,8 @@ export class Runtime {
171
184
  /** Empty the inline caches, so that they keep no traits or code of what is being let go of. */
172
185
  forgetCaches() {
173
186
  invalidate();
187
+ this.stringNames = new WeakMap();
188
+ this.stringNamesEpoch = epoch();
174
189
  }
175
190
  // Names.
176
191
  ns(kind, uri) {
@@ -227,15 +242,27 @@ export class Runtime {
227
242
  typeName(_N, _V, _S, base, param) {
228
243
  return new TypeName(base, [param]);
229
244
  }
230
- /** A multiname with its runtime namespace and name, from the stack. */
231
- runtimeName(mn, ...parts) {
245
+ /**
246
+ * A multiname with its runtime namespace and name, from the stack: `a`
247
+ * the namespace where it has one at run time, and then `b` the name,
248
+ * else `a` the name. Two parameters, not a rest: a lookup by a name made
249
+ * at run time calls this each time, and a rest is an array each call.
250
+ */
251
+ runtimeName(mn, a, b) {
252
+ if (!mn.runtimeNs &&
253
+ mn.runtimeName &&
254
+ mn.cache !== NO_CACHE &&
255
+ typeof a === "string" &&
256
+ a.length <= 128) {
257
+ return this.stringName(mn, a);
258
+ }
232
259
  let namespaces = mn.namespaces;
233
260
  let versions = mn.versions;
234
- let k = 0;
261
+ let last = a;
235
262
  if (mn.runtimeNs) {
236
- const ns = parts[k++];
237
- namespaces = [this.namespaceOf(ns)];
263
+ namespaces = [this.namespaceOf(a)];
238
264
  versions = [255];
265
+ last = b;
239
266
  }
240
267
  const kind = mn.kind === CONSTANT_RTQname ||
241
268
  mn.kind === CONSTANT_RTQnameA ||
@@ -245,7 +272,7 @@ export class Runtime {
245
272
  : mn.kind;
246
273
  let name = mn.name;
247
274
  if (mn.runtimeName) {
248
- const part = parts[k];
275
+ const part = last;
249
276
  if (part?.$local !== undefined && !(part instanceof Namespace)) {
250
277
  // A Proxy's QName of a multiname names its namespaces still.
251
278
  if (part.$mn) {
@@ -272,6 +299,44 @@ export class Runtime {
272
299
  named.domain = mn.domain;
273
300
  return named;
274
301
  }
302
+ stringNames = new WeakMap();
303
+ stringNamesEpoch = -1;
304
+ /** Repeated string keys share lookup work, without retaining arbitrary keys or retired domains. */
305
+ stringName(base, name) {
306
+ const current = epoch();
307
+ if (this.stringNamesEpoch !== current) {
308
+ this.stringNames = new WeakMap();
309
+ this.stringNamesEpoch = current;
310
+ }
311
+ let cache = this.stringNames.get(base);
312
+ if (cache && cache.misses >= 0) {
313
+ const known = cache.names.get(name);
314
+ if (known) {
315
+ cache.misses = 0;
316
+ return known;
317
+ }
318
+ }
319
+ const named = new Multiname(base.kind, base.namespaces, base.versions, name, base.attribute);
320
+ named.domain = base.domain;
321
+ if (!cache) {
322
+ cache = { names: new Map(), misses: 0 };
323
+ this.stringNames.set(base, cache);
324
+ }
325
+ // Replacing filled inline caches would retain their heads until the next invalidation.
326
+ if (cache.misses < 0) {
327
+ return named;
328
+ }
329
+ if (cache.names.size < 64) {
330
+ named.cache = UNFILLED;
331
+ cache.names.set(name, named);
332
+ }
333
+ else if (++cache.misses >= 64) {
334
+ // A stream of unrelated keys should stop paying for a map lookup on every access.
335
+ cache.names.clear();
336
+ cache.misses = -1;
337
+ }
338
+ return named;
339
+ }
275
340
  /** An object's string, as the name of an object that is not a Dictionary. */
276
341
  keyName = (key) => this.toString(key);
277
342
  /** The runtime namespace a Namespace value stands for. */
@@ -427,6 +492,20 @@ export class Runtime {
427
492
  chain.w = outer.w | (withs << outer.length);
428
493
  return chain;
429
494
  }
495
+ /**
496
+ * `chain` for a contested function whose creators' chains differ in
497
+ * length (the codegen's Scope.merged): its first `at` entries, which all
498
+ * of them have, then one entry holding the rest, a ScopePack, searched in
499
+ * its place. The function's chain is then as long whichever creator made
500
+ * it, and what it creates finds its own scopes where it was compiled to.
501
+ * Called by compiled code.
502
+ */
503
+ packScope(chain, at) {
504
+ const packed = chain.slice(0, at);
505
+ packed.push(new ScopePack(chain.slice(at), chain.w >>> at));
506
+ packed.w = (chain.w & ((1 << at) - 1)) | (1 << at);
507
+ return packed;
508
+ }
430
509
  /**
431
510
  * The global object of the script that defines `mn`, run first if it has
432
511
  * not run. A module reads `mn.global` before calling this: once set it
@@ -454,8 +533,15 @@ export class Runtime {
454
533
  }
455
534
  }
456
535
  for (let i = outer.length - 1; i >= 0; i--) {
457
- if (this.scopeHas(outer[i], mn, (outer.w >> i) & 1)) {
458
- return outer[i];
536
+ const o = outer[i];
537
+ if (o instanceof ScopePack) {
538
+ const found = this.packHas(o, mn);
539
+ if (found !== undefined) {
540
+ return found;
541
+ }
542
+ }
543
+ else if (this.scopeHas(o, mn, (outer.w >> i) & 1)) {
544
+ return o;
459
545
  }
460
546
  }
461
547
  return this.global(mn, outer.length ? outer[0] : locals[0], strict);
@@ -506,6 +592,22 @@ export class Runtime {
506
592
  }
507
593
  return global;
508
594
  }
595
+ /** The innermost scope of a pack that has `mn`, a pack in it searched in turn; undefined if none has. */
596
+ packHas(pack, mn) {
597
+ for (let i = pack.scopes.length - 1; i >= 0; i--) {
598
+ const o = pack.scopes[i];
599
+ if (o instanceof ScopePack) {
600
+ const found = this.packHas(o, mn);
601
+ if (found !== undefined) {
602
+ return found;
603
+ }
604
+ }
605
+ else if (this.scopeHas(o, mn, (pack.w >> i) & 1)) {
606
+ return o;
607
+ }
608
+ }
609
+ return undefined;
610
+ }
509
611
  scopeHas(o, mn, isWith) {
510
612
  if (isWith) {
511
613
  return this.withHas(o, mn);
@@ -756,8 +858,23 @@ export class Runtime {
756
858
  return traits.getIndex(o, i, this);
757
859
  }
758
860
  const a = o.$a;
759
- if (a !== undefined && i in a) {
760
- return a[i];
861
+ if (a !== undefined) {
862
+ if (i in a) {
863
+ return a[i];
864
+ }
865
+ }
866
+ else if (traits?.dynamic && traits.properties === null && !traits.refusesNames) {
867
+ // As ScriptObject::getUintProperty: an object with no elements of its own looks the
868
+ // number up among its dynamic properties first, with no binding asked for; a table
869
+ // keyed by numbers, as a Dictionary of handles, is read so on every access. One not
870
+ // there, or there as undefined, is looked up in full.
871
+ const d = o.$d;
872
+ if (d) {
873
+ const v = d.get(`${i}`);
874
+ if (v !== undefined) {
875
+ return v;
876
+ }
877
+ }
761
878
  }
762
879
  }
763
880
  return this.getProperty(o, this.runtimeName(mn, i));