@swf2es/runtime 0.1.19 → 0.1.21
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/amf.d.ts +1 -1
- package/dist/avm2/amf.d.ts.map +1 -1
- package/dist/avm2/amf.js +1 -1
- package/dist/avm2/amf.js.map +1 -1
- package/dist/avm2/enumeration.d.ts +1 -19
- package/dist/avm2/enumeration.d.ts.map +1 -1
- package/dist/avm2/enumeration.js +1 -51
- package/dist/avm2/enumeration.js.map +1 -1
- package/dist/avm2/errors.d.ts +2 -0
- package/dist/avm2/errors.d.ts.map +1 -1
- package/dist/avm2/errors.js +40 -2
- package/dist/avm2/errors.js.map +1 -1
- package/dist/avm2/index.d.ts +3 -1
- package/dist/avm2/index.d.ts.map +1 -1
- package/dist/avm2/index.js +3 -1
- package/dist/avm2/index.js.map +1 -1
- package/dist/avm2/natives/aliases.js +1 -1
- package/dist/avm2/natives/aliases.js.map +1 -1
- package/dist/avm2/natives/bytearray.d.ts +0 -154
- package/dist/avm2/natives/bytearray.d.ts.map +1 -1
- package/dist/avm2/natives/bytearray.js +1 -495
- package/dist/avm2/natives/bytearray.js.map +1 -1
- package/dist/avm2/natives/bytes.d.ts +141 -0
- package/dist/avm2/natives/bytes.d.ts.map +1 -0
- package/dist/avm2/natives/bytes.js +458 -0
- package/dist/avm2/natives/bytes.js.map +1 -0
- package/dist/avm2/natives/concurrent.js +1 -1
- package/dist/avm2/natives/concurrent.js.map +1 -1
- package/dist/avm2/natives/shared-bytes.d.ts +45 -0
- package/dist/avm2/natives/shared-bytes.d.ts.map +1 -0
- package/dist/avm2/natives/shared-bytes.js +52 -0
- package/dist/avm2/natives/shared-bytes.js.map +1 -0
- package/dist/avm2/natives/shell.d.ts.map +1 -1
- package/dist/avm2/natives/shell.js +3 -1
- package/dist/avm2/natives/shell.js.map +1 -1
- package/dist/avm2/options.d.ts +1 -1
- package/dist/avm2/options.d.ts.map +1 -1
- package/dist/avm2/runtime-xml.d.ts +11 -0
- package/dist/avm2/runtime-xml.d.ts.map +1 -0
- package/dist/avm2/runtime-xml.js +44 -0
- package/dist/avm2/runtime-xml.js.map +1 -0
- package/dist/avm2/runtime.d.ts +5 -13
- package/dist/avm2/runtime.d.ts.map +1 -1
- package/dist/avm2/runtime.js +17 -67
- package/dist/avm2/runtime.js.map +1 -1
- package/dist/avm2/sparse-indices.d.ts +34 -0
- package/dist/avm2/sparse-indices.d.ts.map +1 -0
- package/dist/avm2/sparse-indices.js +54 -0
- package/dist/avm2/sparse-indices.js.map +1 -0
- package/package.json +2 -2
- package/src/avm2/amf.ts +1 -1
- package/src/avm2/enumeration.ts +1 -76
- package/src/avm2/errors.ts +48 -3
- package/src/avm2/index.ts +9 -12
- package/src/avm2/natives/aliases.ts +1 -1
- package/src/avm2/natives/bytearray.ts +1 -604
- package/src/avm2/natives/bytes.ts +543 -0
- package/src/avm2/natives/concurrent.ts +1 -1
- package/src/avm2/natives/shared-bytes.ts +99 -0
- package/src/avm2/natives/shell.ts +3 -8
- package/src/avm2/options.ts +1 -1
- package/src/avm2/runtime-xml.ts +54 -0
- package/src/avm2/runtime.ts +31 -78
- package/src/avm2/sparse-indices.ts +80 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
// A ByteArray's state, Bytes: its buffer, grown as avmplus' Grower grows
|
|
2
|
+
// it, its length and position, and the capacity its runtime counts; which
|
|
3
|
+
// the natives (bytearray.ts), AMF and domain memory read and write.
|
|
4
|
+
//
|
|
5
|
+
// Translated from avmplus' core/ByteArrayGlue.cpp, this file is subject to
|
|
6
|
+
// the Mozilla Public License, v. 2.0: http://mozilla.org/MPL/2.0/.
|
|
7
|
+
import type { AsObject } from "../descriptors.js";
|
|
8
|
+
import type { Runtime } from "../runtime.js";
|
|
9
|
+
import {
|
|
10
|
+
canShareMemory,
|
|
11
|
+
type GrowableShared,
|
|
12
|
+
type GrowableSharedConstructor,
|
|
13
|
+
notIsolated,
|
|
14
|
+
type SharedBytesMemory,
|
|
15
|
+
sharedObjectsOf,
|
|
16
|
+
} from "./shared-bytes.js";
|
|
17
|
+
|
|
18
|
+
const kGrowthIncr = 4096;
|
|
19
|
+
const kHugeGrowthThreshold = 24 * 1024 * 1024;
|
|
20
|
+
const kHugeGrowthIncr = 24 * 1024 * 1024;
|
|
21
|
+
/**
|
|
22
|
+
* The most a ByteArray holds: what a 32-bit avmshell, as the oracle's, can
|
|
23
|
+
* allocate, beyond which it fails with MemoryError 1000, and less than a
|
|
24
|
+
* browser's ArrayBuffer may be refused at.
|
|
25
|
+
*/
|
|
26
|
+
const kMaxCapacity = 0x80000000;
|
|
27
|
+
/** DomainEnv::GLOBAL_MEMORY_MIN_SIZE: domain memory's least length. */
|
|
28
|
+
export const GLOBAL_MEMORY_MIN_SIZE = 1024;
|
|
29
|
+
|
|
30
|
+
/** A count of bytes, which a ByteArray and its runtime share. */
|
|
31
|
+
interface Count {
|
|
32
|
+
bytes: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The capacity of each runtime's ByteArrays, live or not yet collected. */
|
|
36
|
+
const runtimeCapacity = new WeakMap<Runtime, Count>();
|
|
37
|
+
/** A ByteArray collected: its capacity no longer counted, by its runtime's count and its own. */
|
|
38
|
+
const collected = new FinalizationRegistry<{ runtime: Count; own: Count }>(({ runtime, own }) => {
|
|
39
|
+
runtime.bytes -= own.bytes;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** The capacity of `rt`'s ByteArrays, which avmshell's System counts in its memory. */
|
|
43
|
+
export function byteArrayCapacity(rt: Runtime): number {
|
|
44
|
+
return runtimeCapacity.get(rt)?.bytes ?? 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A ByteArray's state: its buffer, as long as its capacity, and its length
|
|
49
|
+
* and position. A read or write takes its offset first: growing replaces
|
|
50
|
+
* the buffer and its view.
|
|
51
|
+
*
|
|
52
|
+
* A length or position is stored through `>>> 0`, as a uint32: an int32
|
|
53
|
+
* while below 2^31, and a double only from there.
|
|
54
|
+
* JavaScriptCore keeps a number its JIT once made a double a double, and
|
|
55
|
+
* an addition that has seen one makes doubles from then on: a double
|
|
56
|
+
* offset fails the int32 check of a DataView or typed array access, and
|
|
57
|
+
* after enough failures JavaScriptCore calls DataView's methods instead
|
|
58
|
+
* of inlining them, a quarter slower on every read and write. V8 makes
|
|
59
|
+
* small integers of them itself.
|
|
60
|
+
*/
|
|
61
|
+
export class Bytes {
|
|
62
|
+
buffer: Uint8Array = new Uint8Array(0);
|
|
63
|
+
view = new DataView(this.buffer.buffer);
|
|
64
|
+
length = 0;
|
|
65
|
+
position = 0;
|
|
66
|
+
littleEndian = false;
|
|
67
|
+
/** As ByteArray's constructor: ByteArray.defaultObjectEncoding when it is made. */
|
|
68
|
+
objectEncoding: number;
|
|
69
|
+
/** Whether it is the domain memory, which it then tells when its buffer or length changes. */
|
|
70
|
+
subscribed = false;
|
|
71
|
+
/**
|
|
72
|
+
* Whether `shareable` was set true, which cannot be undone: the
|
|
73
|
+
* ByteArray then crosses to another worker as its memory, not a copy.
|
|
74
|
+
*/
|
|
75
|
+
shareable = false;
|
|
76
|
+
/**
|
|
77
|
+
* Its memory once it has crossed to another worker (share), which its
|
|
78
|
+
* buffer and view are then over, tracking its length as it grows; null
|
|
79
|
+
* until then, when it is a ByteArray like any other.
|
|
80
|
+
*/
|
|
81
|
+
shared: SharedBytesMemory | null = null;
|
|
82
|
+
/** The shared memory's words, the length first, and its bytes as words for the atomics. */
|
|
83
|
+
private sharedWords: Int32Array | null = null;
|
|
84
|
+
private sharedInts: Int32Array | null = null;
|
|
85
|
+
/** Its runtime's capacity count, and its own part of it. */
|
|
86
|
+
private readonly capacity: { runtime: Count; own: Count };
|
|
87
|
+
|
|
88
|
+
constructor(
|
|
89
|
+
readonly rt: Runtime,
|
|
90
|
+
readonly owner: AsObject,
|
|
91
|
+
) {
|
|
92
|
+
this.objectEncoding = rt.defaultObjectEncoding;
|
|
93
|
+
let runtime = runtimeCapacity.get(rt);
|
|
94
|
+
if (!runtime) {
|
|
95
|
+
runtime = { bytes: 0 };
|
|
96
|
+
runtimeCapacity.set(rt, runtime);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.capacity = { runtime, own: { bytes: 0 } };
|
|
100
|
+
collected.register(this, this.capacity);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Its buffer replaced by `next`, its runtime's capacity count with it. */
|
|
104
|
+
setBuffer(next: Uint8Array<ArrayBuffer>): void {
|
|
105
|
+
const { runtime, own } = this.capacity;
|
|
106
|
+
runtime.bytes += next.length - own.bytes;
|
|
107
|
+
own.bytes = next.length;
|
|
108
|
+
this.buffer = next;
|
|
109
|
+
this.view = new DataView(next.buffer);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get available(): number {
|
|
113
|
+
return this.length > this.position ? this.length - this.position : 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** As Grower::ReallocBackingStore: a new buffer, the old length's bytes copied and the rest zero. */
|
|
117
|
+
private realloc(capacityIn: number, minimum: number, fromSetter: boolean): void {
|
|
118
|
+
let capacity = capacityIn;
|
|
119
|
+
if (capacity < minimum) {
|
|
120
|
+
capacity = minimum;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!(this.buffer.length === 0 && fromSetter) && capacity < kGrowthIncr) {
|
|
124
|
+
capacity = kGrowthIncr;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (minimum > kMaxCapacity) {
|
|
128
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
capacity = Math.min(capacity, kMaxCapacity);
|
|
132
|
+
if (capacity === this.buffer.length) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (this.shared !== null) {
|
|
137
|
+
this.reshare(capacity);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let next: Uint8Array<ArrayBuffer>;
|
|
142
|
+
try {
|
|
143
|
+
next = new Uint8Array(capacity);
|
|
144
|
+
} catch {
|
|
145
|
+
// The host would not give it the memory, as the system would not give avmplus.
|
|
146
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
next.set(this.buffer.subarray(0, Math.min(capacity, this.length)));
|
|
150
|
+
this.setBuffer(next);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** As Grower::EnsureWritableCapacity: double, at least `minimum`, and 4096 unless the setter sizes an empty one. */
|
|
154
|
+
private ensure(minimum: number, fromSetter: boolean): void {
|
|
155
|
+
if (minimum > 0xffffffff - 2 * 4096) {
|
|
156
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (minimum > this.buffer.length) {
|
|
160
|
+
this.realloc(this.buffer.length * 2, minimum, fromSetter);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** As ByteArray::SetLengthCommon. */
|
|
165
|
+
setLength(length: number, fromSetter = false): void {
|
|
166
|
+
if (this.subscribed && length < GLOBAL_MEMORY_MIN_SIZE) {
|
|
167
|
+
throw this.rt.error("RangeError", 1506);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.reserve(length, fromSetter);
|
|
171
|
+
this.length = length >>> 0;
|
|
172
|
+
if (this.position > length) {
|
|
173
|
+
this.position = length >>> 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.notify();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The length made at least `length`, as a write past the end makes it:
|
|
181
|
+
* shared, by a compare and swap that only ever raises it, so that
|
|
182
|
+
* workers appending at once never lower what another made it.
|
|
183
|
+
*/
|
|
184
|
+
extend(length: number): void {
|
|
185
|
+
if (this.shared === null) {
|
|
186
|
+
this.setLength(length);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.reserve(length, false);
|
|
191
|
+
this.raise(length);
|
|
192
|
+
this.notify();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A shared length raised to `length` unless another worker made it as long. */
|
|
196
|
+
private raise(length: number): void {
|
|
197
|
+
const words = this.sharedWords as Int32Array;
|
|
198
|
+
for (;;) {
|
|
199
|
+
const now = Atomics.load(words, 0) >>> 0;
|
|
200
|
+
if (now >= length || Atomics.compareExchange(words, 0, now | 0, length | 0) >>> 0 === now) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** As Grower::SetLengthCommon, before the length is set: the capacity for `length`. */
|
|
207
|
+
private reserve(length: number, fromSetter: boolean): void {
|
|
208
|
+
if (!fromSetter || (length < kHugeGrowthThreshold && this.length < kHugeGrowthThreshold)) {
|
|
209
|
+
if (length > this.buffer.length) {
|
|
210
|
+
this.ensure(length, fromSetter);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
const rounded = Math.ceil(length / kHugeGrowthIncr) * kHugeGrowthIncr;
|
|
214
|
+
const capacity = rounded <= 0xffffffff - 2 * 4096 ? rounded : length;
|
|
215
|
+
if (capacity !== this.buffer.length) {
|
|
216
|
+
this.realloc(capacity, length, true);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Tell the domain memory, if this is it, where the bytes are now: a new view only if they moved or their length changed. */
|
|
222
|
+
notify(): void {
|
|
223
|
+
const rt = this.rt;
|
|
224
|
+
if (
|
|
225
|
+
this.subscribed &&
|
|
226
|
+
(rt.memory.buffer !== this.buffer.buffer || rt.memoryLength !== this.length)
|
|
227
|
+
) {
|
|
228
|
+
rt.memory = new DataView(this.buffer.buffer, 0, this.length);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** As ByteArray::Clear: no buffer at all; shared, every worker's ByteArray for it empty. */
|
|
233
|
+
clear(): void {
|
|
234
|
+
if (this.subscribed) {
|
|
235
|
+
throw this.rt.error("RangeError", 1506);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.position = 0;
|
|
239
|
+
if (this.shared !== null) {
|
|
240
|
+
// The memory cannot shrink: what it held is zeroed, as a new buffer
|
|
241
|
+
// would be once it grows again, before the length is told, so that a
|
|
242
|
+
// worker that grows it once it is empty writes into zeroes already.
|
|
243
|
+
this.buffer.fill(0);
|
|
244
|
+
this.length = 0;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.setBuffer(new Uint8Array(0));
|
|
249
|
+
this.length = 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Its memory, shared from now on: a growable SharedArrayBuffer its bytes
|
|
254
|
+
* move to once, and that this ByteArray, and every worker's ByteArray for
|
|
255
|
+
* it, uses from then on. Where memory cannot be shared, the error that
|
|
256
|
+
* names cross-origin isolation.
|
|
257
|
+
*/
|
|
258
|
+
share(): SharedBytesMemory {
|
|
259
|
+
if (this.shared !== null) {
|
|
260
|
+
return this.shared;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!canShareMemory()) {
|
|
264
|
+
throw notIsolated("a shareable ByteArray");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const capacity = Math.max(this.buffer.length, this.length);
|
|
268
|
+
const Shared = SharedArrayBuffer as unknown as GrowableSharedConstructor;
|
|
269
|
+
let data: SharedArrayBuffer | null = null;
|
|
270
|
+
// As much room to grow as a ByteArray has, or as much as the host reserves.
|
|
271
|
+
for (let max = kMaxCapacity; data === null; max /= 2) {
|
|
272
|
+
try {
|
|
273
|
+
data = new Shared(capacity, { maxByteLength: Math.max(max, capacity) });
|
|
274
|
+
} catch {
|
|
275
|
+
if (max <= capacity) {
|
|
276
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
new Uint8Array(data).set(this.buffer);
|
|
282
|
+
const memory: SharedBytesMemory = {
|
|
283
|
+
id: Math.floor(Math.random() * 2 ** 52),
|
|
284
|
+
meta: new SharedArrayBuffer(8),
|
|
285
|
+
data,
|
|
286
|
+
};
|
|
287
|
+
new Int32Array(memory.meta)[0] = this.length | 0;
|
|
288
|
+
this.adopt(memory);
|
|
289
|
+
sharedObjectsOf(this.rt).set(memory.id, new WeakRef(this.owner));
|
|
290
|
+
return memory;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Use `memory`, shared, as its bytes and length from now on. Its length
|
|
295
|
+
* becomes an accessor of the memory's word on this object alone, so that
|
|
296
|
+
* every read sees another worker's change and every write is seen, and
|
|
297
|
+
* a ByteArray that shares nothing reads a plain field as before.
|
|
298
|
+
*/
|
|
299
|
+
adopt(memory: SharedBytesMemory): void {
|
|
300
|
+
this.shared = memory;
|
|
301
|
+
this.shareable = true;
|
|
302
|
+
const words = new Int32Array(memory.meta);
|
|
303
|
+
this.sharedWords = words;
|
|
304
|
+
Object.defineProperty(this, "length", {
|
|
305
|
+
get: () => Atomics.load(words, 0) >>> 0,
|
|
306
|
+
set: (length: number) => Atomics.store(words, 0, length | 0),
|
|
307
|
+
configurable: true,
|
|
308
|
+
});
|
|
309
|
+
// Views that track the memory's length as any worker grows it.
|
|
310
|
+
this.buffer = new Uint8Array(memory.data);
|
|
311
|
+
this.view = new DataView(memory.data);
|
|
312
|
+
this.sharedInts = new Int32Array(memory.data);
|
|
313
|
+
const { runtime, own } = this.capacity;
|
|
314
|
+
runtime.bytes += memory.data.byteLength - own.bytes;
|
|
315
|
+
own.bytes = memory.data.byteLength;
|
|
316
|
+
if (this.subscribed) {
|
|
317
|
+
this.rt.refreshMemory = () => this.notify();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.notify();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* A shared memory's capacity made at least `capacity`: it grows in place,
|
|
325
|
+
* which every worker sees, and never shrinks, as another worker may have
|
|
326
|
+
* grown it past what this one asks for since. Nor is what a smaller
|
|
327
|
+
* buffer would drop zeroed: without avmplus' safepoint, another worker
|
|
328
|
+
* may have grown the length again and written there meanwhile, so bytes
|
|
329
|
+
* past a lowered length may come back when it grows again.
|
|
330
|
+
*/
|
|
331
|
+
private reshare(capacity: number): void {
|
|
332
|
+
const data = (this.shared as SharedBytesMemory).data as unknown as GrowableShared;
|
|
333
|
+
if (capacity <= data.byteLength) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (capacity > data.maxByteLength) {
|
|
338
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
data.grow(capacity);
|
|
343
|
+
} catch {
|
|
344
|
+
// Another worker grew it meanwhile, to as much or more, or the host refused.
|
|
345
|
+
if (data.byteLength < capacity) {
|
|
346
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const { runtime, own } = this.capacity;
|
|
351
|
+
runtime.bytes += data.byteLength - own.bytes;
|
|
352
|
+
own.bytes = data.byteLength;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** As ByteArray::CAS, once the index is checked: atomic where the memory is shared. */
|
|
356
|
+
compareAndSwapInt(at: number, expected: number, next: number): number {
|
|
357
|
+
if (this.sharedInts !== null) {
|
|
358
|
+
return Atomics.compareExchange(this.sharedInts, at >> 2, expected, next);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const previous = this.view.getInt32(at, true);
|
|
362
|
+
if (previous === expected) {
|
|
363
|
+
this.view.setInt32(at, next, true);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return previous;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* As UnprotectedAtomicCompareAndSwapLength: the length set to `next`, as
|
|
371
|
+
* its setter sets it, if it is `expected`; the length before. Shared,
|
|
372
|
+
* the capacity is made first and the length swapped atomically, so that
|
|
373
|
+
* of workers growing it at once one wins.
|
|
374
|
+
*/
|
|
375
|
+
compareAndSwapLength(expected: number, next: number): number {
|
|
376
|
+
if (this.shared === null) {
|
|
377
|
+
const previous = this.length;
|
|
378
|
+
if (previous === expected) {
|
|
379
|
+
this.setLength(next, true);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return previous;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Read once: another worker may change it between two reads.
|
|
386
|
+
const now = this.length;
|
|
387
|
+
if (now !== expected) {
|
|
388
|
+
return now;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Only grows the memory, so it does nothing another worker would undo if the swap fails.
|
|
392
|
+
this.reserve(next, true);
|
|
393
|
+
const previous =
|
|
394
|
+
Atomics.compareExchange(this.sharedWords as Int32Array, 0, expected | 0, next | 0) >>> 0;
|
|
395
|
+
if (previous === expected && this.position > next) {
|
|
396
|
+
this.position = next;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.notify();
|
|
400
|
+
return previous;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** As requestBytesForShortRead: the offset of `n` bytes to read, or EOFError. */
|
|
404
|
+
shortRead(n: number): number {
|
|
405
|
+
const at = this.position;
|
|
406
|
+
if (at >= this.length || at + n > this.length) {
|
|
407
|
+
throw this.rt.error("flash.errors::EOFError", 2030);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.position = (at + n) >>> 0;
|
|
411
|
+
return at;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** As requestBytesForShortWrite: the offset of `n` bytes to write, the length grown to hold them. */
|
|
415
|
+
shortWrite(n: number): number {
|
|
416
|
+
const at = this.position;
|
|
417
|
+
if (at >= this.length || at + n > this.length) {
|
|
418
|
+
if (at + n > 0xffffffff) {
|
|
419
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
this.extend(at + n);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.position = (at + n) >>> 0;
|
|
426
|
+
return at;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** As DataInput::CheckEOF. */
|
|
430
|
+
checkEOF(count: number): void {
|
|
431
|
+
if (this.available < count) {
|
|
432
|
+
throw this.rt.error("flash.errors::EOFError", 2030);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** As ByteArray::Read: `count` bytes from the position. */
|
|
437
|
+
read(count: number): Uint8Array {
|
|
438
|
+
this.checkEOF(count);
|
|
439
|
+
const bytes = this.buffer.slice(this.position, this.position + count);
|
|
440
|
+
this.position = (this.position + count) >>> 0;
|
|
441
|
+
return bytes;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** As read, but the bytes themselves, not a copy: for a caller that only reads them, at once. */
|
|
445
|
+
readView(count: number): Uint8Array {
|
|
446
|
+
this.checkEOF(count);
|
|
447
|
+
const bytes = this.buffer.subarray(this.position, this.position + count);
|
|
448
|
+
this.position = (this.position + count) >>> 0;
|
|
449
|
+
return bytes;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* As ByteArray::Write: `count` bytes of `source` from `offset`, all of
|
|
454
|
+
* it by default, at the position; the length grows to the position after.
|
|
455
|
+
*/
|
|
456
|
+
write(source: Uint8Array, offset = 0, count = source.length): void {
|
|
457
|
+
// Before the buffer is read: making room may replace it.
|
|
458
|
+
const at = this.claim(count);
|
|
459
|
+
copyBytes(this.buffer, at, source, offset, count);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* As ByteArray::Write but for the bytes, which the caller writes itself:
|
|
464
|
+
* room for `count` at the position, which moves past them, the length
|
|
465
|
+
* growing to it. Where in the buffer they start.
|
|
466
|
+
*/
|
|
467
|
+
claim(count: number): number {
|
|
468
|
+
if (count > 0xffffffff - this.position) {
|
|
469
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const at = this.position;
|
|
473
|
+
const end = (at + count) >>> 0;
|
|
474
|
+
if (end > this.buffer.length) {
|
|
475
|
+
this.ensure(end, false);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.position = end;
|
|
479
|
+
if (this.length < end) {
|
|
480
|
+
if (this.shared === null) {
|
|
481
|
+
this.length = end;
|
|
482
|
+
} else {
|
|
483
|
+
this.raise(end);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
this.notify();
|
|
488
|
+
return at;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** As ByteArray's operator[] for a write: the length grows to hold `index`. */
|
|
492
|
+
setByte(index: number, value: number): void {
|
|
493
|
+
if (index >= this.length) {
|
|
494
|
+
if (index === 0xffffffff) {
|
|
495
|
+
throw this.rt.error("flash.errors::MemoryError", 1000);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
this.extend(index + 1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
this.buffer[index] = value;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Below this many bytes, copyBytes copies one at a time. */
|
|
506
|
+
const COPY_LOOP_LENGTH = 32;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* `count` bytes of `source` from `offset` copied into `target` at `at`,
|
|
510
|
+
* as memmove copies them where the two are one array. Part of `source`,
|
|
511
|
+
* fewer than COPY_LOOP_LENGTH bytes, is copied one at a time: the
|
|
512
|
+
* subarray set() would take costs more than they do, in JavaScriptCore up
|
|
513
|
+
* to about 64 bytes and in V8 up to about 32. All of it set() copies
|
|
514
|
+
* faster than a loop in V8, and as fast in JavaScriptCore.
|
|
515
|
+
*/
|
|
516
|
+
export function copyBytes(
|
|
517
|
+
target: Uint8Array,
|
|
518
|
+
at: number,
|
|
519
|
+
source: Uint8Array,
|
|
520
|
+
offset: number,
|
|
521
|
+
count: number,
|
|
522
|
+
): void {
|
|
523
|
+
if (source === target) {
|
|
524
|
+
target.copyWithin(at, offset, offset + count);
|
|
525
|
+
} else if (offset === 0 && count === source.length) {
|
|
526
|
+
target.set(source, at);
|
|
527
|
+
} else if (count < COPY_LOOP_LENGTH) {
|
|
528
|
+
for (let i = 0; i < count; i++) {
|
|
529
|
+
target[at + i] = source[offset + i];
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
target.set(source.subarray(offset, offset + count), at);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** A ByteArray's state, made the first time a native needs it. */
|
|
537
|
+
export function bytesOf(rt: Runtime, o: AsObject): Bytes {
|
|
538
|
+
if (!o.$bytes) {
|
|
539
|
+
o.$bytes = new Bytes(rt, o);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return o.$bytes;
|
|
543
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AsObject, Value } from "../descriptors.js";
|
|
9
9
|
import type { Runtime } from "../runtime.js";
|
|
10
|
-
import { bytesOf } from "./
|
|
10
|
+
import { bytesOf } from "./bytes.js";
|
|
11
11
|
import { type Natives, plain, registerNativeClass } from "./define.js";
|
|
12
12
|
import { newSyncMemory, Sync, SyncFull, SyncTimeout } from "./sync.js";
|
|
13
13
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// A shareable ByteArray's memory between workers, as avmplus' ByteArrays
|
|
2
|
+
// share one Buffer: whether this thread can share memory, and each VM's
|
|
3
|
+
// one ByteArray for each shared memory.
|
|
4
|
+
//
|
|
5
|
+
// Translated from avmplus' core/ByteArrayGlue.cpp, this file is subject to
|
|
6
|
+
// the Mozilla Public License, v. 2.0: http://mozilla.org/MPL/2.0/.
|
|
7
|
+
import type { AsObject } from "../descriptors.js";
|
|
8
|
+
import type { Runtime } from "../runtime.js";
|
|
9
|
+
import { bytesOf } from "./bytes.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A shareable ByteArray's memory once it has crossed to another worker,
|
|
13
|
+
* which every worker's ByteArray for it uses, as avmplus' ByteArrays share
|
|
14
|
+
* one Buffer: its bytes grow in place, and its length is one for all.
|
|
15
|
+
* Plain data, which a structured clone carries to another thread.
|
|
16
|
+
*/
|
|
17
|
+
export interface SharedBytesMemory {
|
|
18
|
+
/** The same in every worker, by which each VM keeps one ByteArray for it, as avmplus interns it. */
|
|
19
|
+
id: number;
|
|
20
|
+
/** Its words: the length, as a uint32. */
|
|
21
|
+
meta: SharedArrayBuffer;
|
|
22
|
+
/** Its bytes, as long as its capacity: growable, up to the most a ByteArray holds where it can. */
|
|
23
|
+
data: SharedArrayBuffer;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The growable SharedArrayBuffer of ES2024, which the runtime's ES2022 library does not type. */
|
|
27
|
+
export interface GrowableShared {
|
|
28
|
+
readonly byteLength: number;
|
|
29
|
+
readonly maxByteLength: number;
|
|
30
|
+
grow(length: number): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type GrowableSharedConstructor = new (
|
|
34
|
+
length: number,
|
|
35
|
+
options: { maxByteLength: number },
|
|
36
|
+
) => SharedArrayBuffer;
|
|
37
|
+
|
|
38
|
+
/** The ways a browser serves a page cross-origin isolated, for the error a page that is not gets. */
|
|
39
|
+
const ISOLATION =
|
|
40
|
+
"a cross-origin isolated page (served with Cross-Origin-Opener-Policy: same-origin and " +
|
|
41
|
+
"Cross-Origin-Embedder-Policy: credentialless or require-corp)";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether this thread can share memory with others: SharedArrayBuffer is
|
|
45
|
+
* there, as node always has it, and a browser only for a page that is
|
|
46
|
+
* cross-origin isolated.
|
|
47
|
+
*/
|
|
48
|
+
export function canShareMemory(): boolean {
|
|
49
|
+
const g = globalThis as { crossOriginIsolated?: boolean };
|
|
50
|
+
return typeof SharedArrayBuffer === "function" && g.crossOriginIsolated !== false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What a page that is not cross-origin isolated gets for sharing `what` with another worker. */
|
|
54
|
+
export function notIsolated(what: string): Error {
|
|
55
|
+
return new Error(
|
|
56
|
+
`swf2es: sharing ${what} with another worker needs shared memory, which a browser gives only ${ISOLATION}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Each runtime's ByteArray for each shared memory, by its id. */
|
|
61
|
+
const sharedObjects = new WeakMap<Runtime, Map<number, WeakRef<AsObject>>>();
|
|
62
|
+
|
|
63
|
+
export function sharedObjectsOf(rt: Runtime): Map<number, WeakRef<AsObject>> {
|
|
64
|
+
let objects = sharedObjects.get(rt);
|
|
65
|
+
if (!objects) {
|
|
66
|
+
objects = new Map();
|
|
67
|
+
sharedObjects.set(rt, objects);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return objects;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A shareable ByteArray's memory, shared from now on, as it crosses to
|
|
75
|
+
* another worker; where memory cannot be shared, the error that names
|
|
76
|
+
* cross-origin isolation.
|
|
77
|
+
*/
|
|
78
|
+
export function shareBytes(rt: Runtime, o: AsObject): SharedBytesMemory {
|
|
79
|
+
return bytesOf(rt, o).share();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** This VM's one ByteArray for a shared memory, as avmplus interns one for its Buffer: shareable, at position 0. */
|
|
83
|
+
export function sharedByteArray(rt: Runtime, memory: SharedBytesMemory): AsObject {
|
|
84
|
+
const objects = sharedObjectsOf(rt);
|
|
85
|
+
const known = objects.get(memory.id)?.deref();
|
|
86
|
+
if (known) {
|
|
87
|
+
return known;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const o = rt.construct(rt.classNamed("flash.utils::ByteArray")) as AsObject;
|
|
91
|
+
bytesOf(rt, o).adopt(memory);
|
|
92
|
+
objects.set(memory.id, new WeakRef(o));
|
|
93
|
+
return o;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** This VM's ByteArray for the shared memory `id`, if it has one. */
|
|
97
|
+
export function knownSharedByteArray(rt: Runtime, id: number): AsObject | undefined {
|
|
98
|
+
return sharedObjectsOf(rt).get(id)?.deref();
|
|
99
|
+
}
|
|
@@ -10,16 +10,11 @@ import type { ShellThread } from "../options.js";
|
|
|
10
10
|
import type { Runtime } from "../runtime.js";
|
|
11
11
|
import { setStaticVar } from "../traits.js";
|
|
12
12
|
import { fromUtf8, utf8 } from "../utf8.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
bytesOf,
|
|
16
|
-
GLOBAL_MEMORY_MIN_SIZE,
|
|
17
|
-
knownSharedByteArray,
|
|
18
|
-
setDomainMemory,
|
|
19
|
-
sharedByteArray,
|
|
20
|
-
} from "./bytearray.js";
|
|
13
|
+
import { setDomainMemory } from "./bytearray.js";
|
|
14
|
+
import { byteArrayCapacity, bytesOf, GLOBAL_MEMORY_MIN_SIZE } from "./bytes.js";
|
|
21
15
|
import { syncObject, syncOf, syncRef } from "./concurrent.js";
|
|
22
16
|
import { elements, type Natives, registerNativeClass } from "./define.js";
|
|
17
|
+
import { knownSharedByteArray, sharedByteArray } from "./shared-bytes.js";
|
|
23
18
|
import {
|
|
24
19
|
CANSTART,
|
|
25
20
|
entryKey,
|
package/src/avm2/options.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// What a host gives a runtime when it makes one, and what it does without.
|
|
2
2
|
|
|
3
3
|
import type { Abc, CompileUnit } from "./descriptors.js";
|
|
4
|
-
import type { SharedBytesMemory } from "./natives/
|
|
4
|
+
import type { SharedBytesMemory } from "./natives/shared-bytes.js";
|
|
5
5
|
import type { SyncInit, SyncMemory } from "./natives/sync.js";
|
|
6
6
|
import type { Runtime } from "./runtime.js";
|
|
7
7
|
|