@swf2es/runtime 0.1.17 → 0.1.19
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/dictionary-keys.d.ts +18 -1
- package/dist/avm2/dictionary-keys.d.ts.map +1 -1
- package/dist/avm2/dictionary-keys.js +107 -7
- package/dist/avm2/dictionary-keys.js.map +1 -1
- package/dist/avm2/enumeration.d.ts +22 -3
- package/dist/avm2/enumeration.d.ts.map +1 -1
- package/dist/avm2/enumeration.js +218 -42
- package/dist/avm2/enumeration.js.map +1 -1
- package/dist/avm2/natives/array.d.ts.map +1 -1
- package/dist/avm2/natives/array.js +7 -1
- package/dist/avm2/natives/array.js.map +1 -1
- package/dist/avm2/natives/object.d.ts.map +1 -1
- package/dist/avm2/natives/object.js +5 -1
- package/dist/avm2/natives/object.js.map +1 -1
- package/dist/avm2/natives/vector.js +1 -1
- package/dist/avm2/natives/vector.js.map +1 -1
- package/dist/avm2/runtime.d.ts +10 -0
- package/dist/avm2/runtime.d.ts.map +1 -1
- package/dist/avm2/runtime.js +46 -12
- package/dist/avm2/runtime.js.map +1 -1
- package/package.json +2 -2
- package/src/avm2/dictionary-keys.ts +124 -7
- package/src/avm2/enumeration.ts +256 -45
- package/src/avm2/natives/array.ts +7 -1
- package/src/avm2/natives/object.ts +6 -1
- package/src/avm2/natives/vector.ts +1 -1
- package/src/avm2/runtime.ts +50 -12
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// Dictionary(true)) keep no key alive, as Flash's do not: their entries are
|
|
6
6
|
// held strongly only by a WeakMap keyed by the key, and their slots hold
|
|
7
7
|
// them weakly, so neither a for-in left off nor a value that refers to its
|
|
8
|
-
// key keeps the key.
|
|
8
|
+
// key keeps the key. Reading a slot's WeakRef is the cost of a weak walk,
|
|
9
|
+
// so the entries one reads are kept beside the slots until the job ends,
|
|
10
|
+
// which is as long as the engine itself keeps what a WeakRef gave: a step's
|
|
11
|
+
// name and value, and the walks after it in that job, read none again.
|
|
9
12
|
|
|
10
13
|
import type { AsObject, Value } from "./descriptors.js";
|
|
11
14
|
|
|
@@ -37,6 +40,24 @@ class WeakEntry extends Entry {
|
|
|
37
40
|
|
|
38
41
|
type Held = Entry | WeakRef<Entry>;
|
|
39
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The weak Dictionaries whose slots' entries are kept open in this job, and
|
|
45
|
+
* whether their closing at its end is asked for. deref() makes the engine
|
|
46
|
+
* keep its target until the job ends, so an entry kept that long keeps no
|
|
47
|
+
* key the walk that read it did not; kept longer, it would.
|
|
48
|
+
*/
|
|
49
|
+
const openedInJob: DictionaryKeys[] = [];
|
|
50
|
+
let closing = false;
|
|
51
|
+
|
|
52
|
+
function closeOpened(): void {
|
|
53
|
+
for (let i = 0; i < openedInJob.length; i++) {
|
|
54
|
+
openedInJob[i].close();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
openedInJob.length = 0;
|
|
58
|
+
closing = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
/**
|
|
41
62
|
* As avmplus' hashtable, a name keeps its slot while it is there, so that a
|
|
42
63
|
* for-in started inside another never moves the outer one's names. One
|
|
@@ -71,6 +92,12 @@ export class DictionaryKeys {
|
|
|
71
92
|
private readonly spare: Entry[] = [];
|
|
72
93
|
/** The slot of each dynamic property's name. */
|
|
73
94
|
private named: Map<string, number> | null = null;
|
|
95
|
+
/**
|
|
96
|
+
* A weak Dictionary's entries that this job's for-ins have read from
|
|
97
|
+
* their slots, each at its slot's index, null where none was; null
|
|
98
|
+
* outside a job that walks it.
|
|
99
|
+
*/
|
|
100
|
+
private opened: (Entry | null)[] | null = null;
|
|
74
101
|
|
|
75
102
|
constructor(weak: boolean) {
|
|
76
103
|
this.weak = weak;
|
|
@@ -171,6 +198,10 @@ export class DictionaryKeys {
|
|
|
171
198
|
this.start(o);
|
|
172
199
|
}
|
|
173
200
|
|
|
201
|
+
if (this.weak) {
|
|
202
|
+
return this.nextWeak(o, index);
|
|
203
|
+
}
|
|
204
|
+
|
|
174
205
|
const slots = this.slots;
|
|
175
206
|
for (let i = index; i < slots.length; i++) {
|
|
176
207
|
const held = slots[i];
|
|
@@ -198,6 +229,64 @@ export class DictionaryKeys {
|
|
|
198
229
|
return 0;
|
|
199
230
|
}
|
|
200
231
|
|
|
232
|
+
/** As next, for weak keys: each slot's WeakRef is read once a job, then its entry kept. */
|
|
233
|
+
private nextWeak(o: AsObject, index: number): number {
|
|
234
|
+
const slots = this.slots;
|
|
235
|
+
const opened = this.opened ?? this.keepOpened();
|
|
236
|
+
for (let i = index; i < slots.length; i++) {
|
|
237
|
+
let entry = opened[i];
|
|
238
|
+
if (entry === null) {
|
|
239
|
+
const held = slots[i];
|
|
240
|
+
if (held === null) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (typeof held === "string") {
|
|
245
|
+
if (o.$d?.has(held) && !o.$dontEnum?.has(held)) {
|
|
246
|
+
return i + 1;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const read = (held as WeakRef<Entry>).deref();
|
|
253
|
+
if (read === undefined) {
|
|
254
|
+
slots[i] = null;
|
|
255
|
+
this.collected.push(i);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
entry = read;
|
|
260
|
+
opened[i] = read;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (entry.value !== GONE) {
|
|
264
|
+
return i + 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Starts keeping the entries read from the slots, until the job ends. */
|
|
272
|
+
private keepOpened(): (Entry | null)[] {
|
|
273
|
+
const opened: (Entry | null)[] = new Array(this.slots.length).fill(null);
|
|
274
|
+
this.opened = opened;
|
|
275
|
+
openedInJob.push(this);
|
|
276
|
+
if (!closing) {
|
|
277
|
+
closing = true;
|
|
278
|
+
// A microtask runs before the job ends, when the engine lets go of what deref() gave.
|
|
279
|
+
void Promise.resolve().then(closeOpened);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return opened;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Lets go of the entries kept: the job that read them is over. */
|
|
286
|
+
close(): void {
|
|
287
|
+
this.opened = null;
|
|
288
|
+
}
|
|
289
|
+
|
|
201
290
|
/** The name at a for-in's index: a key, a dynamic property's name, or "" once a key is gone. */
|
|
202
291
|
nameAt(index: number): object | string {
|
|
203
292
|
const held = this.slots[index - 1];
|
|
@@ -205,7 +294,7 @@ export class DictionaryKeys {
|
|
|
205
294
|
return "";
|
|
206
295
|
}
|
|
207
296
|
|
|
208
|
-
return typeof held === "string" ? held : (this.
|
|
297
|
+
return typeof held === "string" ? held : (this.at(held, index - 1)?.key ?? "");
|
|
209
298
|
}
|
|
210
299
|
|
|
211
300
|
/** The value at a for-in's index: a key's, undefined once it is gone, or NAMED. */
|
|
@@ -219,10 +308,19 @@ export class DictionaryKeys {
|
|
|
219
308
|
return NAMED;
|
|
220
309
|
}
|
|
221
310
|
|
|
222
|
-
const value = this.
|
|
311
|
+
const value = this.at(held, index - 1)?.value;
|
|
223
312
|
return value === GONE ? undefined : value;
|
|
224
313
|
}
|
|
225
314
|
|
|
315
|
+
/** The entry slot `i` holds: a weak one as this job's for-in read it, if it did. */
|
|
316
|
+
private at(held: Held, i: number): Entry | undefined {
|
|
317
|
+
if (!this.weak) {
|
|
318
|
+
return held as Entry;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return this.opened?.[i] ?? (held as WeakRef<Entry>).deref();
|
|
322
|
+
}
|
|
323
|
+
|
|
226
324
|
/** The entry a slot or `added` holds, or undefined once its key is collected. */
|
|
227
325
|
private open(held: Held): Entry | undefined {
|
|
228
326
|
return this.weak ? (held as WeakRef<Entry>).deref() : (held as Entry);
|
|
@@ -249,8 +347,11 @@ export class DictionaryKeys {
|
|
|
249
347
|
}
|
|
250
348
|
}
|
|
251
349
|
|
|
252
|
-
/**
|
|
253
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Those of `added` still there; the others are let go, their keys with
|
|
352
|
+
* them. Their entries go into `entries`, for a caller to read none twice.
|
|
353
|
+
*/
|
|
354
|
+
private settle(added: Held[], entries?: Entry[]): Held[] {
|
|
254
355
|
let kept = 0;
|
|
255
356
|
for (let i = 0; i < added.length; i++) {
|
|
256
357
|
const held = added[i];
|
|
@@ -261,6 +362,7 @@ export class DictionaryKeys {
|
|
|
261
362
|
|
|
262
363
|
if (entry.value !== GONE) {
|
|
263
364
|
added[kept++] = held;
|
|
365
|
+
entries?.push(entry);
|
|
264
366
|
} else {
|
|
265
367
|
this.map.delete(entry.key as object);
|
|
266
368
|
entry.key = null;
|
|
@@ -286,10 +388,16 @@ export class DictionaryKeys {
|
|
|
286
388
|
}
|
|
287
389
|
|
|
288
390
|
const slots = this.slots;
|
|
391
|
+
// In step with the slots: an entry freed is no longer its slot's, one given a slot is.
|
|
392
|
+
const opened = this.opened;
|
|
289
393
|
const free: number[] = [];
|
|
290
394
|
for (let i = 0; i < deleted.length; i++) {
|
|
291
395
|
const entry = deleted[i];
|
|
292
396
|
slots[entry.slot] = null;
|
|
397
|
+
if (opened !== null) {
|
|
398
|
+
opened[entry.slot] = null;
|
|
399
|
+
}
|
|
400
|
+
|
|
293
401
|
free.push(entry.slot);
|
|
294
402
|
entry.slot = -1;
|
|
295
403
|
this.spare.push(entry);
|
|
@@ -322,15 +430,24 @@ export class DictionaryKeys {
|
|
|
322
430
|
const i = next < free.length ? free[next++] : slots.length;
|
|
323
431
|
slots[i] = name;
|
|
324
432
|
this.named.set(name, i);
|
|
433
|
+
if (opened !== null) {
|
|
434
|
+
opened[i] = null;
|
|
435
|
+
}
|
|
325
436
|
}
|
|
326
437
|
}
|
|
327
438
|
}
|
|
328
439
|
|
|
329
|
-
const
|
|
440
|
+
const entries: Entry[] = [];
|
|
441
|
+
const added = this.settle(this.added, entries);
|
|
330
442
|
for (let n = 0; n < added.length; n++) {
|
|
331
443
|
const i = next < free.length ? free[next++] : slots.length;
|
|
444
|
+
const entry = entries[n];
|
|
332
445
|
slots[i] = added[n];
|
|
333
|
-
|
|
446
|
+
entry.slot = i;
|
|
447
|
+
if (opened !== null) {
|
|
448
|
+
// settle read it, so the engine keeps it for this job already.
|
|
449
|
+
opened[i] = entry;
|
|
450
|
+
}
|
|
334
451
|
}
|
|
335
452
|
|
|
336
453
|
// Kept for as many keys as were added since the last start, as the next
|
package/src/avm2/enumeration.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { DictionaryKeys } from "./dictionary-keys.js";
|
|
|
9
9
|
import { type DynamicProperties, GONE, type Walk } from "./dynamic-properties.js";
|
|
10
10
|
import { Namespace } from "./names.js";
|
|
11
11
|
import { FloatVector } from "./natives/float-vector.js";
|
|
12
|
+
import type { Traits } from "./traits.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* What for-ins over one object keep apart from its names: elements are
|
|
@@ -36,6 +37,8 @@ interface SparseIndices {
|
|
|
36
37
|
/** How many were given since they were taken, and whether they were taken again since this for-in met them. */
|
|
37
38
|
sinceTaken: number;
|
|
38
39
|
retaken: boolean;
|
|
40
|
+
/** Whether avmplus holds the Array's elements in its hashtable (see hashed). */
|
|
41
|
+
hashed: boolean;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
@@ -109,15 +112,50 @@ export function ownNames(o: AsObject): EnumeratedName[] {
|
|
|
109
112
|
* The name of `o`, not a Dictionary, at a for-in's index among the names
|
|
110
113
|
* past its elements, the index less their length.
|
|
111
114
|
*/
|
|
112
|
-
export function enumerated(o: AsObject, at: number, index: number): string {
|
|
115
|
+
export function enumerated(o: AsObject, at: number, index: number): string | number {
|
|
113
116
|
// The slots the step itself went through, without looking them up again.
|
|
114
117
|
const walk =
|
|
115
118
|
o === lastStep.o && index === lastStep.index && lastStep.walk !== null
|
|
116
119
|
? lastStep.walk
|
|
117
120
|
: walkOf(o);
|
|
118
|
-
return walk?.slots[at - 1] ??
|
|
121
|
+
return walk?.slots[at - 1] ?? unslotted(index);
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
/**
|
|
125
|
+
* The name at a for-in's index that no slot holds: an element's, where the
|
|
126
|
+
* index is negative, one a step by a sparse Array's indices gave (see
|
|
127
|
+
* stepBy), found here so that the names of every other step are looked up
|
|
128
|
+
* as before; else none.
|
|
129
|
+
*/
|
|
130
|
+
function unslotted(index: number): string | number {
|
|
131
|
+
if (index >= 0) {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const element = -index - 1;
|
|
136
|
+
return element < INT_ATOM_LIMIT ? element : String(element);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The value of the element a step by a sparse Array's indices gave at
|
|
141
|
+
* for-in index `index`, negative, if `o` reads its elements as an Array
|
|
142
|
+
* does (`plain`) and it is still there; else `missing`.
|
|
143
|
+
*/
|
|
144
|
+
export function sparseElement(o: AsObject, index: number, plain: boolean, missing: Value): Value {
|
|
145
|
+
const element = -index - 1;
|
|
146
|
+
if (plain) {
|
|
147
|
+
const value = o.$a[element];
|
|
148
|
+
if (value !== undefined || element in o.$a) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return missing;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The name of the element at a for-in's index, as a property's. */
|
|
157
|
+
export const elementName = (index: number) => String(index < 0 ? -index - 1 : index - 1);
|
|
158
|
+
|
|
121
159
|
function walkOf(o: AsObject): Walk | null {
|
|
122
160
|
const d: DynamicProperties | null | undefined = o.$d;
|
|
123
161
|
return d ? d.walk : null;
|
|
@@ -135,10 +173,29 @@ export function nextIndex(
|
|
|
135
173
|
o: AsObject,
|
|
136
174
|
index: number,
|
|
137
175
|
): number {
|
|
138
|
-
//
|
|
176
|
+
// What the last step read of the object stands: past its first step, no need to look again.
|
|
139
177
|
const known = o === lastStep.o && index === lastStep.index;
|
|
140
|
-
|
|
141
|
-
|
|
178
|
+
stepKnown = known;
|
|
179
|
+
let a: Value[] | FloatVector | undefined;
|
|
180
|
+
if (known) {
|
|
181
|
+
a = lastStep.elements;
|
|
182
|
+
|
|
183
|
+
// The element right after the last, there and not hidden: nearly every
|
|
184
|
+
// step. Not over an Array walked by its sparse indices, where the one
|
|
185
|
+
// after is a hole nearly every time.
|
|
186
|
+
if (
|
|
187
|
+
a !== undefined &&
|
|
188
|
+
lastStep.hidden === undefined &&
|
|
189
|
+
index < a.length &&
|
|
190
|
+
o !== sparseOwner &&
|
|
191
|
+
index in a
|
|
192
|
+
) {
|
|
193
|
+
lastStep.index = index + 1;
|
|
194
|
+
return index + 1;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
a = o.$a;
|
|
198
|
+
lastStep.elements = a;
|
|
142
199
|
lastStep.walk = null;
|
|
143
200
|
if (a !== undefined) {
|
|
144
201
|
// Not the keys of a Dictionary whose for-in was left off.
|
|
@@ -151,13 +208,14 @@ export function nextIndex(
|
|
|
151
208
|
? nextOfNames(o, index, known)
|
|
152
209
|
: nextOfElements(enumerating, o, a, index, known);
|
|
153
210
|
if (next === 0) {
|
|
154
|
-
|
|
155
|
-
lastStep.keys = null;
|
|
156
|
-
lastStep.walk = null;
|
|
211
|
+
letGo();
|
|
157
212
|
} else {
|
|
158
213
|
lastStep.o = o;
|
|
159
214
|
lastStep.index = next;
|
|
160
|
-
|
|
215
|
+
if (!known) {
|
|
216
|
+
lastStep.hidden = o.$dontEnum;
|
|
217
|
+
lastStep.traits = o.$traits;
|
|
218
|
+
}
|
|
161
219
|
}
|
|
162
220
|
|
|
163
221
|
return next;
|
|
@@ -188,27 +246,72 @@ export function dictionaryAt(o: AsObject, index: number): DictionaryKeys | null
|
|
|
188
246
|
}
|
|
189
247
|
|
|
190
248
|
/**
|
|
191
|
-
* The step a for-in last took: the object, the index it gave,
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
249
|
+
* The step a for-in last took: the object, the index it gave, and what the
|
|
250
|
+
* step read of the object: its elements, its hidden names, its traits and
|
|
251
|
+
* its keys if a Dictionary, so that the next step and the nextName and
|
|
252
|
+
* nextValue right after it need not read them again. The steps of every
|
|
253
|
+
* for-in go through this one code, over objects of every class, where the
|
|
254
|
+
* engine finds each of those by a lookup, several to a step: more than the
|
|
255
|
+
* step itself costs. Let go when a for-in ends.
|
|
256
|
+
*
|
|
257
|
+
* What it keeps must stand for as long as the object is the same: one
|
|
258
|
+
* without elements never gains them, and its traits and whether it is a
|
|
259
|
+
* Dictionary are for good; whoever gives an object other elements, or its
|
|
260
|
+
* first hidden name, says so (forgetStep).
|
|
196
261
|
*/
|
|
197
262
|
const lastStep: {
|
|
198
263
|
o: AsObject;
|
|
199
264
|
index: number;
|
|
200
|
-
|
|
265
|
+
/** Its elements, undefined for an object without any. */
|
|
266
|
+
elements: Value[] | FloatVector | undefined;
|
|
267
|
+
/** Its names hidden from for-ins, undefined while it has had none. */
|
|
268
|
+
hidden: Set<string> | undefined;
|
|
269
|
+
traits: Traits | undefined;
|
|
201
270
|
keys: DictionaryKeys | null;
|
|
202
271
|
/** The slots of the names it went through, past any elements: the object's, as they never change. */
|
|
203
272
|
walk: Walk | null;
|
|
204
273
|
} = {
|
|
205
274
|
o: null,
|
|
206
275
|
index: 0,
|
|
207
|
-
|
|
276
|
+
elements: undefined,
|
|
277
|
+
hidden: undefined,
|
|
278
|
+
traits: undefined,
|
|
208
279
|
keys: null,
|
|
209
280
|
walk: null,
|
|
210
281
|
};
|
|
211
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Whether the step nextIndex is taking goes on from the last, so that the
|
|
285
|
+
* last one's hidden names are its object's: else they are read from the
|
|
286
|
+
* object, by a step that comes to ask, which one over an object with
|
|
287
|
+
* nothing to go through never does.
|
|
288
|
+
*/
|
|
289
|
+
let stepKnown = false;
|
|
290
|
+
|
|
291
|
+
function hiddenOf(o: AsObject): Set<string> | undefined {
|
|
292
|
+
return stepKnown ? lastStep.hidden : o.$dontEnum;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* `o` has other elements than it had (its storage, not what is in it), or
|
|
297
|
+
* its first hidden name: a for-in's next step over it reads them again.
|
|
298
|
+
*/
|
|
299
|
+
export function forgetStep(o: AsObject): void {
|
|
300
|
+
if (o === lastStep.o) {
|
|
301
|
+
letGo();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The for-in is over: nothing of its object is kept, which would hold its class and all that hangs on it. */
|
|
306
|
+
function letGo(): void {
|
|
307
|
+
lastStep.o = null;
|
|
308
|
+
lastStep.elements = undefined;
|
|
309
|
+
lastStep.hidden = undefined;
|
|
310
|
+
lastStep.traits = undefined;
|
|
311
|
+
lastStep.keys = null;
|
|
312
|
+
lastStep.walk = null;
|
|
313
|
+
}
|
|
314
|
+
|
|
212
315
|
/**
|
|
213
316
|
* Whether a for-in's step over `o` at `index` goes on from the last one
|
|
214
317
|
* nextIndex gave, so that `o` is an ordinary object: no hook, no QName.
|
|
@@ -217,13 +320,14 @@ export function continues(o: Value, index: number): boolean {
|
|
|
217
320
|
return o === lastStep.o && index === lastStep.index;
|
|
218
321
|
}
|
|
219
322
|
|
|
220
|
-
/** The
|
|
221
|
-
export function elementsAt(o: AsObject, index: number):
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
323
|
+
/** The elements of `o` at a for-in's index, whose length is where those end and its other names start. */
|
|
324
|
+
export function elementsAt(o: AsObject, index: number): Value[] | FloatVector | undefined {
|
|
325
|
+
return o === lastStep.o && index === lastStep.index ? lastStep.elements : o?.$a;
|
|
326
|
+
}
|
|
225
327
|
|
|
226
|
-
|
|
328
|
+
/** The traits of `o` at a for-in's index. */
|
|
329
|
+
export function traitsAt(o: AsObject, index: number): Traits | undefined {
|
|
330
|
+
return o === lastStep.o && index === lastStep.index ? lastStep.traits : o?.$traits;
|
|
227
331
|
}
|
|
228
332
|
|
|
229
333
|
/** As nextIndex, over an object with elements. */
|
|
@@ -234,20 +338,24 @@ function nextOfElements(
|
|
|
234
338
|
index: number,
|
|
235
339
|
known: boolean,
|
|
236
340
|
): number {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
341
|
+
// The start of a for-in, or a step from a negative index: both off the
|
|
342
|
+
// path of a step over elements, which this one comparison is all of.
|
|
343
|
+
if (index <= 0) {
|
|
344
|
+
const next = startOrGoOn(enumerating, o, a, index, known);
|
|
345
|
+
if (index < 0) {
|
|
346
|
+
return next;
|
|
347
|
+
}
|
|
240
348
|
}
|
|
241
349
|
|
|
242
350
|
const length = a.length;
|
|
243
351
|
if (index < length) {
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
return
|
|
352
|
+
const next = nextElement(enumerating, o, a, index);
|
|
353
|
+
if (next !== 0) {
|
|
354
|
+
return next;
|
|
247
355
|
}
|
|
248
356
|
}
|
|
249
357
|
|
|
250
|
-
const at =
|
|
358
|
+
const at = index > length ? index - length : 0;
|
|
251
359
|
|
|
252
360
|
// An Array that has never had other names needs no list of them.
|
|
253
361
|
const next = at === 0 && !o.$d?.size ? 0 : nextNameIndex(o, at, false, known);
|
|
@@ -263,7 +371,11 @@ function nextOfElements(
|
|
|
263
371
|
/** After this many holes in a row, the next element is found by an Array's indices, so that a sparse one stays cheap. */
|
|
264
372
|
const HOLE_SCAN_LIMIT = 16;
|
|
265
373
|
|
|
266
|
-
/**
|
|
374
|
+
/**
|
|
375
|
+
* The index a for-in goes on from once it is given the first element of `o`
|
|
376
|
+
* at or after `from`, or 0 without one: one more than the element's, or
|
|
377
|
+
* that negated (see stepBy).
|
|
378
|
+
*/
|
|
267
379
|
function nextElement(
|
|
268
380
|
enumerating: WeakMap<object, Enumeration>,
|
|
269
381
|
o: AsObject,
|
|
@@ -272,24 +384,24 @@ function nextElement(
|
|
|
272
384
|
): number {
|
|
273
385
|
const length = a.length;
|
|
274
386
|
if (a instanceof FloatVector) {
|
|
275
|
-
return from < length ? from :
|
|
387
|
+
return from < length ? from + 1 : 0;
|
|
276
388
|
}
|
|
277
389
|
|
|
278
390
|
if (enumerable(o, a, from)) {
|
|
279
|
-
return from;
|
|
391
|
+
return from + 1;
|
|
280
392
|
}
|
|
281
393
|
|
|
282
394
|
let e: Enumeration | undefined;
|
|
283
395
|
if (sparseKept !== 0) {
|
|
284
396
|
if (o === sparseOwner) {
|
|
285
|
-
return
|
|
397
|
+
return stepBy(o, a, ownerSparse as SparseIndices, from);
|
|
286
398
|
}
|
|
287
399
|
|
|
288
400
|
e = enumerating.get(o);
|
|
289
401
|
if (e?.sparse !== undefined) {
|
|
290
402
|
sparseOwner = o;
|
|
291
403
|
ownerSparse = e.sparse;
|
|
292
|
-
return
|
|
404
|
+
return stepBy(o, a, e.sparse, from);
|
|
293
405
|
}
|
|
294
406
|
}
|
|
295
407
|
|
|
@@ -297,28 +409,104 @@ function nextElement(
|
|
|
297
409
|
const scanned = Math.min(length, from + HOLE_SCAN_LIMIT);
|
|
298
410
|
for (let i = from + 1; i < scanned; i++) {
|
|
299
411
|
if (enumerable(o, a, i)) {
|
|
300
|
-
return i;
|
|
412
|
+
return i + 1;
|
|
301
413
|
}
|
|
302
414
|
}
|
|
303
415
|
|
|
304
416
|
if (scanned === length) {
|
|
305
|
-
return
|
|
417
|
+
return 0;
|
|
306
418
|
}
|
|
307
419
|
|
|
420
|
+
return stepBy(o, a, takeSparse(enumerating, o, a, e), scanned);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Take the indices of sparse Array `o` for the for-in over it, which `e` keeps things for, if any. */
|
|
424
|
+
function takeSparse(
|
|
425
|
+
enumerating: WeakMap<object, Enumeration>,
|
|
426
|
+
o: AsObject,
|
|
427
|
+
a: Value[],
|
|
428
|
+
e: Enumeration | undefined,
|
|
429
|
+
): SparseIndices {
|
|
308
430
|
const keeper: Enumeration = e ?? enumerating.get(o) ?? {};
|
|
309
431
|
enumerating.set(o, keeper);
|
|
310
432
|
sparseKept++;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
433
|
+
const indices = indicesOf(a, 0);
|
|
434
|
+
const sparse: SparseIndices = {
|
|
435
|
+
indices,
|
|
436
|
+
length: a.length,
|
|
314
437
|
at: -1,
|
|
315
438
|
given: -1,
|
|
316
439
|
sinceTaken: 0,
|
|
317
440
|
retaken: false,
|
|
441
|
+
hashed: hashed(indices),
|
|
318
442
|
};
|
|
443
|
+
keeper.sparse = sparse;
|
|
319
444
|
sparseOwner = o;
|
|
320
|
-
ownerSparse =
|
|
321
|
-
return
|
|
445
|
+
ownerSparse = sparse;
|
|
446
|
+
return sparse;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The index a for-in goes on from after the first element of `o` at or
|
|
451
|
+
* after `from` by its sparse indices, or 0. Where avmplus walks the Array
|
|
452
|
+
* by index, that is one more than the element's, and the next step first
|
|
453
|
+
* asks whether the hole right after it was filled, as avmplus reaches one
|
|
454
|
+
* filled during the walk. Where avmplus walks its hashtable, such an
|
|
455
|
+
* element is reached or not by where it hashes, which no order of indices
|
|
456
|
+
* follows, and asking is a miss in the engine's dictionary-mode elements,
|
|
457
|
+
* a third of what a step cost: the index given is negative, the element's
|
|
458
|
+
* plus one negated, so that the next step comes straight back to the
|
|
459
|
+
* indices (startOrGoOn), told apart by the comparison with 0 every step
|
|
460
|
+
* makes already.
|
|
461
|
+
*/
|
|
462
|
+
function stepBy(o: AsObject, a: Value[], sparse: SparseIndices, from: number): number {
|
|
463
|
+
const i = nextSparse(o, a, sparse, from);
|
|
464
|
+
if (i < 0) {
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return sparse.hashed ? -(i + 1) : i + 1;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* What nextOfElements does first at a for-in's start, index 0: the indices
|
|
473
|
+
* and the deleted names' slots a for-in left off kept are let go. From a
|
|
474
|
+
* negative index, one stepBy gave, the whole step, by the indices alone,
|
|
475
|
+
* then the Array's other names.
|
|
476
|
+
*/
|
|
477
|
+
function startOrGoOn(
|
|
478
|
+
enumerating: WeakMap<object, Enumeration>,
|
|
479
|
+
o: AsObject,
|
|
480
|
+
a: Value[] | FloatVector,
|
|
481
|
+
index: number,
|
|
482
|
+
known: boolean,
|
|
483
|
+
): number {
|
|
484
|
+
if (index === 0) {
|
|
485
|
+
dropSparse(enumerating, o);
|
|
486
|
+
walkOf(o)?.freeDeleted();
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const from = -index;
|
|
491
|
+
let next = 0;
|
|
492
|
+
if (from < a.length) {
|
|
493
|
+
if (o === sparseOwner) {
|
|
494
|
+
next = stepBy(o, a as Value[], ownerSparse as SparseIndices, from);
|
|
495
|
+
} else {
|
|
496
|
+
const sparse = sparseKept !== 0 ? enumerating.get(o)?.sparse : undefined;
|
|
497
|
+
if (sparse !== undefined) {
|
|
498
|
+
sparseOwner = o;
|
|
499
|
+
ownerSparse = sparse;
|
|
500
|
+
next = stepBy(o, a as Value[], sparse, from);
|
|
501
|
+
} else {
|
|
502
|
+
// Dropped when another for-in over the Array started: taken again from here.
|
|
503
|
+
next = nextElement(enumerating, o, a, from);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Past the last element, as a step from the Array's length.
|
|
509
|
+
return next !== 0 ? next : nextOfElements(enumerating, o, a, a.length, known);
|
|
322
510
|
}
|
|
323
511
|
|
|
324
512
|
/**
|
|
@@ -378,12 +566,32 @@ function retake(a: Value[], sparse: SparseIndices): void {
|
|
|
378
566
|
sparse.at = -1;
|
|
379
567
|
sparse.given = -1;
|
|
380
568
|
sparse.sinceTaken = 0;
|
|
569
|
+
// An Array in avmplus' hashtable never leaves it.
|
|
570
|
+
sparse.hashed ||= hashed(sparse.indices);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Whether avmplus holds an Array with these elements in its hashtable, as
|
|
575
|
+
* it must when they span more than 32 slots and more than four times one
|
|
576
|
+
* more than their count (ArrayObject's shouldBeSparse): its dense part
|
|
577
|
+
* never holds so few over so many. Else avmplus may hold them dense,
|
|
578
|
+
* walking them by index, as the for-in here does.
|
|
579
|
+
*/
|
|
580
|
+
function hashed(indices: number[]): boolean {
|
|
581
|
+
const n = indices.length;
|
|
582
|
+
if (n === 0) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const span = indices[n - 1] - indices[0] + 1;
|
|
587
|
+
return span > 32 && span > (n + 1) * 4;
|
|
381
588
|
}
|
|
382
589
|
|
|
383
590
|
/** An Array's indices from `from` on, in order, as Object.keys gives them, appended to `indices`. */
|
|
384
591
|
function indicesOf(a: Value[], from: number, indices: number[] = []): number[] {
|
|
385
|
-
|
|
386
|
-
|
|
592
|
+
const keys = Object.keys(a);
|
|
593
|
+
for (let k = 0; k < keys.length; k++) {
|
|
594
|
+
const i = Number(keys[k]);
|
|
387
595
|
if (i >= from) {
|
|
388
596
|
indices.push(i);
|
|
389
597
|
}
|
|
@@ -408,8 +616,10 @@ function firstAtOrAfter(indices: number[], from: number): number {
|
|
|
408
616
|
return low;
|
|
409
617
|
}
|
|
410
618
|
|
|
619
|
+
/** Whether element `i` of `o`, the object nextIndex steps over, is there and not hidden. */
|
|
411
620
|
function enumerable(o: AsObject, a: Value[], i: number): boolean {
|
|
412
|
-
|
|
621
|
+
const hidden = hiddenOf(o);
|
|
622
|
+
return i in a && (hidden === undefined || !hidden.has(String(i)));
|
|
413
623
|
}
|
|
414
624
|
|
|
415
625
|
/**
|
|
@@ -437,8 +647,9 @@ function nextNameIndex(o: AsObject, index: number, starting: boolean, known: boo
|
|
|
437
647
|
// A name deleted since the for-in started is passed over.
|
|
438
648
|
const slots = walk.slots;
|
|
439
649
|
const live = walk.live;
|
|
650
|
+
const hidden = hiddenOf(o);
|
|
440
651
|
for (let i = index; i < slots.length; i++) {
|
|
441
|
-
if (live[i] !== GONE && !
|
|
652
|
+
if (live[i] !== GONE && (hidden === undefined || !hidden.has(slots[i] as string))) {
|
|
442
653
|
return i + 1;
|
|
443
654
|
}
|
|
444
655
|
}
|
|
@@ -237,7 +237,13 @@ export const arrayNatives: Natives = {
|
|
|
237
237
|
"Array#set:length": (rt) =>
|
|
238
238
|
function (this: AsObject, n: Value) {
|
|
239
239
|
if (!sealed(rt, this, 0)) {
|
|
240
|
-
|
|
240
|
+
// Array.prototype.push sets the length its elements already gave it, at
|
|
241
|
+
// every call: the engine's setter is a call into its runtime even then.
|
|
242
|
+
const a: Value[] = this.$a;
|
|
243
|
+
const length = rt.toUint(n);
|
|
244
|
+
if (length !== a.length) {
|
|
245
|
+
a.length = length;
|
|
246
|
+
}
|
|
241
247
|
}
|
|
242
248
|
},
|
|
243
249
|
[`Array#${AS3}::push`]: (rt) =>
|