@bradensbay/globals-core 0.2.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/README.md +69 -0
- package/dist/src/allocator.d.ts +78 -0
- package/dist/src/allocator.d.ts.map +1 -0
- package/dist/src/allocator.js +211 -0
- package/dist/src/allocator.js.map +1 -0
- package/dist/src/arena.d.ts +81 -0
- package/dist/src/arena.d.ts.map +1 -0
- package/dist/src/arena.js +224 -0
- package/dist/src/arena.js.map +1 -0
- package/dist/src/checksum.d.ts +5 -0
- package/dist/src/checksum.d.ts.map +1 -0
- package/dist/src/checksum.js +35 -0
- package/dist/src/checksum.js.map +1 -0
- package/dist/src/draft.d.ts +48 -0
- package/dist/src/draft.d.ts.map +1 -0
- package/dist/src/draft.js +427 -0
- package/dist/src/draft.js.map +1 -0
- package/dist/src/errors.d.ts +56 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +69 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/external.d.ts +52 -0
- package/dist/src/external.d.ts.map +1 -0
- package/dist/src/external.js +68 -0
- package/dist/src/external.js.map +1 -0
- package/dist/src/hamt.d.ts +87 -0
- package/dist/src/hamt.d.ts.map +1 -0
- package/dist/src/hamt.js +643 -0
- package/dist/src/hamt.js.map +1 -0
- package/dist/src/history.d.ts +45 -0
- package/dist/src/history.d.ts.map +1 -0
- package/dist/src/history.js +49 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/index.d.ts +55 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inspect.d.ts +74 -0
- package/dist/src/inspect.d.ts.map +1 -0
- package/dist/src/inspect.js +127 -0
- package/dist/src/inspect.js.map +1 -0
- package/dist/src/layout.d.ts +130 -0
- package/dist/src/layout.d.ts.map +1 -0
- package/dist/src/layout.js +149 -0
- package/dist/src/layout.js.map +1 -0
- package/dist/src/liveness.d.ts +48 -0
- package/dist/src/liveness.d.ts.map +1 -0
- package/dist/src/liveness.js +92 -0
- package/dist/src/liveness.js.map +1 -0
- package/dist/src/owner.d.ts +130 -0
- package/dist/src/owner.d.ts.map +1 -0
- package/dist/src/owner.js +368 -0
- package/dist/src/owner.js.map +1 -0
- package/dist/src/reader.d.ts +122 -0
- package/dist/src/reader.d.ts.map +1 -0
- package/dist/src/reader.js +325 -0
- package/dist/src/reader.js.map +1 -0
- package/dist/src/readers.d.ts +60 -0
- package/dist/src/readers.d.ts.map +1 -0
- package/dist/src/readers.js +122 -0
- package/dist/src/readers.js.map +1 -0
- package/dist/src/retained.d.ts +39 -0
- package/dist/src/retained.d.ts.map +1 -0
- package/dist/src/retained.js +99 -0
- package/dist/src/retained.js.map +1 -0
- package/dist/src/schema.d.ts +82 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +18 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/store.d.ts +83 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +127 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/strings.d.ts +37 -0
- package/dist/src/strings.d.ts.map +1 -0
- package/dist/src/strings.js +144 -0
- package/dist/src/strings.js.map +1 -0
- package/dist/src/tags.d.ts +47 -0
- package/dist/src/tags.d.ts.map +1 -0
- package/dist/src/tags.js +53 -0
- package/dist/src/tags.js.map +1 -0
- package/dist/src/values.d.ts +90 -0
- package/dist/src/values.d.ts.map +1 -0
- package/dist/src/values.js +458 -0
- package/dist/src/values.js.map +1 -0
- package/dist/src/vector.d.ts +64 -0
- package/dist/src/vector.d.ts.map +1 -0
- package/dist/src/vector.js +387 -0
- package/dist/src/vector.js.map +1 -0
- package/dist/src/verify.d.ts +38 -0
- package/dist/src/verify.d.ts.map +1 -0
- package/dist/src/verify.js +179 -0
- package/dist/src/verify.js.map +1 -0
- package/dist/src/view.d.ts +66 -0
- package/dist/src/view.d.ts.map +1 -0
- package/dist/src/view.js +278 -0
- package/dist/src/view.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value tags.
|
|
3
|
+
*
|
|
4
|
+
* A slot is eight bytes, two 32 bit words: a tag word and a payload word. Small integers
|
|
5
|
+
* and the singletons live entirely in the slot. Everything else stores a byte offset into
|
|
6
|
+
* the arena, which is why every decode has to bounds check before it dereferences.
|
|
7
|
+
*/
|
|
8
|
+
export declare const Tag: {
|
|
9
|
+
/** No payload. */
|
|
10
|
+
readonly Undefined: 0;
|
|
11
|
+
/** No payload. */
|
|
12
|
+
readonly Null: 1;
|
|
13
|
+
/** No payload. */
|
|
14
|
+
readonly False: 2;
|
|
15
|
+
/** No payload. */
|
|
16
|
+
readonly True: 3;
|
|
17
|
+
/** Payload is the value itself, in the signed 32 bit range. */
|
|
18
|
+
readonly Int32: 4;
|
|
19
|
+
/** Payload is the byte offset of an eight byte float64. */
|
|
20
|
+
readonly Double: 5;
|
|
21
|
+
/** Payload is the byte offset of an interned string record. */
|
|
22
|
+
readonly String: 6;
|
|
23
|
+
/** Payload is the byte offset of a HAMT root. Added by the object layer. */
|
|
24
|
+
readonly Object: 7;
|
|
25
|
+
/** Payload is the byte offset of a vector header. Added by the object layer. */
|
|
26
|
+
readonly Array: 8;
|
|
27
|
+
/** Payload is the byte offset of a HAMT root with arbitrary keys. */
|
|
28
|
+
readonly Map: 9;
|
|
29
|
+
/** Payload is the byte offset of a HAMT root used as a set. */
|
|
30
|
+
readonly Set: 10;
|
|
31
|
+
/** Payload is the byte offset of an eight byte float64 holding epoch milliseconds. */
|
|
32
|
+
readonly Date: 11;
|
|
33
|
+
/** Payload is the byte offset of a two word record: source string, flags string. */
|
|
34
|
+
readonly RegExp: 12;
|
|
35
|
+
/** Payload is the byte offset of a length prefixed little endian byte sequence. */
|
|
36
|
+
readonly BigInt: 13;
|
|
37
|
+
/** Payload is the byte offset of a kind, length, and byte payload record. */
|
|
38
|
+
readonly TypedArray: 14;
|
|
39
|
+
/** Payload is a handle into the asynchronous tier. */
|
|
40
|
+
readonly External: 15;
|
|
41
|
+
};
|
|
42
|
+
export type TagValue = (typeof Tag)[keyof typeof Tag];
|
|
43
|
+
export declare function tagName(tag: number): string;
|
|
44
|
+
export declare function isKnownTag(tag: number): tag is TagValue;
|
|
45
|
+
/** Tags whose payload is a byte offset that must be bounds checked before use. */
|
|
46
|
+
export declare function payloadIsOffset(tag: number): boolean;
|
|
47
|
+
//# sourceMappingURL=tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/tags.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,GAAG;IACd,kBAAkB;aAClB,SAAS,EAAE,CAAC;IACZ,kBAAkB;aAClB,IAAI,EAAE,CAAC;IACP,kBAAkB;aAClB,KAAK,EAAE,CAAC;IACR,kBAAkB;aAClB,IAAI,EAAE,CAAC;IACP,+DAA+D;aAC/D,KAAK,EAAE,CAAC;IACR,2DAA2D;aAC3D,MAAM,EAAE,CAAC;IACT,+DAA+D;aAC/D,MAAM,EAAE,CAAC;IACT,4EAA4E;aAC5E,MAAM,EAAE,CAAC;IACT,gFAAgF;aAChF,KAAK,EAAE,CAAC;IACR,qEAAqE;aACrE,GAAG,EAAE,CAAC;IACN,+DAA+D;aAC/D,GAAG,EAAE,EAAE;IACP,sFAAsF;aACtF,IAAI,EAAE,EAAE;IACR,oFAAoF;aACpF,MAAM,EAAE,EAAE;IACV,mFAAmF;aACnF,MAAM,EAAE,EAAE;IACV,6EAA6E;aAC7E,UAAU,EAAE,EAAE;IACd,sDAAsD;aACtD,QAAQ,EAAE,EAAE;CACJ,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AAMtD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,QAAQ,CAEvD;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
package/dist/src/tags.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value tags.
|
|
3
|
+
*
|
|
4
|
+
* A slot is eight bytes, two 32 bit words: a tag word and a payload word. Small integers
|
|
5
|
+
* and the singletons live entirely in the slot. Everything else stores a byte offset into
|
|
6
|
+
* the arena, which is why every decode has to bounds check before it dereferences.
|
|
7
|
+
*/
|
|
8
|
+
export const Tag = {
|
|
9
|
+
/** No payload. */
|
|
10
|
+
Undefined: 0,
|
|
11
|
+
/** No payload. */
|
|
12
|
+
Null: 1,
|
|
13
|
+
/** No payload. */
|
|
14
|
+
False: 2,
|
|
15
|
+
/** No payload. */
|
|
16
|
+
True: 3,
|
|
17
|
+
/** Payload is the value itself, in the signed 32 bit range. */
|
|
18
|
+
Int32: 4,
|
|
19
|
+
/** Payload is the byte offset of an eight byte float64. */
|
|
20
|
+
Double: 5,
|
|
21
|
+
/** Payload is the byte offset of an interned string record. */
|
|
22
|
+
String: 6,
|
|
23
|
+
/** Payload is the byte offset of a HAMT root. Added by the object layer. */
|
|
24
|
+
Object: 7,
|
|
25
|
+
/** Payload is the byte offset of a vector header. Added by the object layer. */
|
|
26
|
+
Array: 8,
|
|
27
|
+
/** Payload is the byte offset of a HAMT root with arbitrary keys. */
|
|
28
|
+
Map: 9,
|
|
29
|
+
/** Payload is the byte offset of a HAMT root used as a set. */
|
|
30
|
+
Set: 10,
|
|
31
|
+
/** Payload is the byte offset of an eight byte float64 holding epoch milliseconds. */
|
|
32
|
+
Date: 11,
|
|
33
|
+
/** Payload is the byte offset of a two word record: source string, flags string. */
|
|
34
|
+
RegExp: 12,
|
|
35
|
+
/** Payload is the byte offset of a length prefixed little endian byte sequence. */
|
|
36
|
+
BigInt: 13,
|
|
37
|
+
/** Payload is the byte offset of a kind, length, and byte payload record. */
|
|
38
|
+
TypedArray: 14,
|
|
39
|
+
/** Payload is a handle into the asynchronous tier. */
|
|
40
|
+
External: 15,
|
|
41
|
+
};
|
|
42
|
+
const NAMES = Object.fromEntries(Object.entries(Tag).map(([name, value]) => [value, name]));
|
|
43
|
+
export function tagName(tag) {
|
|
44
|
+
return NAMES[tag] ?? `unknown(${tag})`;
|
|
45
|
+
}
|
|
46
|
+
export function isKnownTag(tag) {
|
|
47
|
+
return tag >= Tag.Undefined && tag <= Tag.External;
|
|
48
|
+
}
|
|
49
|
+
/** Tags whose payload is a byte offset that must be bounds checked before use. */
|
|
50
|
+
export function payloadIsOffset(tag) {
|
|
51
|
+
return tag >= Tag.Double && tag <= Tag.TypedArray;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/tags.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,kBAAkB;IAClB,SAAS,EAAE,CAAC;IACZ,kBAAkB;IAClB,IAAI,EAAE,CAAC;IACP,kBAAkB;IAClB,KAAK,EAAE,CAAC;IACR,kBAAkB;IAClB,IAAI,EAAE,CAAC;IACP,+DAA+D;IAC/D,KAAK,EAAE,CAAC;IACR,2DAA2D;IAC3D,MAAM,EAAE,CAAC;IACT,+DAA+D;IAC/D,MAAM,EAAE,CAAC;IACT,4EAA4E;IAC5E,MAAM,EAAE,CAAC;IACT,gFAAgF;IAChF,KAAK,EAAE,CAAC;IACR,qEAAqE;IACrE,GAAG,EAAE,CAAC;IACN,+DAA+D;IAC/D,GAAG,EAAE,EAAE;IACP,sFAAsF;IACtF,IAAI,EAAE,EAAE;IACR,oFAAoF;IACpF,MAAM,EAAE,EAAE;IACV,mFAAmF;IACnF,MAAM,EAAE,EAAE;IACV,6EAA6E;IAC7E,UAAU,EAAE,EAAE;IACd,sDAAsD;IACtD,QAAQ,EAAE,EAAE;CACJ,CAAC;AAIX,MAAM,KAAK,GAA2B,MAAM,CAAC,WAAW,CACtD,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAC1D,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,WAAW,GAAG,GAAG,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC;AACrD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SharedArena } from "./arena.js";
|
|
2
|
+
import type { Allocator } from "./allocator.js";
|
|
3
|
+
import type { StringTable } from "./strings.js";
|
|
4
|
+
import { type HamtContext } from "./hamt.js";
|
|
5
|
+
/** A tagged eight byte slot, as a pair of 32 bit words. */
|
|
6
|
+
export interface Slot {
|
|
7
|
+
readonly tag: number;
|
|
8
|
+
readonly payload: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const UNDEFINED_SLOT: Slot;
|
|
11
|
+
export declare const NULL_SLOT: Slot;
|
|
12
|
+
export declare const TRUE_SLOT: Slot;
|
|
13
|
+
/** Typed array kinds, by index. The index is stored, so this list is append only. */
|
|
14
|
+
export declare const TYPED_ARRAY_KINDS: readonly ["Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"];
|
|
15
|
+
export type TypedArrayKind = (typeof TYPED_ARRAY_KINDS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* An opaque handle into the asynchronous tier.
|
|
18
|
+
*
|
|
19
|
+
* Values that cannot be encoded are reached through a visibly different API rather than
|
|
20
|
+
* throwing at some later point. The boundary between synchronous and asynchronous is meant
|
|
21
|
+
* to be legible in the calling code, not a runtime surprise.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ExternalRef {
|
|
24
|
+
readonly handle: number;
|
|
25
|
+
constructor(handle: number);
|
|
26
|
+
toJSON(): {
|
|
27
|
+
external: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface EncodeContext extends HamtContext {
|
|
31
|
+
readonly arena: SharedArena;
|
|
32
|
+
readonly allocator: Allocator;
|
|
33
|
+
readonly strings: StringTable;
|
|
34
|
+
/** Blocks the encoder allocated, so a failed commit can release them. */
|
|
35
|
+
readonly allocated: number[];
|
|
36
|
+
/** Nodes replaced on a copied path, retired when the superseded version is unreadable. */
|
|
37
|
+
readonly retired: number[];
|
|
38
|
+
/** Strings this commit newly interned, so a rejected commit can release them. */
|
|
39
|
+
readonly interned: string[];
|
|
40
|
+
/** The bump pointer before this commit, so a rejected commit can rewind to it. */
|
|
41
|
+
readonly bumpBefore: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function encodeNumber(context: EncodeContext, value: number): Slot;
|
|
44
|
+
export declare function encodeString(context: EncodeContext, value: string): Slot;
|
|
45
|
+
/**
|
|
46
|
+
* Encode a value into a slot.
|
|
47
|
+
*
|
|
48
|
+
* Containers are built from the bottom up, so a partially built structure is never
|
|
49
|
+
* reachable from a published root. Anything outside the ladder raises
|
|
50
|
+
* UnencodableValueError, which is the boundary the asynchronous tier exists to cover.
|
|
51
|
+
*/
|
|
52
|
+
export declare function encodeValue(context: EncodeContext, value: unknown): Slot;
|
|
53
|
+
/**
|
|
54
|
+
* A budget for one whole decode, measured in units of output rather than in calls.
|
|
55
|
+
*
|
|
56
|
+
* Counting calls is not enough, and the reason is worth writing down because it is the same
|
|
57
|
+
* shape as several bugs above. The depth bound and the per node budgets are all per call. A
|
|
58
|
+
* correct structure is a tree, but a corrupt one can be a graph, and a graph materialises a
|
|
59
|
+
* shared subtree once per reference. A trie whose entries all point at one large string
|
|
60
|
+
* record decodes that string once per entry: a few thousand references to a hundred kilobyte
|
|
61
|
+
* record is a gigabyte of output from a quarter megabyte arena.
|
|
62
|
+
*
|
|
63
|
+
* Note that this is not purely a corruption problem. A correct arena can do it too, because
|
|
64
|
+
* interned strings are shared in the arena and materialised separately by an eager decode.
|
|
65
|
+
* A structure with a thousand references to one long string legitimately decodes to a
|
|
66
|
+
* thousand copies. So the budget is generous rather than tight: it exists to turn an
|
|
67
|
+
* unbounded decode into a typed error, not to second guess a caller who really does want
|
|
68
|
+
* their data.
|
|
69
|
+
*
|
|
70
|
+
* The lazy read path has none of this to worry about, because it decodes what is touched.
|
|
71
|
+
*/
|
|
72
|
+
interface DecodeBudget {
|
|
73
|
+
remaining: number;
|
|
74
|
+
}
|
|
75
|
+
export declare function decodeValue(arena: SharedArena, slot: Slot, depth?: number, budget?: DecodeBudget): unknown;
|
|
76
|
+
/**
|
|
77
|
+
* Every block reachable from a slot, for retiring a structure the writer is replacing
|
|
78
|
+
* outright.
|
|
79
|
+
*
|
|
80
|
+
* Interned strings are deliberately absent. They are shared between versions and freed only
|
|
81
|
+
* when the arena is discarded, which is the documented cost of interning.
|
|
82
|
+
*
|
|
83
|
+
* This is the wholesale path. A targeted update collects retired nodes from the path copies
|
|
84
|
+
* it performs, which is bounded rather than proportional to the structure.
|
|
85
|
+
*/
|
|
86
|
+
export declare function collectBlocks(arena: SharedArena, slot: Slot, out?: number[]): number[];
|
|
87
|
+
/** The element or entry count of a container, without materialising it. */
|
|
88
|
+
export declare function containerSize(arena: SharedArena, slot: Slot): number;
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../src/values.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AASnB,2DAA2D;AAC3D,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,EAAE,IAAyC,CAAC;AACvE,eAAO,MAAM,SAAS,EAAE,IAAoC,CAAC;AAC7D,eAAO,MAAM,SAAS,EAAE,IAAoC,CAAC;AAE7D,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,YAC5B,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,CACR,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AA8BhE;;;;;;GAMG;AACH,qBAAa,WAAW;IACV,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAnC,YAAqB,MAAM,EAAE,MAAM,EAAI;IACvC,MAAM,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAE7B;CACF;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAYD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOxE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKxE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAuCxE;AA0ID;;;;;;;;;;;;;;;;;;GAkBG;AACH,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAiBD,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,IAAI,EACV,KAAK,SAAI,EACT,MAAM,GAAE,YAA+B,GACtC,OAAO,CA0ET;AAqGD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA+B1F;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAWpE"}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { decodeString } from "./strings.js";
|
|
2
|
+
import { ArenaCorruptError, UnencodableValueError } from "./errors.js";
|
|
3
|
+
import { Tag, isKnownTag, tagName } from "./tags.js";
|
|
4
|
+
import { WORD } from "./layout.js";
|
|
5
|
+
import { EMPTY_NODE, emptyNode, hamtAssoc, hamtEntries, hamtNodes, hamtSize, keyHash, } from "./hamt.js";
|
|
6
|
+
import { EMPTY_VECTOR, vectorFromSlots, vectorLength, vectorNodes, vectorSlots, } from "./vector.js";
|
|
7
|
+
export const UNDEFINED_SLOT = { tag: Tag.Undefined, payload: 0 };
|
|
8
|
+
export const NULL_SLOT = { tag: Tag.Null, payload: 0 };
|
|
9
|
+
export const TRUE_SLOT = { tag: Tag.True, payload: 0 };
|
|
10
|
+
/** Typed array kinds, by index. The index is stored, so this list is append only. */
|
|
11
|
+
export const TYPED_ARRAY_KINDS = [
|
|
12
|
+
"Int8Array",
|
|
13
|
+
"Uint8Array",
|
|
14
|
+
"Uint8ClampedArray",
|
|
15
|
+
"Int16Array",
|
|
16
|
+
"Uint16Array",
|
|
17
|
+
"Int32Array",
|
|
18
|
+
"Uint32Array",
|
|
19
|
+
"Float32Array",
|
|
20
|
+
"Float64Array",
|
|
21
|
+
"BigInt64Array",
|
|
22
|
+
"BigUint64Array",
|
|
23
|
+
];
|
|
24
|
+
const TYPED_ARRAY_ELEMENT_BYTES = {
|
|
25
|
+
Int8Array: 1,
|
|
26
|
+
Uint8Array: 1,
|
|
27
|
+
Uint8ClampedArray: 1,
|
|
28
|
+
Int16Array: 2,
|
|
29
|
+
Uint16Array: 2,
|
|
30
|
+
Int32Array: 4,
|
|
31
|
+
Uint32Array: 4,
|
|
32
|
+
Float32Array: 4,
|
|
33
|
+
Float64Array: 8,
|
|
34
|
+
BigInt64Array: 8,
|
|
35
|
+
BigUint64Array: 8,
|
|
36
|
+
};
|
|
37
|
+
const TYPED_ARRAY_CONSTRUCTORS = {
|
|
38
|
+
Int8Array,
|
|
39
|
+
Uint8Array,
|
|
40
|
+
Uint8ClampedArray,
|
|
41
|
+
Int16Array,
|
|
42
|
+
Uint16Array,
|
|
43
|
+
Int32Array,
|
|
44
|
+
Uint32Array,
|
|
45
|
+
Float32Array,
|
|
46
|
+
Float64Array,
|
|
47
|
+
BigInt64Array,
|
|
48
|
+
BigUint64Array,
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* An opaque handle into the asynchronous tier.
|
|
52
|
+
*
|
|
53
|
+
* Values that cannot be encoded are reached through a visibly different API rather than
|
|
54
|
+
* throwing at some later point. The boundary between synchronous and asynchronous is meant
|
|
55
|
+
* to be legible in the calling code, not a runtime surprise.
|
|
56
|
+
*/
|
|
57
|
+
export class ExternalRef {
|
|
58
|
+
handle;
|
|
59
|
+
constructor(handle) {
|
|
60
|
+
this.handle = handle;
|
|
61
|
+
}
|
|
62
|
+
toJSON() {
|
|
63
|
+
return { external: this.handle };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isInt32(value) {
|
|
67
|
+
return Number.isInteger(value) && value >= -2147483648 && value <= 2147483647;
|
|
68
|
+
}
|
|
69
|
+
function allocate(context, bytes) {
|
|
70
|
+
const offset = context.allocator.allocate(bytes);
|
|
71
|
+
context.allocated.push(offset);
|
|
72
|
+
return offset;
|
|
73
|
+
}
|
|
74
|
+
export function encodeNumber(context, value) {
|
|
75
|
+
// Negative zero has to take the double path. Encoded as an int32 it would read back as
|
|
76
|
+
// positive zero, and Object.is would disagree with the value that went in.
|
|
77
|
+
if (isInt32(value) && !Object.is(value, -0))
|
|
78
|
+
return { tag: Tag.Int32, payload: value };
|
|
79
|
+
const offset = allocate(context, 8);
|
|
80
|
+
context.arena.floats[offset / 8] = value;
|
|
81
|
+
return { tag: Tag.Double, payload: offset };
|
|
82
|
+
}
|
|
83
|
+
export function encodeString(context, value) {
|
|
84
|
+
// Interned, so the offset may be one the table already held. It goes on the interning
|
|
85
|
+
// journal rather than the allocated list: a rollback must release only the records this
|
|
86
|
+
// commit created, never one an earlier version already references.
|
|
87
|
+
return { tag: Tag.String, payload: context.strings.intern(value, context.interned) };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Encode a value into a slot.
|
|
91
|
+
*
|
|
92
|
+
* Containers are built from the bottom up, so a partially built structure is never
|
|
93
|
+
* reachable from a published root. Anything outside the ladder raises
|
|
94
|
+
* UnencodableValueError, which is the boundary the asynchronous tier exists to cover.
|
|
95
|
+
*/
|
|
96
|
+
export function encodeValue(context, value) {
|
|
97
|
+
switch (typeof value) {
|
|
98
|
+
case "undefined":
|
|
99
|
+
return UNDEFINED_SLOT;
|
|
100
|
+
case "boolean":
|
|
101
|
+
return { tag: value ? Tag.True : Tag.False, payload: 0 };
|
|
102
|
+
case "number":
|
|
103
|
+
return encodeNumber(context, value);
|
|
104
|
+
case "string":
|
|
105
|
+
return encodeString(context, value);
|
|
106
|
+
case "bigint":
|
|
107
|
+
return encodeBigInt(context, value);
|
|
108
|
+
case "symbol":
|
|
109
|
+
throw new UnencodableValueError(value, "symbols have no cross process identity");
|
|
110
|
+
case "function":
|
|
111
|
+
throw new UnencodableValueError(value, "functions cannot cross a process boundary");
|
|
112
|
+
case "object":
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
throw new UnencodableValueError(value, `unsupported typeof result ${typeof value}`);
|
|
116
|
+
}
|
|
117
|
+
if (value === null)
|
|
118
|
+
return NULL_SLOT;
|
|
119
|
+
if (value instanceof ExternalRef)
|
|
120
|
+
return { tag: Tag.External, payload: value.handle };
|
|
121
|
+
if (value instanceof Date)
|
|
122
|
+
return encodeDate(context, value);
|
|
123
|
+
if (value instanceof RegExp)
|
|
124
|
+
return encodeRegExp(context, value);
|
|
125
|
+
if (Array.isArray(value))
|
|
126
|
+
return encodeArray(context, value);
|
|
127
|
+
if (value instanceof Map)
|
|
128
|
+
return encodeMap(context, value);
|
|
129
|
+
if (value instanceof Set)
|
|
130
|
+
return encodeSet(context, value);
|
|
131
|
+
if (ArrayBuffer.isView(value))
|
|
132
|
+
return encodeTypedArray(context, value);
|
|
133
|
+
const prototype = Object.getPrototypeOf(value);
|
|
134
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
135
|
+
throw new UnencodableValueError(value, "only plain objects are encodable, a class instance loses its prototype and its identity");
|
|
136
|
+
}
|
|
137
|
+
return encodeObject(context, value);
|
|
138
|
+
}
|
|
139
|
+
function encodeDate(context, value) {
|
|
140
|
+
const offset = allocate(context, 8);
|
|
141
|
+
context.arena.floats[offset / 8] = value.getTime();
|
|
142
|
+
return { tag: Tag.Date, payload: offset };
|
|
143
|
+
}
|
|
144
|
+
function encodeRegExp(context, value) {
|
|
145
|
+
const source = context.strings.intern(value.source, context.interned);
|
|
146
|
+
const flags = context.strings.intern(value.flags, context.interned);
|
|
147
|
+
const offset = allocate(context, 2 * WORD);
|
|
148
|
+
context.arena.words[offset / WORD] = source;
|
|
149
|
+
context.arena.words[offset / WORD + 1] = flags;
|
|
150
|
+
return { tag: Tag.RegExp, payload: offset };
|
|
151
|
+
}
|
|
152
|
+
function encodeBigInt(context, value) {
|
|
153
|
+
const negative = value < 0n;
|
|
154
|
+
let magnitude = negative ? -value : value;
|
|
155
|
+
const digits = [];
|
|
156
|
+
while (magnitude > 0n) {
|
|
157
|
+
digits.push(Number(magnitude & 0xffn));
|
|
158
|
+
magnitude >>= 8n;
|
|
159
|
+
}
|
|
160
|
+
const offset = allocate(context, 2 * WORD + Math.max(digits.length, 1));
|
|
161
|
+
const words = context.arena.words;
|
|
162
|
+
words[offset / WORD] = negative ? 1 : 0;
|
|
163
|
+
words[offset / WORD + 1] = digits.length;
|
|
164
|
+
const bytes = context.arena.bytes;
|
|
165
|
+
for (let i = 0; i < digits.length; i += 1)
|
|
166
|
+
bytes[offset + 2 * WORD + i] = digits[i];
|
|
167
|
+
return { tag: Tag.BigInt, payload: offset };
|
|
168
|
+
}
|
|
169
|
+
function encodeTypedArray(context, value) {
|
|
170
|
+
const kind = value.constructor.name;
|
|
171
|
+
const kindIndex = TYPED_ARRAY_KINDS.indexOf(kind);
|
|
172
|
+
if (kindIndex === -1) {
|
|
173
|
+
throw new UnencodableValueError(value, `${kind} is not on the typed array ladder`);
|
|
174
|
+
}
|
|
175
|
+
const source = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
176
|
+
const offset = allocate(context, 2 * WORD + Math.max(value.byteLength, 1));
|
|
177
|
+
const words = context.arena.words;
|
|
178
|
+
words[offset / WORD] = kindIndex;
|
|
179
|
+
words[offset / WORD + 1] = value.byteLength;
|
|
180
|
+
context.arena.bytes.set(source, offset + 2 * WORD);
|
|
181
|
+
return { tag: Tag.TypedArray, payload: offset };
|
|
182
|
+
}
|
|
183
|
+
function encodeArray(context, value) {
|
|
184
|
+
const slots = value.map((element) => encodeValue(context, element));
|
|
185
|
+
return { tag: Tag.Array, payload: vectorFromSlots(context, slots) };
|
|
186
|
+
}
|
|
187
|
+
function encodeObject(context, value) {
|
|
188
|
+
let node = emptyNode(context);
|
|
189
|
+
for (const key of Object.keys(value)) {
|
|
190
|
+
const keySlot = encodeString(context, key);
|
|
191
|
+
const valueSlot = encodeValue(context, value[key]);
|
|
192
|
+
node = hamtAssoc(context, node, keySlot, keyHash(context.arena, keySlot), valueSlot);
|
|
193
|
+
}
|
|
194
|
+
return { tag: Tag.Object, payload: node };
|
|
195
|
+
}
|
|
196
|
+
function encodeMap(context, value) {
|
|
197
|
+
let node = emptyNode(context);
|
|
198
|
+
for (const [key, entry] of value) {
|
|
199
|
+
const keySlot = encodeValue(context, key);
|
|
200
|
+
assertKeyable(keySlot, key);
|
|
201
|
+
node = hamtAssoc(context, node, keySlot, keyHash(context.arena, keySlot), encodeValue(context, entry));
|
|
202
|
+
}
|
|
203
|
+
return { tag: Tag.Map, payload: node };
|
|
204
|
+
}
|
|
205
|
+
function encodeSet(context, value) {
|
|
206
|
+
let node = emptyNode(context);
|
|
207
|
+
for (const member of value) {
|
|
208
|
+
const keySlot = encodeValue(context, member);
|
|
209
|
+
assertKeyable(keySlot, member);
|
|
210
|
+
node = hamtAssoc(context, node, keySlot, keyHash(context.arena, keySlot), TRUE_SLOT);
|
|
211
|
+
}
|
|
212
|
+
return { tag: Tag.Set, payload: node };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Keys have to be comparable by their slot, so a container cannot be one.
|
|
216
|
+
*
|
|
217
|
+
* Object identity does not survive a process boundary, so a map keyed on an object would be
|
|
218
|
+
* a map nobody could look anything up in. Rejecting it is better than encoding it into
|
|
219
|
+
* something that silently never matches.
|
|
220
|
+
*/
|
|
221
|
+
function assertKeyable(slot, original) {
|
|
222
|
+
const keyable = slot.tag === Tag.String ||
|
|
223
|
+
slot.tag === Tag.Int32 ||
|
|
224
|
+
slot.tag === Tag.Double ||
|
|
225
|
+
slot.tag === Tag.True ||
|
|
226
|
+
slot.tag === Tag.False ||
|
|
227
|
+
slot.tag === Tag.Null ||
|
|
228
|
+
slot.tag === Tag.Undefined;
|
|
229
|
+
if (!keyable) {
|
|
230
|
+
throw new UnencodableValueError(original, "map and set keys must be primitives, because object identity does not cross a process");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Decoding
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
/**
|
|
237
|
+
* Decode a slot eagerly, materialising the whole structure.
|
|
238
|
+
*
|
|
239
|
+
* Used by `toJSON`, by tests, and by any caller that wants a detached plain value. The lazy
|
|
240
|
+
* proxy path in snapshot-proxy.ts is what a render actually uses, because it decodes only
|
|
241
|
+
* the properties that are touched.
|
|
242
|
+
*
|
|
243
|
+
* Every branch that dereferences an offset validates it first. Any window mapping the arena
|
|
244
|
+
* can write a slot, so a wild offset is a reachable state rather than a bug that cannot
|
|
245
|
+
* happen.
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* How deeply a decode will nest before it gives up.
|
|
249
|
+
*
|
|
250
|
+
* A corrupt offset can make a value contain itself, and that recursion is not covered by the
|
|
251
|
+
* depth bound inside a single trie: each level is a separate structure. Without this, a cycle
|
|
252
|
+
* across containers runs the stack out and produces a RangeError rather than a typed one.
|
|
253
|
+
*/
|
|
254
|
+
const MAX_DECODE_DEPTH = 512;
|
|
255
|
+
/** Sixteen times the arena, and never less than a megabyte of output. */
|
|
256
|
+
function newBudget(arena) {
|
|
257
|
+
return { remaining: Math.max(1 << 20, arena.byteLength * 16) };
|
|
258
|
+
}
|
|
259
|
+
function spend(budget, units) {
|
|
260
|
+
budget.remaining -= units;
|
|
261
|
+
if (budget.remaining >= 0)
|
|
262
|
+
return;
|
|
263
|
+
throw new ArenaCorruptError("decoding produced more output than the budget allows, which means the structure shares " +
|
|
264
|
+
"nodes far more heavily than its size can explain. Read through a snapshot view instead " +
|
|
265
|
+
"of materialising it, or raise the arena size if the data really is this shape.");
|
|
266
|
+
}
|
|
267
|
+
export function decodeValue(arena, slot, depth = 0, budget = newBudget(arena)) {
|
|
268
|
+
const { tag, payload } = slot;
|
|
269
|
+
if (depth > MAX_DECODE_DEPTH) {
|
|
270
|
+
throw new ArenaCorruptError(`decoding nested past ${MAX_DECODE_DEPTH} levels, which means a cycle`, { actual: depth });
|
|
271
|
+
}
|
|
272
|
+
spend(budget, 1);
|
|
273
|
+
if (!isKnownTag(tag)) {
|
|
274
|
+
throw new ArenaCorruptError(`slot carries an unknown tag ${tag}`, { actual: tag });
|
|
275
|
+
}
|
|
276
|
+
switch (tag) {
|
|
277
|
+
case Tag.Undefined:
|
|
278
|
+
return undefined;
|
|
279
|
+
case Tag.Null:
|
|
280
|
+
return null;
|
|
281
|
+
case Tag.False:
|
|
282
|
+
return false;
|
|
283
|
+
case Tag.True:
|
|
284
|
+
return true;
|
|
285
|
+
case Tag.Int32:
|
|
286
|
+
return payload;
|
|
287
|
+
case Tag.Double:
|
|
288
|
+
arena.checkBlock(payload, "double");
|
|
289
|
+
return arena.floats[payload / 8];
|
|
290
|
+
case Tag.String: {
|
|
291
|
+
const text = decodeString(arena, payload);
|
|
292
|
+
spend(budget, text.length);
|
|
293
|
+
return text;
|
|
294
|
+
}
|
|
295
|
+
case Tag.Date:
|
|
296
|
+
arena.checkBlock(payload, "date");
|
|
297
|
+
return new Date(arena.floats[payload / 8]);
|
|
298
|
+
case Tag.RegExp:
|
|
299
|
+
return decodeRegExp(arena, payload);
|
|
300
|
+
case Tag.BigInt: {
|
|
301
|
+
const value = decodeBigInt(arena, payload);
|
|
302
|
+
spend(budget, arena.checkBlock(payload, "bigint"));
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
case Tag.TypedArray: {
|
|
306
|
+
const view = decodeTypedArray(arena, payload);
|
|
307
|
+
spend(budget, view.byteLength);
|
|
308
|
+
return view;
|
|
309
|
+
}
|
|
310
|
+
case Tag.Object:
|
|
311
|
+
return decodeObject(arena, payload, depth, budget);
|
|
312
|
+
case Tag.Array:
|
|
313
|
+
return vectorSlots(arena, payload).map((element) => decodeValue(arena, element, depth + 1, budget));
|
|
314
|
+
case Tag.Map:
|
|
315
|
+
return new Map(hamtEntries(arena, payload).map((entry) => [
|
|
316
|
+
decodeValue(arena, entry.key, depth + 1, budget),
|
|
317
|
+
decodeValue(arena, entry.value, depth + 1, budget),
|
|
318
|
+
]));
|
|
319
|
+
case Tag.Set:
|
|
320
|
+
return new Set(hamtEntries(arena, payload).map((entry) => decodeValue(arena, entry.key, depth + 1, budget)));
|
|
321
|
+
case Tag.External:
|
|
322
|
+
return new ExternalRef(payload);
|
|
323
|
+
default:
|
|
324
|
+
throw new ArenaCorruptError(`tag ${tagName(tag)} has no decoder`, { actual: tag });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function decodeObject(arena, node, depth, budget) {
|
|
328
|
+
const out = {};
|
|
329
|
+
for (const entry of hamtEntries(arena, node)) {
|
|
330
|
+
if (entry.key.tag !== Tag.String) {
|
|
331
|
+
throw new ArenaCorruptError("object key is not a string", { actual: entry.key.tag });
|
|
332
|
+
}
|
|
333
|
+
out[decodeString(arena, entry.key.payload)] = decodeValue(arena, entry.value, depth + 1, budget);
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
function decodeRegExp(arena, offset) {
|
|
338
|
+
const byteSize = arena.checkBlock(offset, "regexp");
|
|
339
|
+
if (byteSize < 2 * WORD) {
|
|
340
|
+
throw new ArenaCorruptError(`regexp record at ${offset} is too small`, { offset });
|
|
341
|
+
}
|
|
342
|
+
const source = decodeString(arena, arena.words[offset / WORD]);
|
|
343
|
+
const flags = decodeString(arena, arena.words[offset / WORD + 1]);
|
|
344
|
+
try {
|
|
345
|
+
return new RegExp(source, flags);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
throw new ArenaCorruptError(`regexp record at ${offset} does not describe a valid expression: ${error instanceof Error ? error.message : String(error)}`, { offset });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function decodeBigInt(arena, offset) {
|
|
352
|
+
const byteSize = arena.checkBlock(offset, "bigint");
|
|
353
|
+
const negative = arena.words[offset / WORD] === 1;
|
|
354
|
+
const length = arena.words[offset / WORD + 1];
|
|
355
|
+
if (length < 0 || 2 * WORD + length > byteSize) {
|
|
356
|
+
throw new ArenaCorruptError(`bigint at ${offset} claims ${length} bytes`, { offset });
|
|
357
|
+
}
|
|
358
|
+
if (length === 0)
|
|
359
|
+
return 0n;
|
|
360
|
+
// Built through a hex string rather than by shifting a BigInt one byte at a time. The
|
|
361
|
+
// shifting version allocates a new BigInt per byte, each one longer than the last, so it is
|
|
362
|
+
// quadratic in both time and allocation. A corrupt length of a quarter megabyte made it
|
|
363
|
+
// churn tens of gigabytes and take the process down, which the fuzzer found as an out of
|
|
364
|
+
// memory crash rather than as an error.
|
|
365
|
+
const bytes = arena.bytes;
|
|
366
|
+
const digits = new Array(length);
|
|
367
|
+
for (let i = 0; i < length; i += 1) {
|
|
368
|
+
digits[length - 1 - i] = bytes[offset + 2 * WORD + i].toString(16).padStart(2, "0");
|
|
369
|
+
}
|
|
370
|
+
const value = BigInt(`0x${digits.join("")}`);
|
|
371
|
+
return negative ? -value : value;
|
|
372
|
+
}
|
|
373
|
+
function decodeTypedArray(arena, offset) {
|
|
374
|
+
const byteSize = arena.checkBlock(offset, "typed array");
|
|
375
|
+
const kindIndex = arena.words[offset / WORD];
|
|
376
|
+
const byteLength = arena.words[offset / WORD + 1];
|
|
377
|
+
const kind = TYPED_ARRAY_KINDS[kindIndex];
|
|
378
|
+
if (kind === undefined) {
|
|
379
|
+
throw new ArenaCorruptError(`typed array at ${offset} has kind ${kindIndex}`, { offset });
|
|
380
|
+
}
|
|
381
|
+
if (byteLength < 0 || 2 * WORD + byteLength > byteSize) {
|
|
382
|
+
throw new ArenaCorruptError(`typed array at ${offset} claims ${byteLength} bytes`, { offset });
|
|
383
|
+
}
|
|
384
|
+
const Constructor = TYPED_ARRAY_CONSTRUCTORS[kind];
|
|
385
|
+
const elementBytes = TYPED_ARRAY_ELEMENT_BYTES[kind];
|
|
386
|
+
if (Constructor === undefined || elementBytes === undefined) {
|
|
387
|
+
throw new ArenaCorruptError(`typed array kind ${kind} is not constructible here`, { offset });
|
|
388
|
+
}
|
|
389
|
+
if (byteLength % elementBytes !== 0) {
|
|
390
|
+
throw new ArenaCorruptError(`typed array at ${offset} has ${byteLength} bytes, which is not a whole number of ` +
|
|
391
|
+
`${kind} elements`, { offset });
|
|
392
|
+
}
|
|
393
|
+
const result = new Constructor(byteLength / elementBytes);
|
|
394
|
+
new Uint8Array(result.buffer).set(arena.bytes.subarray(offset + 2 * WORD, offset + 2 * WORD + byteLength));
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Retirement
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
/**
|
|
401
|
+
* Every block reachable from a slot, for retiring a structure the writer is replacing
|
|
402
|
+
* outright.
|
|
403
|
+
*
|
|
404
|
+
* Interned strings are deliberately absent. They are shared between versions and freed only
|
|
405
|
+
* when the arena is discarded, which is the documented cost of interning.
|
|
406
|
+
*
|
|
407
|
+
* This is the wholesale path. A targeted update collects retired nodes from the path copies
|
|
408
|
+
* it performs, which is bounded rather than proportional to the structure.
|
|
409
|
+
*/
|
|
410
|
+
export function collectBlocks(arena, slot, out = []) {
|
|
411
|
+
// The writer only ever walks structures it built, so this needs no budget of its own. The
|
|
412
|
+
// walks it delegates to carry theirs.
|
|
413
|
+
switch (slot.tag) {
|
|
414
|
+
case Tag.Double:
|
|
415
|
+
case Tag.Date:
|
|
416
|
+
case Tag.RegExp:
|
|
417
|
+
case Tag.BigInt:
|
|
418
|
+
case Tag.TypedArray:
|
|
419
|
+
out.push(slot.payload);
|
|
420
|
+
return out;
|
|
421
|
+
case Tag.Object:
|
|
422
|
+
case Tag.Map:
|
|
423
|
+
case Tag.Set: {
|
|
424
|
+
if (slot.payload === EMPTY_NODE)
|
|
425
|
+
return out;
|
|
426
|
+
for (const entry of hamtEntries(arena, slot.payload)) {
|
|
427
|
+
collectBlocks(arena, entry.key, out);
|
|
428
|
+
collectBlocks(arena, entry.value, out);
|
|
429
|
+
}
|
|
430
|
+
hamtNodes(arena, slot.payload, out);
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
case Tag.Array: {
|
|
434
|
+
if (slot.payload === EMPTY_VECTOR)
|
|
435
|
+
return out;
|
|
436
|
+
for (const element of vectorSlots(arena, slot.payload))
|
|
437
|
+
collectBlocks(arena, element, out);
|
|
438
|
+
vectorNodes(arena, slot.payload, out);
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
default:
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** The element or entry count of a container, without materialising it. */
|
|
446
|
+
export function containerSize(arena, slot) {
|
|
447
|
+
switch (slot.tag) {
|
|
448
|
+
case Tag.Object:
|
|
449
|
+
case Tag.Map:
|
|
450
|
+
case Tag.Set:
|
|
451
|
+
return hamtSize(arena, slot.payload);
|
|
452
|
+
case Tag.Array:
|
|
453
|
+
return vectorLength(arena, slot.payload);
|
|
454
|
+
default:
|
|
455
|
+
return 0;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
//# sourceMappingURL=values.js.map
|