@hew-lang/sandbox-vm 0.9.3 → 0.10.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/interpreter/run-program.js +5 -0
- package/dist/interpreter/strings.d.ts +6 -0
- package/dist/interpreter/strings.js +8 -0
- package/dist/interpreter/types.d.ts +1 -1
- package/dist/interpreter/v1/browser-directory.js +2 -2
- package/dist/interpreter/v1/codecs.js +2 -2
- package/dist/interpreter/v1/encoding-storage.d.ts +33 -0
- package/dist/interpreter/v1/encoding-storage.js +113 -0
- package/dist/interpreter/v1/encoding-values.js +28 -18
- package/dist/interpreter/v1/exec.js +1506 -130
- package/dist/interpreter/v1/instructions.generated.d.ts +100 -8
- package/dist/interpreter/v1/instructions.generated.js +1 -0
- package/dist/interpreter/v1/managed-budget.d.ts +88 -0
- package/dist/interpreter/v1/managed-budget.js +561 -0
- package/dist/interpreter/v1/package-graph.js +77 -2
- package/dist/interpreter/v1/package-validator.generated.js +22796 -17531
- package/dist/interpreter/v1/package.d.ts +4 -2
- package/dist/interpreter/v1/package.js +6 -0
- package/dist/interpreter/v1/pipes.d.ts +24 -1
- package/dist/interpreter/v1/pipes.js +91 -8
- package/dist/interpreter/v1/shims.d.ts +1 -0
- package/dist/interpreter/v1/shims.js +88 -62
- package/dist/interpreter/v1/structural.js +4 -2
- package/dist/interpreter/v1/validate.js +4 -2
- package/dist/interpreter/v1/value-operations.d.ts +3 -1
- package/dist/interpreter/v1/value-operations.js +32 -18
- package/dist/interpreter/values.d.ts +45 -11
- package/dist/interpreter/values.js +176 -8
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UNIT, cloneValue, compareUtf8, } from "../values.js";
|
|
1
|
+
import { UNIT, mutableVector, vectorItems, vectorLength, vectorElement, cloneValue, compareUtf8, } from "../values.js";
|
|
2
2
|
import { runtimeTag, } from "./package.js";
|
|
3
3
|
import { ShimFault } from "./shims.js";
|
|
4
4
|
import { actorIdentical } from "./actor-identity.js";
|
|
@@ -109,13 +109,13 @@ export function* selectedValue(pkg, id, args) {
|
|
|
109
109
|
if (plan.ty === "bytes") {
|
|
110
110
|
if (left.kind !== "vector")
|
|
111
111
|
throw new Error("bytes capability has no bytes");
|
|
112
|
-
const bytes = left.
|
|
112
|
+
const bytes = vectorItems(left).map((value) => Number(bits(value, "u8")));
|
|
113
113
|
if (hash)
|
|
114
114
|
return integer(bytesHash(bytes));
|
|
115
115
|
if (equality)
|
|
116
116
|
return boolean(right?.kind === "vector" &&
|
|
117
|
-
bytes.length === right
|
|
118
|
-
bytes.every((value, i) => BigInt(value) === bits(right
|
|
117
|
+
bytes.length === vectorLength(right) &&
|
|
118
|
+
bytes.every((value, i) => BigInt(value) === bits(vectorElement(right, i), "u8")));
|
|
119
119
|
throw new Error("bytes ordering is outside the checked capability domain");
|
|
120
120
|
}
|
|
121
121
|
if (left.kind === "actor" || left.kind === "supervisor") {
|
|
@@ -151,7 +151,7 @@ export function* selectedValue(pkg, id, args) {
|
|
|
151
151
|
selections = plan.variants?.[left.tag] ?? [];
|
|
152
152
|
}
|
|
153
153
|
else if (left.kind === "vector") {
|
|
154
|
-
fields = left
|
|
154
|
+
fields = vectorItems(left);
|
|
155
155
|
if (hash) {
|
|
156
156
|
const elementSelection = components[0];
|
|
157
157
|
if (!elementSelection)
|
|
@@ -160,9 +160,10 @@ export function* selectedValue(pkg, id, args) {
|
|
|
160
160
|
vectorHashLength = fields.length;
|
|
161
161
|
}
|
|
162
162
|
else {
|
|
163
|
-
if (right?.kind !== "vector" ||
|
|
163
|
+
if (right?.kind !== "vector" ||
|
|
164
|
+
vectorLength(left) !== vectorLength(right))
|
|
164
165
|
return boolean(false);
|
|
165
|
-
others = right
|
|
166
|
+
others = vectorItems(right);
|
|
166
167
|
selections = fields.map(() => components[0]);
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -324,17 +325,20 @@ export function* sharedOperation(operation, args, owner, typeName, option) {
|
|
|
324
325
|
/** Open-addressed collection operations preserve native callback ordering:
|
|
325
326
|
* growth hashes occupied slots before probing the incoming key. Staged slots
|
|
326
327
|
* borrow their owners until every fallible callback has completed. */
|
|
327
|
-
export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
328
|
+
export function* collectionOperation(pkg, request, args, callbacks, shape, elementType) {
|
|
328
329
|
const { family, operation } = request;
|
|
329
330
|
if (family === "Vector") {
|
|
330
331
|
const receiver = args[0];
|
|
331
332
|
if (receiver?.kind !== "vector")
|
|
332
333
|
throw new Error("vector operation has no vector");
|
|
333
334
|
if (operation === "Clear") {
|
|
335
|
+
mutableVector(receiver);
|
|
334
336
|
const items = receiver.items.splice(0);
|
|
335
337
|
yield {
|
|
336
338
|
kind: "release",
|
|
337
339
|
value: { kind: "vector", elementType: receiver.elementType, items },
|
|
340
|
+
retained: items,
|
|
341
|
+
retainedType: elementType,
|
|
338
342
|
};
|
|
339
343
|
return receiver;
|
|
340
344
|
}
|
|
@@ -343,14 +347,15 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
343
347
|
if (index?.kind !== "i64")
|
|
344
348
|
throw new Error("vector mutation has no index");
|
|
345
349
|
const at = Number(index.value);
|
|
346
|
-
if (at < 0 || at >= receiver
|
|
347
|
-
throw new ShimFault("vector_bounds",
|
|
350
|
+
if (at < 0 || at >= vectorLength(receiver))
|
|
351
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${vectorLength(receiver)}`);
|
|
352
|
+
mutableVector(receiver);
|
|
348
353
|
const old = receiver.items[at];
|
|
349
354
|
receiver.items[at] = args[2];
|
|
350
|
-
yield { kind: "release", value: old };
|
|
355
|
+
yield { kind: "release", value: old, retainedType: elementType };
|
|
351
356
|
return receiver;
|
|
352
357
|
}
|
|
353
|
-
for (const item of receiver
|
|
358
|
+
for (const item of vectorItems(receiver)) {
|
|
354
359
|
const equal = yield* selectedValue(pkg, callbacks[0], [item, args[1]]);
|
|
355
360
|
if (equal.kind !== "bool")
|
|
356
361
|
throw new Error("Eq returned a non-boolean value");
|
|
@@ -377,6 +382,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
377
382
|
receiver.tombstones = new Set();
|
|
378
383
|
yield {
|
|
379
384
|
kind: "release",
|
|
385
|
+
retained: entries.flatMap((entry) => [entry.key, entry.value]),
|
|
380
386
|
value: {
|
|
381
387
|
kind: "vector",
|
|
382
388
|
elementType: "",
|
|
@@ -389,15 +395,22 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
389
395
|
operation === "Values" ||
|
|
390
396
|
operation === "Elements" ||
|
|
391
397
|
operation === "Entries") {
|
|
392
|
-
|
|
398
|
+
const result = {
|
|
393
399
|
kind: "vector",
|
|
394
400
|
elementType: "",
|
|
395
|
-
items: [
|
|
396
|
-
? cloneValue(value)
|
|
397
|
-
: operation === "Entries"
|
|
398
|
-
? tuple(cloneValue(key), cloneValue(value))
|
|
399
|
-
: cloneValue(key)),
|
|
401
|
+
items: [],
|
|
400
402
|
};
|
|
403
|
+
const items = [...receiver.entries.values()].map(({ key, value }) => operation === "Values"
|
|
404
|
+
? cloneValue(value)
|
|
405
|
+
: operation === "Entries"
|
|
406
|
+
? tuple(cloneValue(key), cloneValue(value))
|
|
407
|
+
: cloneValue(key));
|
|
408
|
+
// Native snapshots push into an initially empty Vec, retaining its normal
|
|
409
|
+
// growth capacity. A later semantic clone may use exact live length.
|
|
410
|
+
mutableVector(result, items.length);
|
|
411
|
+
for (const item of items)
|
|
412
|
+
result.items.push(item);
|
|
413
|
+
return result;
|
|
401
414
|
}
|
|
402
415
|
let staged = receiver;
|
|
403
416
|
if (operation === "Insert" &&
|
|
@@ -435,6 +448,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
|
|
|
435
448
|
return cloneValue(entry.value);
|
|
436
449
|
}
|
|
437
450
|
if (operation === "Insert") {
|
|
451
|
+
receiver.prepareInsert?.(staged, result.slot, entry?.key ?? args[1], family === "Set" ? UNIT : args[2], family !== "Set" && !entry);
|
|
438
452
|
receiver.entries = staged.entries;
|
|
439
453
|
receiver.capacity = staged.capacity;
|
|
440
454
|
receiver.tombstones = staged.tombstones;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { JsonValue } from "./types.js";
|
|
2
2
|
import { IndexedString } from "./strings.js";
|
|
3
|
-
import type { EncodingNode, EncodingFormat } from "./v1/encoding-values.js";
|
|
4
3
|
export { stringValue } from "./strings.js";
|
|
5
4
|
/** One creator-owned membership list; payload destruction uses the ordinary VM release path. */
|
|
6
5
|
export interface RcOwner {
|
|
@@ -8,6 +7,8 @@ export interface RcOwner {
|
|
|
8
7
|
closed: boolean;
|
|
9
8
|
}
|
|
10
9
|
export interface RcCell {
|
|
10
|
+
managedLease?: import("./v1/managed-budget.js").ManagedLease;
|
|
11
|
+
managedPayload?: number;
|
|
11
12
|
value: VmValue;
|
|
12
13
|
refs: number;
|
|
13
14
|
weak: number;
|
|
@@ -15,11 +16,27 @@ export interface RcCell {
|
|
|
15
16
|
owner: RcOwner | null;
|
|
16
17
|
typeName: string;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
/** The language payload allocation. JS arrays holding view metadata do not
|
|
20
|
+
* determine its capacity or identity. Bytes views retain this complete owner. */
|
|
21
|
+
export interface VectorStorage {
|
|
22
|
+
items: VmValue[];
|
|
23
|
+
capacity: number;
|
|
24
|
+
owners: number;
|
|
25
|
+
bytes: boolean;
|
|
26
|
+
shareOnCopy: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface VmVector {
|
|
29
|
+
kind: "vector";
|
|
30
|
+
elementType: string;
|
|
31
|
+
items: VmValue[];
|
|
32
|
+
storage?: VectorStorage;
|
|
33
|
+
offset?: number;
|
|
34
|
+
/** Normal Vec live length; absent for materialized vectors and byte views. */
|
|
35
|
+
length?: number;
|
|
36
|
+
closed?: boolean;
|
|
37
|
+
prepareMutation?: (capacity: number, detached: boolean, appended?: readonly VmValue[]) => void;
|
|
38
|
+
}
|
|
39
|
+
export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | import("./v1/encoding-storage.js").EncodingOwner | {
|
|
23
40
|
kind: "rc";
|
|
24
41
|
cell: RcCell | null;
|
|
25
42
|
closed: boolean;
|
|
@@ -86,17 +103,14 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
86
103
|
typeId: string;
|
|
87
104
|
shape?: number;
|
|
88
105
|
fields: VmValue[];
|
|
106
|
+
managedCaptures?: readonly number[];
|
|
89
107
|
} | {
|
|
90
108
|
kind: "enum";
|
|
91
109
|
typeId: string;
|
|
92
110
|
shape: number;
|
|
93
111
|
tag: number;
|
|
94
112
|
payload: VmValue[];
|
|
95
|
-
} |
|
|
96
|
-
kind: "vector";
|
|
97
|
-
elementType: string;
|
|
98
|
-
items: VmValue[];
|
|
99
|
-
}
|
|
113
|
+
} | VmVector
|
|
100
114
|
/** A map or set owns its occupied slots. Selected Hash/Eq operations probe
|
|
101
115
|
* the table; tombstones preserve collision chains across removal. */
|
|
102
116
|
| {
|
|
@@ -107,6 +121,9 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
107
121
|
}>;
|
|
108
122
|
capacity?: number;
|
|
109
123
|
tombstones?: Set<number>;
|
|
124
|
+
prepareInsert?: (staged: Extract<VmValue, {
|
|
125
|
+
kind: "map";
|
|
126
|
+
}>, slot: string, key: VmValue, value: VmValue, copyKey: boolean) => void;
|
|
110
127
|
}
|
|
111
128
|
/** A trait object: the vtable the checker selected, and the value it wraps.
|
|
112
129
|
* A `dyn.call` resolves its method through `vtable`, never through the
|
|
@@ -115,6 +132,7 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
115
132
|
kind: "dyn";
|
|
116
133
|
vtable: number;
|
|
117
134
|
value: VmValue;
|
|
135
|
+
managedPayload?: number;
|
|
118
136
|
}
|
|
119
137
|
/** A first-class function reference materialised by `const.function`.
|
|
120
138
|
* `id` is the bytecode function id (e.g. `"fn:my_handler"`).
|
|
@@ -135,10 +153,26 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
|
|
|
135
153
|
}>;
|
|
136
154
|
specialization: number | null;
|
|
137
155
|
environment: VmValue;
|
|
156
|
+
managedCaptures?: readonly number[];
|
|
138
157
|
};
|
|
139
158
|
export declare const UNIT: VmValue;
|
|
140
159
|
export declare function compareUtf8(a: string, b: string): number;
|
|
141
160
|
export declare function cloneValue(value: VmValue): VmValue;
|
|
161
|
+
/** The live range is header metadata; the backing keeps its full capacity. */
|
|
162
|
+
export declare function vectorLength(value: VmVector): number;
|
|
163
|
+
export declare function vectorElement(value: VmVector, index: number): VmValue | undefined;
|
|
164
|
+
/** Materialize a view only for operations that already traverse its elements. */
|
|
165
|
+
export declare function vectorItems(value: VmVector): VmValue[];
|
|
166
|
+
export declare function vectorStorage(value: VmVector): VectorStorage;
|
|
167
|
+
export declare function byteVectorValue(items: VmValue[]): VmVector;
|
|
168
|
+
export declare function sliceByteVector(value: VmVector, start: number, end: number): VmVector;
|
|
169
|
+
/** Establish uniqueness before any mutation, preserving native capacity rules. */
|
|
170
|
+
export declare function mutableVector(value: VmVector, needed?: number, appended?: readonly VmValue[]): VmVector;
|
|
171
|
+
/** Move a normal Vec end in constant time, retaining its allocation identity. */
|
|
172
|
+
export declare function takeVectorEnd(value: VmVector, back: boolean): VmValue;
|
|
173
|
+
/** Consume one handle; only its remaining live elements need destruction. */
|
|
174
|
+
export declare function releaseVector(value: VmVector): VmValue[];
|
|
175
|
+
export declare function clearByteVector(value: VmVector): VmVector;
|
|
142
176
|
export declare function valueFromLiteral(value: JsonValue): VmValue;
|
|
143
177
|
export declare function renderF64(value: number): string;
|
|
144
178
|
export declare function renderStdout(value: VmValue): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stringValue } from "./strings.js";
|
|
2
|
+
import { cloneEncoding } from "./v1/encoding-storage.js";
|
|
2
3
|
export { stringValue } from "./strings.js";
|
|
3
4
|
export const UNIT = { kind: "unit" };
|
|
4
5
|
const UTF8_ENCODER = new TextEncoder();
|
|
@@ -18,7 +19,7 @@ const I64_LITERAL_DECIMAL = /^(?:0|-?[1-9]\d*)$/;
|
|
|
18
19
|
export function cloneValue(value) {
|
|
19
20
|
switch (value.kind) {
|
|
20
21
|
case "encoding":
|
|
21
|
-
return
|
|
22
|
+
return cloneEncoding(value);
|
|
22
23
|
case "rc":
|
|
23
24
|
if (value.closed)
|
|
24
25
|
throw new Error("cannot copy a released Rc");
|
|
@@ -69,6 +70,7 @@ export function cloneValue(value) {
|
|
|
69
70
|
kind: "record",
|
|
70
71
|
typeId: value.typeId,
|
|
71
72
|
shape: value.shape,
|
|
73
|
+
managedCaptures: value.managedCaptures,
|
|
72
74
|
fields: value.fields.map(cloneValue),
|
|
73
75
|
};
|
|
74
76
|
case "enum":
|
|
@@ -80,16 +82,13 @@ export function cloneValue(value) {
|
|
|
80
82
|
payload: value.payload.map(cloneValue),
|
|
81
83
|
};
|
|
82
84
|
case "vector":
|
|
83
|
-
return
|
|
84
|
-
kind: "vector",
|
|
85
|
-
elementType: value.elementType,
|
|
86
|
-
items: value.items.map(cloneValue),
|
|
87
|
-
};
|
|
85
|
+
return cloneVector(value);
|
|
88
86
|
case "dyn":
|
|
89
87
|
return {
|
|
90
88
|
kind: "dyn",
|
|
91
89
|
vtable: value.vtable,
|
|
92
90
|
value: cloneValue(value.value),
|
|
91
|
+
managedPayload: value.managedPayload,
|
|
93
92
|
};
|
|
94
93
|
case "closure":
|
|
95
94
|
return {
|
|
@@ -97,6 +96,7 @@ export function cloneValue(value) {
|
|
|
97
96
|
entries: value.entries,
|
|
98
97
|
specialization: value.specialization,
|
|
99
98
|
environment: cloneValue(value.environment),
|
|
99
|
+
managedCaptures: value.managedCaptures,
|
|
100
100
|
};
|
|
101
101
|
case "map":
|
|
102
102
|
return {
|
|
@@ -110,6 +110,174 @@ export function cloneValue(value) {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
/** The live range is header metadata; the backing keeps its full capacity. */
|
|
114
|
+
export function vectorLength(value) {
|
|
115
|
+
return value.length ?? value.items.length;
|
|
116
|
+
}
|
|
117
|
+
export function vectorElement(value, index) {
|
|
118
|
+
if (index < 0 || index >= vectorLength(value))
|
|
119
|
+
return undefined;
|
|
120
|
+
return value.items[(value.length === undefined ? 0 : (value.offset ?? 0)) + index];
|
|
121
|
+
}
|
|
122
|
+
/** Materialize a view only for operations that already traverse its elements. */
|
|
123
|
+
export function vectorItems(value) {
|
|
124
|
+
return value.length === undefined
|
|
125
|
+
? value.items
|
|
126
|
+
: value.items.slice(value.offset ?? 0, (value.offset ?? 0) + value.length);
|
|
127
|
+
}
|
|
128
|
+
export function vectorStorage(value) {
|
|
129
|
+
if (value.closed)
|
|
130
|
+
throw new Error("cannot read a released vector");
|
|
131
|
+
return (value.storage ??= {
|
|
132
|
+
items: value.items,
|
|
133
|
+
capacity: value.items.length,
|
|
134
|
+
owners: 1,
|
|
135
|
+
bytes: false,
|
|
136
|
+
shareOnCopy: false,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export function byteVectorValue(items) {
|
|
140
|
+
return {
|
|
141
|
+
kind: "vector",
|
|
142
|
+
elementType: "u8",
|
|
143
|
+
items,
|
|
144
|
+
storage: {
|
|
145
|
+
items,
|
|
146
|
+
capacity: items.length ? Math.max(items.length, 16) : 0,
|
|
147
|
+
owners: 1,
|
|
148
|
+
bytes: true,
|
|
149
|
+
shareOnCopy: true,
|
|
150
|
+
},
|
|
151
|
+
offset: 0,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function cloneVector(value) {
|
|
155
|
+
const storage = vectorStorage(value);
|
|
156
|
+
// Native Vec clones discard an empty receiver's spare capacity. Bytes
|
|
157
|
+
// clones retain their allocation even when the visible length is zero.
|
|
158
|
+
if (storage.bytes || (storage.shareOnCopy && vectorLength(value) !== 0)) {
|
|
159
|
+
storage.owners++;
|
|
160
|
+
return { ...value, storage, closed: false };
|
|
161
|
+
}
|
|
162
|
+
const items = vectorItems(value).map(cloneValue);
|
|
163
|
+
return {
|
|
164
|
+
kind: "vector",
|
|
165
|
+
elementType: value.elementType,
|
|
166
|
+
items,
|
|
167
|
+
storage: { ...storage, items, capacity: items.length, owners: 1 },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function sliceByteVector(value, start, end) {
|
|
171
|
+
const storage = vectorStorage(value);
|
|
172
|
+
if (!storage.bytes)
|
|
173
|
+
throw new Error("bytes slice has no bytes storage");
|
|
174
|
+
if (start === end)
|
|
175
|
+
return byteVectorValue([]);
|
|
176
|
+
storage.owners++;
|
|
177
|
+
return {
|
|
178
|
+
kind: "vector",
|
|
179
|
+
elementType: value.elementType,
|
|
180
|
+
items: value.items.slice(start, end),
|
|
181
|
+
storage,
|
|
182
|
+
offset: (value.offset ?? 0) + start,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Establish uniqueness before any mutation, preserving native capacity rules. */
|
|
186
|
+
export function mutableVector(value, needed = vectorLength(value), appended) {
|
|
187
|
+
let storage = vectorStorage(value);
|
|
188
|
+
let capacity = storage.capacity;
|
|
189
|
+
const detached = storage.owners !== 1;
|
|
190
|
+
if (detached && storage.bytes)
|
|
191
|
+
capacity = Math.max(vectorLength(value), 16);
|
|
192
|
+
if (needed > capacity) {
|
|
193
|
+
if (storage.bytes)
|
|
194
|
+
capacity = Math.max(Math.min(capacity * 2, 0xffffffff), needed, 16);
|
|
195
|
+
else {
|
|
196
|
+
capacity = capacity || 4;
|
|
197
|
+
while (capacity < needed)
|
|
198
|
+
capacity *= 2;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!Number.isSafeInteger(capacity) ||
|
|
202
|
+
(storage.bytes && capacity > 0xffffffff))
|
|
203
|
+
throw new Error("vector capacity overflow");
|
|
204
|
+
if (detached || capacity !== storage.capacity || appended?.length)
|
|
205
|
+
value.prepareMutation?.(capacity, detached, appended);
|
|
206
|
+
if (detached) {
|
|
207
|
+
const items = vectorItems(value).map(cloneValue);
|
|
208
|
+
storage.owners--;
|
|
209
|
+
storage = { ...storage, items, capacity, owners: 1 };
|
|
210
|
+
value.storage = storage;
|
|
211
|
+
value.items = items;
|
|
212
|
+
value.offset = 0;
|
|
213
|
+
value.length = undefined;
|
|
214
|
+
}
|
|
215
|
+
else if (!storage.bytes && value.length !== undefined) {
|
|
216
|
+
// A sole remaining view can reclaim its consumed prefix without
|
|
217
|
+
// changing the language allocation's identity or retained capacity.
|
|
218
|
+
if ((value.offset ?? 0) === 0)
|
|
219
|
+
value.items.length = value.length;
|
|
220
|
+
else
|
|
221
|
+
value.items = vectorItems(value);
|
|
222
|
+
storage.items = value.items;
|
|
223
|
+
value.offset = 0;
|
|
224
|
+
value.length = undefined;
|
|
225
|
+
}
|
|
226
|
+
if (capacity !== storage.capacity) {
|
|
227
|
+
storage.capacity = capacity;
|
|
228
|
+
value.offset = 0;
|
|
229
|
+
}
|
|
230
|
+
else if (storage.bytes && (value.offset ?? 0) + needed > capacity) {
|
|
231
|
+
value.offset = 0;
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
/** Move a normal Vec end in constant time, retaining its allocation identity. */
|
|
236
|
+
export function takeVectorEnd(value, back) {
|
|
237
|
+
const storage = vectorStorage(value);
|
|
238
|
+
const length = vectorLength(value);
|
|
239
|
+
if (length === 0)
|
|
240
|
+
throw new Error("cannot take an empty vector end");
|
|
241
|
+
if (storage.bytes) {
|
|
242
|
+
mutableVector(value);
|
|
243
|
+
return (back ? value.items.pop() : value.items.shift());
|
|
244
|
+
}
|
|
245
|
+
const start = value.offset ?? 0;
|
|
246
|
+
const index = start + (back ? length - 1 : 0);
|
|
247
|
+
const item = value.items[index];
|
|
248
|
+
// Managed/affine storage is unique. Clear its moved slot immediately so
|
|
249
|
+
// the backing cannot retain payload references after ownership transfers.
|
|
250
|
+
// Shared callback-free plain bytes remain readable by other headers.
|
|
251
|
+
if (storage.owners === 1)
|
|
252
|
+
value.items[index] = UNIT;
|
|
253
|
+
else if (!storage.shareOnCopy)
|
|
254
|
+
throw new Error("managed vector unexpectedly shares its backing");
|
|
255
|
+
value.offset = start + (back ? 0 : 1);
|
|
256
|
+
value.length = length - 1;
|
|
257
|
+
return item;
|
|
258
|
+
}
|
|
259
|
+
/** Consume one handle; only its remaining live elements need destruction. */
|
|
260
|
+
export function releaseVector(value) {
|
|
261
|
+
if (value.closed)
|
|
262
|
+
return [];
|
|
263
|
+
const storage = vectorStorage(value);
|
|
264
|
+
const items = --storage.owners === 0
|
|
265
|
+
? storage.bytes
|
|
266
|
+
? storage.items
|
|
267
|
+
: vectorItems(value)
|
|
268
|
+
: [];
|
|
269
|
+
value.closed = true;
|
|
270
|
+
return items;
|
|
271
|
+
}
|
|
272
|
+
export function clearByteVector(value) {
|
|
273
|
+
const storage = vectorStorage(value);
|
|
274
|
+
storage.owners--;
|
|
275
|
+
const empty = byteVectorValue([]);
|
|
276
|
+
value.items = empty.items;
|
|
277
|
+
value.storage = empty.storage;
|
|
278
|
+
value.offset = 0;
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
113
281
|
export function valueFromLiteral(value) {
|
|
114
282
|
const taggedI64 = taggedI64Literal(value);
|
|
115
283
|
if (taggedI64 !== null) {
|
|
@@ -258,7 +426,7 @@ export function renderStdout(value) {
|
|
|
258
426
|
payload: value.payload.map((field) => toJsonValue(field)),
|
|
259
427
|
});
|
|
260
428
|
case "vector":
|
|
261
|
-
return canonicalJson(value.
|
|
429
|
+
return canonicalJson(vectorItems(value).map((item) => toJsonValue(item)));
|
|
262
430
|
case "rc":
|
|
263
431
|
return "Rc";
|
|
264
432
|
case "weak":
|
|
@@ -316,7 +484,7 @@ export function toJsonValue(value) {
|
|
|
316
484
|
payload: value.payload.map(toJsonValue),
|
|
317
485
|
};
|
|
318
486
|
case "vector":
|
|
319
|
-
return value.
|
|
487
|
+
return vectorItems(value).map(toJsonValue);
|
|
320
488
|
case "rc":
|
|
321
489
|
return "Rc";
|
|
322
490
|
case "weak":
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hew-lang/sandbox-vm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"hewSource": {
|
|
5
|
-
"commit": "
|
|
6
|
-
"tag": "v0.
|
|
5
|
+
"commit": "75e14a2775f9af8fd77a6a907da277b2de666824",
|
|
6
|
+
"tag": "v0.10.0",
|
|
7
7
|
"dirty": false
|
|
8
8
|
},
|
|
9
9
|
"description": "Deterministic TypeScript interpreter for the Hew educational sandbox",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"cli/"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@hew-lang/wasm": "0.
|
|
24
|
+
"@hew-lang/wasm": "0.10.0"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"hew",
|