@earendil-works/djs 0.0.0-stage → 0.1.0

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/reader.js ADDED
@@ -0,0 +1,599 @@
1
+ // The outbound direct reader: decodes VM values straight out of the heap arena
2
+ // (VM -> host, no serialization in the VM). Everything it knows about the heap comes from
3
+ // layout.generated.ts, which djs-core exports from its layout table (D1052).
4
+ // Format notes: docs/wire.md, "Outbound: direct heap reads".
5
+ import { LAYOUT } from "./layout.generated.js";
6
+ const cells = LAYOUT.cells;
7
+ const tags = LAYOUT.value.tags;
8
+ // Offsets and constants, looked up once from the layout description.
9
+ const ROOTS_MAGIC = cells.Roots.fields.MAGIC.offset;
10
+ const ROOTS_VERSION = cells.Roots.fields.VERSION.offset;
11
+ const ROOTS_ATOMS = cells.Roots.fields.ATOMS.offset;
12
+ const HDR_KIND = LAYOUT.header.KIND;
13
+ const HDR_FLAGS = LAYOUT.header.FLAGS;
14
+ const K_STR8 = cells.Str8.kind;
15
+ const K_STR16 = cells.Str16.kind;
16
+ const K_ROPE = cells.Rope.kind;
17
+ const K_SLICE = cells.Slice.kind;
18
+ const SLICE_LEN = cells.Slice.fields.LEN.offset;
19
+ const SLICE_PARENT = cells.Slice.fields.PARENT.offset;
20
+ const SLICE_OFF = cells.Slice.fields.OFF.offset;
21
+ const STR8_LEN = cells.Str8.fields.LEN.offset;
22
+ const STR8_DATA = cells.Str8.tail.offset;
23
+ const STR16_LEN = cells.Str16.fields.LEN.offset;
24
+ const STR16_DATA = cells.Str16.tail.offset;
25
+ const ROPE_LEFT = cells.Rope.fields.LEFT.offset;
26
+ const ROPE_RIGHT = cells.Rope.fields.RIGHT.offset;
27
+ const ROPE_FLATTENED = LAYOUT.flags.ROPE_FLATTENED;
28
+ const ROPE_APPEND = LAYOUT.flags.ROPE_APPEND;
29
+ const ROPE_LEN = cells.Rope.fields.LEN.offset;
30
+ const SYM_DESCRIPTION = cells.Symbol.fields.DESCRIPTION.offset;
31
+ const BIG_LEN = cells.BigInt.fields.LEN.offset;
32
+ const BIG_LIMBS = cells.BigInt.tail.offset;
33
+ const BIGINT_NEG = LAYOUT.flags.BIGINT_NEG;
34
+ const OBJ_CLASS = cells.Object.fields.CLASS.offset;
35
+ const OBJ_PROPS = cells.Object.fields.PROPS.offset;
36
+ const OBJ_ELEMENTS = cells.Object.fields.ELEMENTS.offset;
37
+ const OBJ_SLOTS = cells.Object.tail.offset;
38
+ const OBJ_INDEXED_PROPS = LAYOUT.flags.OBJ_INDEXED_PROPS;
39
+ const TAB_COUNT = cells.PropTable.fields.COUNT.offset;
40
+ const TAB_ENTRIES = cells.PropTable.tail.offset;
41
+ const ENTRY_SIZE = cells.PropTable.tail.elemSize;
42
+ const ENTRY_KEY = cells.PropTable.tail.fields.KEY.offset;
43
+ const ENTRY_FLAGS = cells.PropTable.tail.fields.FLAGS.offset;
44
+ const ENTRY_VALUE = cells.PropTable.tail.fields.VALUE.offset;
45
+ const OBJ_DICTIONARY = LAYOUT.flags.OBJ_DICTIONARY;
46
+ const PV_SHAPE = cells.PropValues.fields.SHAPE.offset;
47
+ const PV_LEN = cells.PropValues.fields.LEN.offset;
48
+ const PV_VALUES = cells.PropValues.tail.offset;
49
+ const SHAPE_DESCS = cells.Shape.fields.DESCS.offset;
50
+ const U32_DATA = cells.U32Array.tail.offset;
51
+ const VA_LEN = cells.ValueArray.fields.LEN.offset;
52
+ const VA_VALUES = cells.ValueArray.tail.offset;
53
+ const ATOMS_ENTRIES = cells.AtomTable.fields.ENTRIES.offset;
54
+ // Two levels in a VM from the base image (D2431): the base's atoms, then the VM's own.
55
+ const ATOMS_BASE_ENTRIES = cells.AtomTable.fields.BASE_ENTRIES.offset;
56
+ const ATOMS_BASE_COUNT = cells.AtomTable.fields.BASE_COUNT.offset;
57
+ const P_ENUMERABLE = LAYOUT.propFlags.ENUMERABLE;
58
+ const P_ACCESSOR = LAYOUT.propFlags.ACCESSOR;
59
+ const A_INT = LAYOUT.atom.intBit;
60
+ const A_SYMBOL = LAYOUT.atom.symbolBit;
61
+ const A_INDEX = LAYOUT.atom.indexMask;
62
+ const C_ORDINARY = LAYOUT.objectClasses.Ordinary;
63
+ const C_ARRAY = LAYOUT.objectClasses.Array;
64
+ const C_ARRAY_BUFFER = LAYOUT.objectClasses.ArrayBuffer;
65
+ const C_TYPED_ARRAY = LAYOUT.objectClasses.TypedArray;
66
+ const BYTES_LEN = cells.Bytes.fields.LEN.offset;
67
+ const BYTES_DATA = cells.Bytes.tail.offset;
68
+ const F16 = globalThis.Float16Array ?? Uint16Array;
69
+ const TA_CTORS = [
70
+ [Int8Array, 1], [Uint8Array, 1], [Uint8ClampedArray, 1], [Int16Array, 2], [Uint16Array, 2],
71
+ [Int32Array, 4], [Uint32Array, 4], [F16, 2], [Float32Array, 4], [Float64Array, 8],
72
+ [BigInt64Array, 8], [BigUint64Array, 8],
73
+ ];
74
+ const TAG_PREFIX = LAYOUT.value.tagPrefix;
75
+ const HI_HOLE = ((TAG_PREFIX << 16) | tags.HOLE) >>> 0;
76
+ const HI_INT = ((TAG_PREFIX << 16) | tags.INT) >>> 0;
77
+ const HI_UNDEFINED = ((TAG_PREFIX << 16) | tags.UNDEFINED) >>> 0;
78
+ const tagNames = [];
79
+ for (const [name, t] of Object.entries(tags))
80
+ tagNames[t] = name;
81
+ const classNames = [];
82
+ for (const [name, c] of Object.entries(LAYOUT.objectClasses))
83
+ classNames[c] = name;
84
+ /** Strings up to this length are built character by character. */
85
+ const TINY = 4;
86
+ /** Without Buffer, strings shorter than this use String.fromCharCode, not a TextDecoder. */
87
+ const SHORT = 48;
88
+ /** String.fromCharCode.apply argument chunk. */
89
+ const CHUNK = 0x2000;
90
+ const NodeBuffer = globalThis.Buffer;
91
+ const hasNodeBuffer = NodeBuffer !== undefined &&
92
+ typeof NodeBuffer.from(new ArrayBuffer(2), 0, 2).latin1Slice === "function";
93
+ const utf16Decoder = new TextDecoder("utf-16le", { fatal: true, ignoreBOM: true });
94
+ // "latin1" is windows-1252 in the Encoding standard. It agrees with Latin-1 everywhere but
95
+ // 0x80..0x9F, so it is only used for data without bytes in that range.
96
+ const win1252Decoder = new TextDecoder("windows-1252");
97
+ /**
98
+ * A VM value with no host equivalent: a symbol, a function or other non-plain object, a
99
+ * host ref or an internal value (BigInts are read as native bigints). `ref` is the arena offset of its cell (0 if none).
100
+ */
101
+ export class Opaque {
102
+ type;
103
+ ref;
104
+ /** The object class (`Function`, `Error`, ...) for objects. */
105
+ className;
106
+ /** A symbol's description. */
107
+ description;
108
+ constructor(type, ref, className, description) {
109
+ this.type = type;
110
+ this.ref = ref;
111
+ this.className = className;
112
+ this.description = description;
113
+ }
114
+ }
115
+ /** A value that cannot be read as a tree: `path` says where (`$.a[3]`). */
116
+ class ReadError extends TypeError {
117
+ segments = [];
118
+ reason;
119
+ constructor(reason) {
120
+ super(reason);
121
+ this.reason = reason;
122
+ }
123
+ }
124
+ function formatPath(segments) {
125
+ let p = "$";
126
+ for (const s of segments) {
127
+ if (typeof s === "number")
128
+ p += `[${s}]`;
129
+ else if (/^[A-Za-z_$][\w$]*$/.test(s))
130
+ p += `.${s}`;
131
+ else
132
+ p += `[${JSON.stringify(s)}]`;
133
+ }
134
+ return p;
135
+ }
136
+ /** Cycles are looked for once nesting is this deep (a cycle eventually gets there). */
137
+ const CYCLE_CHECK_DEPTH = 32;
138
+ /** Thrown when the memory does not hold a heap of this build's layout. */
139
+ export class LayoutMismatch extends Error {
140
+ }
141
+ /**
142
+ * Checks that `memory` holds a djs heap with the layout this reader was generated for
143
+ * (the magic and `LAYOUT_HASH` in the roots cell).
144
+ */
145
+ export function checkHeap(memory) {
146
+ if (memory.length < cells.Roots.minSize)
147
+ throw new LayoutMismatch("not a djs heap: too small");
148
+ const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
149
+ if (view.getUint32(ROOTS_MAGIC, true) !== LAYOUT.magic) {
150
+ throw new LayoutMismatch("not a djs heap: bad magic");
151
+ }
152
+ const version = view.getUint32(ROOTS_VERSION, true);
153
+ if (version !== LAYOUT.layoutHash) {
154
+ throw new LayoutMismatch(`djs heap layout ${version.toString(16)} does not match the reader's ${LAYOUT.layoutHash.toString(16)}`);
155
+ }
156
+ }
157
+ /**
158
+ * Decodes the VM value `value` (its 64 bits, or `[lo, hi]` as two u32) from the heap arena
159
+ * `memory` (offset 0 of the view is the arena start, i.e. the roots cell).
160
+ *
161
+ * Primitives map to themselves; strings (flat or ropes) to strings; arrays to arrays (holes
162
+ * stay holes); ordinary objects to plain objects with their own enumerable string-keyed data
163
+ * properties in property order. Accessors, non-enumerable and symbol-keyed properties are
164
+ * left out. Everything else becomes an {@link Opaque}. The result is a tree, as with JSON:
165
+ * a shared object is read once per reference, and a cycle is a TypeError naming its path.
166
+ */
167
+ export function readValue(memory, value, opts = {}) {
168
+ checkHeap(memory);
169
+ let lo, hi;
170
+ if (typeof value === "bigint") {
171
+ lo = Number(value & 0xffffffffn);
172
+ hi = Number((value >> 32n) & 0xffffffffn);
173
+ }
174
+ else {
175
+ [lo, hi] = value;
176
+ }
177
+ const budget = opts.maxValues ?? 4 * (memory.length >>> 3) + 1_000_000;
178
+ const r = new Reader(memory, opts.atomCache ?? [], opts.portable === true, budget);
179
+ if (hi >>> 16 !== TAG_PREFIX) {
180
+ const scratch = new DataView(new ArrayBuffer(8));
181
+ scratch.setUint32(0, lo, true);
182
+ scratch.setUint32(4, hi, true);
183
+ return scratch.getFloat64(0, true);
184
+ }
185
+ try {
186
+ return r.tagged(hi & 0xffff, lo >>> 0);
187
+ }
188
+ catch (e) {
189
+ if (e instanceof ReadError)
190
+ throw new TypeError(`${e.reason} at ${formatPath(e.segments)}`);
191
+ throw e;
192
+ }
193
+ }
194
+ class Reader {
195
+ mem;
196
+ view;
197
+ u16;
198
+ /** u32 and f64 views over the arena when its base is 8-aligned (for element loops). */
199
+ w32;
200
+ f64;
201
+ /** A Buffer over the arena, when running on node. */
202
+ buf;
203
+ atoms;
204
+ atomEntries;
205
+ atomBaseEntries;
206
+ atomBaseCount;
207
+ /** Offsets of the containers being read (cycle detection). */
208
+ stack = [];
209
+ depth = 0;
210
+ /** Values left in the budget. */
211
+ budget;
212
+ constructor(mem, atoms, portable, budget) {
213
+ this.budget = budget;
214
+ this.mem = mem;
215
+ this.view = new DataView(mem.buffer, mem.byteOffset, mem.byteLength);
216
+ // Two-byte string data is 2-aligned within the arena; a view needs the base aligned too.
217
+ this.u16 = mem.byteOffset % 2 === 0 ? new Uint16Array(mem.buffer, mem.byteOffset, mem.length >> 1) : null;
218
+ const aligned = mem.byteOffset % 8 === 0;
219
+ this.w32 = aligned ? new Uint32Array(mem.buffer, mem.byteOffset, mem.length >>> 2) : null;
220
+ this.f64 = aligned ? new Float64Array(mem.buffer, mem.byteOffset, mem.length >>> 3) : null;
221
+ this.buf = hasNodeBuffer && !portable ? NodeBuffer.from(mem.buffer, mem.byteOffset, mem.byteLength) : null;
222
+ this.atoms = atoms;
223
+ const table = this.view.getUint32(ROOTS_ATOMS, true);
224
+ this.atomEntries = table === 0 ? 0 : this.view.getUint32(table + ATOMS_ENTRIES, true);
225
+ this.atomBaseEntries = table === 0 ? 0 : this.view.getUint32(table + ATOMS_BASE_ENTRIES, true);
226
+ this.atomBaseCount = table === 0 ? 0 : this.view.getUint32(table + ATOMS_BASE_COUNT, true);
227
+ }
228
+ /** The value stored at arena offset `off`. */
229
+ slot(off) {
230
+ const hi = this.view.getUint32(off + 4, true);
231
+ if (hi >>> 16 !== TAG_PREFIX)
232
+ return this.view.getFloat64(off, true);
233
+ return this.tagged(hi & 0xffff, this.view.getUint32(off, true));
234
+ }
235
+ tagged(tag, payload) {
236
+ switch (tag) {
237
+ case tags.UNDEFINED:
238
+ case tags.HOLE:
239
+ return undefined;
240
+ case tags.NULL:
241
+ return null;
242
+ case tags.BOOL:
243
+ return payload !== 0;
244
+ case tags.INT:
245
+ return payload | 0;
246
+ case tags.STRING:
247
+ return this.string(payload);
248
+ case tags.OBJECT:
249
+ return this.object(payload);
250
+ case tags.BIGINT:
251
+ return this.bigint(payload);
252
+ case tags.SYMBOL: {
253
+ const d = this.slotIfString(payload + SYM_DESCRIPTION);
254
+ return new Opaque("symbol", payload, undefined, d);
255
+ }
256
+ default: {
257
+ const name = tagNames[tag];
258
+ if (name === undefined || tag < LAYOUT.value.firstRef) {
259
+ throw new Error(`djs heap: unexpected value tag ${tag}`);
260
+ }
261
+ return new Opaque(name.toLowerCase(), payload);
262
+ }
263
+ }
264
+ }
265
+ /** A BigInt cell as a native bigint (0046: u32 limbs, little-endian, sign flag). */
266
+ bigint(off) {
267
+ const len = this.view.getUint32(off + BIG_LEN, true);
268
+ let v = 0n;
269
+ for (let i = len - 1; i >= 0; i--) {
270
+ v = (v << 32n) | BigInt(this.view.getUint32(off + BIG_LIMBS + 4 * i, true));
271
+ }
272
+ return this.view.getUint16(off + HDR_FLAGS, true) & BIGINT_NEG ? -v : v;
273
+ }
274
+ slotIfString(off) {
275
+ const hi = this.view.getUint32(off + 4, true);
276
+ if (hi !== ((TAG_PREFIX << 16) | tags.STRING) >>> 0)
277
+ return undefined;
278
+ return this.string(this.view.getUint32(off, true));
279
+ }
280
+ string(off) {
281
+ const kind = this.mem[off + HDR_KIND];
282
+ if (kind === K_STR8)
283
+ return this.latin1(off + STR8_DATA, this.view.getUint32(off + STR8_LEN, true));
284
+ if (kind === K_STR16)
285
+ return this.utf16(off + STR16_DATA, this.view.getUint32(off + STR16_LEN, true));
286
+ if (kind === K_ROPE) {
287
+ if (this.view.getUint16(off + HDR_FLAGS, true) & ROPE_APPEND)
288
+ return this.appendRope(off);
289
+ return this.rope(off);
290
+ }
291
+ if (kind === K_SLICE) {
292
+ // Units [OFF, OFF + LEN) of a flat one-byte string (D2542).
293
+ const parent = this.view.getUint32(off + SLICE_PARENT, true);
294
+ const at = parent + STR8_DATA + this.view.getUint32(off + SLICE_OFF, true);
295
+ return this.latin1(at, this.view.getUint32(off + SLICE_LEN, true));
296
+ }
297
+ throw new Error(`djs heap: cell ${off} is not a string`);
298
+ }
299
+ /** Walks a rope's leaves left to right (iteratively; ropes can be deep). */
300
+ rope(off) {
301
+ const parts = [];
302
+ const stack = [off];
303
+ while (stack.length > 0) {
304
+ const c = stack.pop();
305
+ if (this.mem[c + HDR_KIND] !== K_ROPE) {
306
+ parts.push(this.string(c));
307
+ }
308
+ else if (this.view.getUint16(c + HDR_FLAGS, true) & ROPE_APPEND) {
309
+ parts.push(this.appendRope(c));
310
+ }
311
+ else if (this.view.getUint16(c + HDR_FLAGS, true) & ROPE_FLATTENED) {
312
+ stack.push(this.view.getUint32(c + ROPE_LEFT, true));
313
+ }
314
+ else {
315
+ stack.push(this.view.getUint32(c + ROPE_RIGHT, true), this.view.getUint32(c + ROPE_LEFT, true));
316
+ }
317
+ }
318
+ return parts.join("");
319
+ }
320
+ /** An append rope (D1610): the first LEN code units of its buffer (LEFT). */
321
+ appendRope(off) {
322
+ const buf = this.view.getUint32(off + ROPE_LEFT, true);
323
+ const n = this.view.getUint32(off + ROPE_LEN, true);
324
+ if (this.mem[buf + HDR_KIND] === K_STR8)
325
+ return this.latin1(buf + STR8_DATA, n);
326
+ return this.utf16(buf + STR16_DATA, n);
327
+ }
328
+ latin1(at, n) {
329
+ const mem = this.mem;
330
+ if (n <= TINY) {
331
+ let s = "";
332
+ for (let i = 0; i < n; i++)
333
+ s += String.fromCharCode(mem[at + i]);
334
+ return s;
335
+ }
336
+ if (this.buf !== null)
337
+ return this.buf.latin1Slice(at, at + n);
338
+ if (n < SHORT)
339
+ return fromCodes(mem, at, n);
340
+ const bytes = mem.subarray(at, at + n);
341
+ // Without bytes in 0x80..0x9F, windows-1252 is Latin-1.
342
+ let c1 = false;
343
+ for (let i = 0; i < n; i++) {
344
+ if ((bytes[i] & 0xe0) === 0x80) {
345
+ c1 = true;
346
+ break;
347
+ }
348
+ }
349
+ if (!c1)
350
+ return win1252Decoder.decode(bytes);
351
+ return chunked(bytes);
352
+ }
353
+ utf16(at, n) {
354
+ if (this.buf !== null && n > TINY)
355
+ return this.buf.ucs2Slice(at, at + 2 * n);
356
+ const u16 = this.u16;
357
+ if (u16 === null) {
358
+ const copy = new Uint16Array(n);
359
+ for (let i = 0; i < n; i++)
360
+ copy[i] = this.view.getUint16(at + 2 * i, true);
361
+ return chunked(copy);
362
+ }
363
+ if (n < SHORT)
364
+ return fromCodes(u16, at >> 1, n);
365
+ const units = u16.subarray(at >> 1, (at >> 1) + n);
366
+ try {
367
+ return utf16Decoder.decode(units);
368
+ }
369
+ catch {
370
+ // Lone surrogates: the fatal decoder refuses them, fromCharCode keeps them.
371
+ return chunked(units);
372
+ }
373
+ }
374
+ /** A property key, `undefined` for symbols. */
375
+ key(atom) {
376
+ if (atom & A_INT)
377
+ return (atom & ~A_INT) >>> 0;
378
+ if (atom & A_SYMBOL)
379
+ return undefined;
380
+ const index = atom & A_INDEX;
381
+ let s = this.atoms[index];
382
+ if (s === undefined) {
383
+ const n = this.atomBaseCount;
384
+ const slot = index < n
385
+ ? this.atomBaseEntries + VA_VALUES + 8 * index
386
+ : this.atomEntries + VA_VALUES + 8 * (index - n);
387
+ s = this.string(this.view.getUint32(slot, true));
388
+ this.atoms[index] = s;
389
+ }
390
+ return s;
391
+ }
392
+ object(off) {
393
+ const cls = this.view.getUint32(off + OBJ_CLASS, true);
394
+ if (cls === C_TYPED_ARRAY)
395
+ return this.typedArray(off);
396
+ if (cls === C_ARRAY_BUFFER)
397
+ return this.arrayBuffer(off);
398
+ if (cls !== C_ARRAY && cls !== C_ORDINARY) {
399
+ return new Opaque("object", off, classNames[cls] ?? `class ${cls}`);
400
+ }
401
+ // A cycle reaches any depth, so only deep nesting needs the (linear) check.
402
+ const depth = this.depth;
403
+ if (depth >= CYCLE_CHECK_DEPTH) {
404
+ const stack = this.stack;
405
+ for (let i = 0; i < depth; i++) {
406
+ if (stack[i] === off)
407
+ throw new ReadError("cyclic value");
408
+ }
409
+ }
410
+ this.stack[depth] = off;
411
+ this.depth = depth + 1;
412
+ const v = cls === C_ARRAY ? this.array(off) : this.plain(off);
413
+ this.depth = depth;
414
+ return v;
415
+ }
416
+ /**
417
+ * The bytes of an ArrayBuffer object: the arena offset of its Bytes cell and its
418
+ * length, or null once detached.
419
+ */
420
+ bufferBytes(buffer) {
421
+ const slot = buffer + OBJ_SLOTS;
422
+ if (this.view.getUint32(slot + 4, true) === HI_UNDEFINED)
423
+ return null;
424
+ const cell = this.view.getUint32(slot, true);
425
+ return [cell + BYTES_DATA, this.view.getUint32(cell + BYTES_LEN, true)];
426
+ }
427
+ /** An ArrayBuffer: a copy of its bytes (docs/wire.md, "Typed arrays"). */
428
+ arrayBuffer(off) {
429
+ const bytes = this.bufferBytes(off);
430
+ if (bytes === null)
431
+ return new ArrayBuffer(0);
432
+ this.spend(1);
433
+ return this.mem.slice(bytes[0], bytes[0] + bytes[1]).buffer;
434
+ }
435
+ /**
436
+ * A typed array: a copy of its elements as the host's typed array of the same kind
437
+ * (detached or out of bounds: empty). Copies, not views: the arena moves with every
438
+ * collection and may be replaced when wasm memory grows.
439
+ */
440
+ typedArray(off) {
441
+ const slots = off + OBJ_SLOTS;
442
+ const kind = this.slot(slots + 8);
443
+ const [Ctor, size] = TA_CTORS[kind] ?? TA_CTORS[1];
444
+ const byteOffset = this.slot(slots + 16);
445
+ const length = this.slot(slots + 24);
446
+ const bytes = this.bufferBytes(this.view.getUint32(slots, true));
447
+ let n = 0;
448
+ if (bytes !== null && byteOffset <= bytes[1]) {
449
+ n = length >= 0 ? length : Math.floor((bytes[1] - byteOffset) / size);
450
+ if (byteOffset + n * size > bytes[1])
451
+ n = 0;
452
+ }
453
+ this.spend(1);
454
+ const start = bytes === null ? 0 : bytes[0] + byteOffset;
455
+ return new Ctor(this.mem.slice(start, start + n * size).buffer);
456
+ }
457
+ spend(n) {
458
+ this.budget -= n;
459
+ if (this.budget < 0)
460
+ throw new RangeError("djs heap read: value too large (maxValues)");
461
+ }
462
+ plain(off) {
463
+ const o = {};
464
+ let key = undefined;
465
+ try {
466
+ const view = this.view;
467
+ const el = view.getUint32(off + OBJ_ELEMENTS, true);
468
+ if (el !== 0) {
469
+ const n = view.getUint32(el + VA_LEN, true);
470
+ this.spend(n);
471
+ for (let i = 0; i < n; i++) {
472
+ const at = el + VA_VALUES + 8 * i;
473
+ key = i;
474
+ if (view.getUint32(at + 4, true) !== HI_HOLE)
475
+ o[i] = this.slot(at);
476
+ }
477
+ }
478
+ const t = view.getUint32(off + OBJ_PROPS, true);
479
+ if (t === 0)
480
+ return o;
481
+ // Dictionary mode: a table of (key, flags, value) entries. Shape mode: a values
482
+ // block whose shape's descriptors hold (key, flags) pairs (docs/objects.md 7).
483
+ const dict = (view.getUint16(off + HDR_FLAGS, true) & OBJ_DICTIONARY) !== 0;
484
+ const count = view.getUint32(t + (dict ? TAB_COUNT : PV_LEN), true);
485
+ const descs = dict ? 0 : view.getUint32(view.getUint32(t + PV_SHAPE, true) + SHAPE_DESCS, true) + U32_DATA + 4;
486
+ this.spend(count + 1);
487
+ for (let i = 0; i < count; i++) {
488
+ const e = dict ? t + TAB_ENTRIES + ENTRY_SIZE * i : descs + 8 * i;
489
+ const atom = view.getUint32(e + (dict ? ENTRY_KEY : 0), true);
490
+ if (atom === 0)
491
+ continue; // deleted
492
+ const flags = view.getUint32(e + (dict ? ENTRY_FLAGS : 4), true);
493
+ if (flags & P_ACCESSOR || !(flags & P_ENUMERABLE))
494
+ continue;
495
+ key = this.key(atom);
496
+ if (key === undefined)
497
+ continue;
498
+ const v = this.slot(dict ? e + ENTRY_VALUE : t + PV_VALUES + 8 * i);
499
+ // Assigning "__proto__" would set the prototype instead of defining a property.
500
+ if (key === "__proto__") {
501
+ Object.defineProperty(o, key, { value: v, writable: true, enumerable: true, configurable: true });
502
+ }
503
+ else {
504
+ o[key] = v;
505
+ }
506
+ }
507
+ }
508
+ catch (e) {
509
+ if (e instanceof ReadError && key !== undefined)
510
+ e.segments.unshift(key);
511
+ throw e;
512
+ }
513
+ return o;
514
+ }
515
+ array(off) {
516
+ const a = [];
517
+ let i = 0;
518
+ try {
519
+ const view = this.view;
520
+ const length = this.slot(off + OBJ_SLOTS);
521
+ const el = view.getUint32(off + OBJ_ELEMENTS, true);
522
+ if (el !== 0) {
523
+ const n = view.getUint32(el + VA_LEN, true);
524
+ this.spend(n + 1);
525
+ const w32 = this.w32;
526
+ const f64 = this.f64;
527
+ for (; i < n; i++) {
528
+ const at = el + VA_VALUES + 8 * i;
529
+ if (w32 !== null) {
530
+ // Numbers (the common element) straight from typed views.
531
+ const hi = w32[(at >> 2) + 1];
532
+ if (hi >>> 16 !== TAG_PREFIX) {
533
+ a.push(f64[at >> 3]);
534
+ continue;
535
+ }
536
+ if (hi === HI_INT) {
537
+ a.push(w32[at >> 2] | 0);
538
+ continue;
539
+ }
540
+ }
541
+ if (view.getUint32(at + 4, true) === HI_HOLE) {
542
+ a.length = i + 1; // keep the hole
543
+ }
544
+ else {
545
+ a.push(this.slot(at));
546
+ }
547
+ }
548
+ }
549
+ const hf = view.getUint16(off + HDR_FLAGS, true);
550
+ if (hf & OBJ_INDEXED_PROPS && hf & OBJ_DICTIONARY) {
551
+ // Elements beyond the dense part live in the property table (index keys always
552
+ // make an object a dictionary).
553
+ const t = view.getUint32(off + OBJ_PROPS, true);
554
+ const count = t === 0 ? 0 : view.getUint32(t + TAB_COUNT, true);
555
+ for (let k = 0; k < count; k++) {
556
+ const e = t + TAB_ENTRIES + ENTRY_SIZE * k;
557
+ const atom = view.getUint32(e + ENTRY_KEY, true);
558
+ const flags = view.getUint32(e + ENTRY_FLAGS, true);
559
+ if (atom === 0 || flags & P_ACCESSOR || !(flags & P_ENUMERABLE))
560
+ continue;
561
+ const key = this.key(atom);
562
+ if (typeof key === "number" || (typeof key === "string" && isArrayIndex(key))) {
563
+ i = Number(key);
564
+ this.spend(1);
565
+ a[i] = this.slot(e + ENTRY_VALUE);
566
+ }
567
+ }
568
+ }
569
+ if (a.length < length)
570
+ a.length = length;
571
+ }
572
+ catch (e) {
573
+ if (e instanceof ReadError)
574
+ e.segments.unshift(i);
575
+ throw e;
576
+ }
577
+ return a;
578
+ }
579
+ }
580
+ /** Array indexes that are string atoms (2^31 .. 2^32 - 2). */
581
+ function isArrayIndex(s) {
582
+ const n = Number(s);
583
+ return n >= 0 && n < 4294967295 && Number.isInteger(n) && String(n) === s;
584
+ }
585
+ const codes = [];
586
+ /** `n` code units from `at`, via a reused plain array (faster than applying a subarray). */
587
+ function fromCodes(units, at, n) {
588
+ codes.length = n;
589
+ for (let i = 0; i < n; i++)
590
+ codes[i] = units[at + i];
591
+ return String.fromCharCode.apply(null, codes);
592
+ }
593
+ function chunked(units) {
594
+ let s = "";
595
+ for (let i = 0; i < units.length; i += CHUNK) {
596
+ s += String.fromCharCode.apply(null, units.subarray(i, Math.min(i + CHUNK, units.length)));
597
+ }
598
+ return s;
599
+ }