@hew-lang/sandbox-vm 0.10.0-rc1 → 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 +4 -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/exec.js +144 -46
- package/dist/interpreter/v1/instructions.generated.d.ts +7 -3
- package/dist/interpreter/v1/managed-budget.js +9 -4
- package/dist/interpreter/v1/package-validator.generated.js +828 -619
- package/dist/interpreter/v1/shims.js +44 -44
- package/dist/interpreter/v1/structural.js +4 -2
- package/dist/interpreter/v1/validate.js +2 -1
- package/dist/interpreter/v1/value-operations.js +11 -10
- package/dist/interpreter/values.d.ts +10 -1
- package/dist/interpreter/values.js +65 -9
- package/package.json +4 -4
|
@@ -4,7 +4,7 @@ import { executeEncoding } from "./encoding-values.js";
|
|
|
4
4
|
import { charProperty, charCase } from "../unicode.js";
|
|
5
5
|
/// Runtime implementations and their host execution registration. Admission and
|
|
6
6
|
/// execution consume the same ordinary, resumable or refused disposition.
|
|
7
|
-
import { UNIT, byteVectorValue, clearByteVector, mutableVector, sliceByteVector, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
|
|
7
|
+
import { UNIT, byteVectorValue, clearByteVector, mutableVector, vectorItems, vectorLength, vectorElement, takeVectorEnd, sliceByteVector, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
|
|
8
8
|
import { seededMt } from "./mt19937.js";
|
|
9
9
|
/// A language-visible fault raised inside a shim. The executor routes it to the
|
|
10
10
|
/// call's `unwind` edge, or ends the program when the call names none.
|
|
@@ -446,14 +446,14 @@ const UNIT_FAMILIES = {
|
|
|
446
446
|
// native lossy decoding does.
|
|
447
447
|
BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
|
|
448
448
|
BytesNew: () => byteVectorValue([]),
|
|
449
|
-
BytesIsEmpty: (_host, args) => bool(vec(args, 0)
|
|
450
|
-
BytesLen: (_host, args) => int(BigInt(vec(args, 0)
|
|
449
|
+
BytesIsEmpty: (_host, args) => bool(vectorLength(vec(args, 0)) === 0),
|
|
450
|
+
BytesLen: (_host, args) => int(BigInt(vectorLength(vec(args, 0)))),
|
|
451
451
|
BytesContains: (_host, args) => {
|
|
452
452
|
const byte = integer(args, 1);
|
|
453
|
-
return bool(vec(args, 0).
|
|
453
|
+
return bool(vectorItems(vec(args, 0)).some((item) => integer([item], 0) === byte));
|
|
454
454
|
},
|
|
455
455
|
BytesIndex: (_host, args) => {
|
|
456
|
-
const items = vec(args, 0)
|
|
456
|
+
const items = vectorItems(vec(args, 0));
|
|
457
457
|
const at = index(args, 1);
|
|
458
458
|
if (at < 0 || at >= items.length) {
|
|
459
459
|
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${items.length}`);
|
|
@@ -462,37 +462,37 @@ const UNIT_FAMILIES = {
|
|
|
462
462
|
},
|
|
463
463
|
// Nonempty views retain the exact original allocation and payload capacity.
|
|
464
464
|
BytesSlice: (_host, args) => {
|
|
465
|
-
const items = vec(args, 0)
|
|
465
|
+
const items = vectorItems(vec(args, 0));
|
|
466
466
|
const start = index(args, 1);
|
|
467
467
|
const end = index(args, 2);
|
|
468
468
|
checkSliceBounds(start, end, items.length);
|
|
469
469
|
return sliceByteVector(vec(args, 0), start, end);
|
|
470
470
|
},
|
|
471
471
|
BytesSliceFrom: (_host, args) => {
|
|
472
|
-
const items = vec(args, 0)
|
|
472
|
+
const items = vectorItems(vec(args, 0));
|
|
473
473
|
const start = index(args, 1);
|
|
474
474
|
checkSliceBounds(start, items.length, items.length);
|
|
475
475
|
return sliceByteVector(vec(args, 0), start, items.length);
|
|
476
476
|
},
|
|
477
|
-
BytesConcat: (_host, args) => byteVector([...vec(args, 0)
|
|
478
|
-
BytesStartsWith: (_host, args) => bool(runAt(vec(args, 0)
|
|
477
|
+
BytesConcat: (_host, args) => byteVector([...vectorItems(vec(args, 0)), ...vectorItems(vec(args, 1))]),
|
|
478
|
+
BytesStartsWith: (_host, args) => bool(runAt(vectorItems(vec(args, 0)), vectorItems(vec(args, 1)), 0)),
|
|
479
479
|
BytesEndsWith: (_host, args) => {
|
|
480
|
-
const items = vec(args, 0)
|
|
481
|
-
const run = vec(args, 1)
|
|
480
|
+
const items = vectorItems(vec(args, 0));
|
|
481
|
+
const run = vectorItems(vec(args, 1));
|
|
482
482
|
return bool(runAt(items, run, items.length - run.length));
|
|
483
483
|
},
|
|
484
484
|
// A mutating call receives its receiver by `move` and returns it.
|
|
485
485
|
BytesPush: (_host, args) => {
|
|
486
486
|
const target = vec(args, 0);
|
|
487
|
-
mutableVector(target, target
|
|
487
|
+
mutableVector(target, vectorLength(target) + 1);
|
|
488
488
|
target.items.push(cloneValue(arg(args, 1)));
|
|
489
489
|
return target;
|
|
490
490
|
},
|
|
491
491
|
BytesSet: (_host, args) => {
|
|
492
492
|
const target = vec(args, 0);
|
|
493
493
|
const at = index(args, 1);
|
|
494
|
-
if (at < 0 || at >= target
|
|
495
|
-
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${target
|
|
494
|
+
if (at < 0 || at >= vectorLength(target)) {
|
|
495
|
+
throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${vectorLength(target)}`);
|
|
496
496
|
}
|
|
497
497
|
mutableVector(target);
|
|
498
498
|
target.items[at] = cloneValue(arg(args, 2));
|
|
@@ -506,7 +506,7 @@ const UNIT_FAMILIES = {
|
|
|
506
506
|
if (!shape) {
|
|
507
507
|
throw new TypeError("BytesGet names no result shape to build its Option against");
|
|
508
508
|
}
|
|
509
|
-
const items = vec(args, 0)
|
|
509
|
+
const items = vectorItems(vec(args, 0));
|
|
510
510
|
const at = index(args, 1);
|
|
511
511
|
return at >= 0 && at < items.length
|
|
512
512
|
? host.enumValue(shape, "OptionSome", [cloneValue(items[at])])
|
|
@@ -516,8 +516,8 @@ const UNIT_FAMILIES = {
|
|
|
516
516
|
// append, paired with the removed byte for a pop.
|
|
517
517
|
BytesAppend: (_host, args) => {
|
|
518
518
|
const target = vec(args, 0);
|
|
519
|
-
const added = vec(args, 1).
|
|
520
|
-
mutableVector(target, target
|
|
519
|
+
const added = vectorItems(vec(args, 1)).map(cloneValue);
|
|
520
|
+
mutableVector(target, vectorLength(target) + added.length);
|
|
521
521
|
target.items.push(...added);
|
|
522
522
|
return target;
|
|
523
523
|
},
|
|
@@ -526,7 +526,7 @@ const UNIT_FAMILIES = {
|
|
|
526
526
|
throw new TypeError("BytesPop names no result shape to build its Option against");
|
|
527
527
|
}
|
|
528
528
|
const target = vec(args, 0);
|
|
529
|
-
if (target
|
|
529
|
+
if (vectorLength(target))
|
|
530
530
|
mutableVector(target);
|
|
531
531
|
const last = target.items.pop();
|
|
532
532
|
return {
|
|
@@ -708,9 +708,9 @@ const VECTOR_SHIMS = {
|
|
|
708
708
|
const left = index(args, 1);
|
|
709
709
|
const right = index(args, 2);
|
|
710
710
|
if (left < 0 ||
|
|
711
|
-
left >= target
|
|
711
|
+
left >= vectorLength(target) ||
|
|
712
712
|
right < 0 ||
|
|
713
|
-
right >= target
|
|
713
|
+
right >= vectorLength(target)) {
|
|
714
714
|
throw new ShimFault("vector_bounds", "vector swap index out of bounds");
|
|
715
715
|
}
|
|
716
716
|
mutableVector(target);
|
|
@@ -722,14 +722,13 @@ const VECTOR_SHIMS = {
|
|
|
722
722
|
},
|
|
723
723
|
Pop: (_host, args) => {
|
|
724
724
|
const target = vec(args, 0);
|
|
725
|
-
if (target
|
|
725
|
+
if (vectorLength(target) === 0) {
|
|
726
726
|
throw new ShimFault("vector_bounds", "pop from empty vector");
|
|
727
727
|
}
|
|
728
|
-
mutableVector(target);
|
|
729
728
|
return {
|
|
730
729
|
kind: "record",
|
|
731
730
|
typeId: "",
|
|
732
|
-
fields: [target, target
|
|
731
|
+
fields: [target, takeVectorEnd(target, true)],
|
|
733
732
|
};
|
|
734
733
|
},
|
|
735
734
|
New: () => ({ kind: "vector", elementType: "", items: [] }),
|
|
@@ -738,15 +737,15 @@ const VECTOR_SHIMS = {
|
|
|
738
737
|
// ownership decision SIR proved, so it moves in as it arrives.
|
|
739
738
|
Push: (_host, args) => {
|
|
740
739
|
const target = vec(args, 0);
|
|
741
|
-
mutableVector(target, target
|
|
740
|
+
mutableVector(target, vectorLength(target) + 1);
|
|
742
741
|
target.items.push(arg(args, 1));
|
|
743
742
|
return target;
|
|
744
743
|
},
|
|
745
744
|
Append: (_host, args) => {
|
|
746
745
|
const target = vec(args, 0);
|
|
747
|
-
const added = [...vec(args, 1)
|
|
746
|
+
const added = [...vectorItems(vec(args, 1))];
|
|
748
747
|
if (added.length) {
|
|
749
|
-
mutableVector(target, target
|
|
748
|
+
mutableVector(target, vectorLength(target) + added.length, added);
|
|
750
749
|
target.items.push(...added.map(cloneValue));
|
|
751
750
|
}
|
|
752
751
|
return target;
|
|
@@ -754,41 +753,41 @@ const VECTOR_SHIMS = {
|
|
|
754
753
|
Remove: (_host, args) => {
|
|
755
754
|
const target = vec(args, 0);
|
|
756
755
|
const at = index(args, 1);
|
|
757
|
-
if (at < 0 || at >= target
|
|
758
|
-
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${target
|
|
756
|
+
if (at < 0 || at >= vectorLength(target)) {
|
|
757
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${vectorLength(target)}`);
|
|
759
758
|
}
|
|
760
|
-
|
|
761
|
-
|
|
759
|
+
const item = at === 0
|
|
760
|
+
? takeVectorEnd(target, false)
|
|
761
|
+
: mutableVector(target).items.splice(at, 1)[0];
|
|
762
762
|
return { kind: "record", typeId: "", fields: [target, item] };
|
|
763
763
|
},
|
|
764
764
|
Get: (host, args, shape) => {
|
|
765
765
|
if (!shape) {
|
|
766
766
|
throw new TypeError("Vector::Get names no result shape to build its Option against");
|
|
767
767
|
}
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
? host.enumValue(shape, "OptionSome", [cloneValue(items[at])])
|
|
768
|
+
const item = vectorElement(vec(args, 0), index(args, 1));
|
|
769
|
+
return item
|
|
770
|
+
? host.enumValue(shape, "OptionSome", [cloneValue(item)])
|
|
772
771
|
: host.enumValue(shape, "OptionNone", []);
|
|
773
772
|
},
|
|
774
773
|
Index: (_host, args) => cloneValue(sequenceElement(args)),
|
|
775
774
|
IndexBorrow: (_host, args) => sequenceElement(args),
|
|
776
|
-
Len: (_host, args) => int(BigInt(vec(args, 0)
|
|
775
|
+
Len: (_host, args) => int(BigInt(vectorLength(vec(args, 0)))),
|
|
777
776
|
Join: (_host, args) => {
|
|
778
|
-
const items = vec(args, 0)
|
|
777
|
+
const items = vectorItems(vec(args, 0));
|
|
779
778
|
return str(items
|
|
780
779
|
.map((_, at) => indexed(items, at).value)
|
|
781
780
|
.join(indexed(args, 1).value));
|
|
782
781
|
},
|
|
783
782
|
Slice: (_host, args) => {
|
|
784
|
-
const items = vec(args, 0)
|
|
783
|
+
const items = vectorItems(vec(args, 0));
|
|
785
784
|
const start = index(args, 1);
|
|
786
785
|
const end = index(args, 2);
|
|
787
786
|
checkSliceBounds(start, end, items.length);
|
|
788
787
|
return vectorSlice(items.slice(start, end));
|
|
789
788
|
},
|
|
790
789
|
SliceFrom: (_host, args) => {
|
|
791
|
-
const items = vec(args, 0)
|
|
790
|
+
const items = vectorItems(vec(args, 0));
|
|
792
791
|
const start = index(args, 1);
|
|
793
792
|
checkSliceBounds(start, items.length, items.length);
|
|
794
793
|
return vectorSlice(items.slice(start));
|
|
@@ -805,12 +804,13 @@ function vectorSlice(items) {
|
|
|
805
804
|
return result;
|
|
806
805
|
}
|
|
807
806
|
function sequenceElement(args) {
|
|
808
|
-
const
|
|
807
|
+
const target = vec(args, 0);
|
|
809
808
|
const at = index(args, 1);
|
|
810
|
-
|
|
811
|
-
|
|
809
|
+
const item = vectorElement(target, at);
|
|
810
|
+
if (!item) {
|
|
811
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${vectorLength(target)}`);
|
|
812
812
|
}
|
|
813
|
-
return
|
|
813
|
+
return item;
|
|
814
814
|
}
|
|
815
815
|
function byteVector(items) {
|
|
816
816
|
return byteVectorValue(items.map(cloneValue));
|
|
@@ -1230,7 +1230,7 @@ const EXTERN_SHIMS = {
|
|
|
1230
1230
|
export function decodeUtf8Checked(value) {
|
|
1231
1231
|
if (value.kind !== "vector")
|
|
1232
1232
|
throw new TypeError("UTF-8 input must be bytes");
|
|
1233
|
-
const bytes = Uint8Array.from(value.
|
|
1233
|
+
const bytes = Uint8Array.from(vectorItems(value).map((item) => Number(integer([item], 0))));
|
|
1234
1234
|
let at = 0;
|
|
1235
1235
|
while (at < bytes.length) {
|
|
1236
1236
|
const first = bytes[at];
|
|
@@ -1276,7 +1276,7 @@ function decodeUtf8(value) {
|
|
|
1276
1276
|
if (value.kind !== "vector") {
|
|
1277
1277
|
return renderStdout(value);
|
|
1278
1278
|
}
|
|
1279
|
-
return new TextDecoder().decode(Uint8Array.from(value.
|
|
1279
|
+
return new TextDecoder().decode(Uint8Array.from(vectorItems(value).map((item) => item.kind === "i64" ? Number(item.value) : 0)));
|
|
1280
1280
|
}
|
|
1281
1281
|
function compileRegex(host, args) {
|
|
1282
1282
|
const source = pattern(host, arg(args, 0));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderStdout } from "../values.js";
|
|
1
|
+
import { vectorItems, renderStdout } from "../values.js";
|
|
2
2
|
/** A borrowed traversal. Display callbacks can suspend, so their callers retain
|
|
3
3
|
* this stack and resume it with the returned string. No later sibling is read
|
|
4
4
|
* after a callback fails. */
|
|
@@ -49,7 +49,9 @@ export class StructuralRenderer {
|
|
|
49
49
|
case "vector": {
|
|
50
50
|
if (value.kind !== "vector")
|
|
51
51
|
throw new Error("structural vector has no elements");
|
|
52
|
-
this.sequence("[", "]", value.
|
|
52
|
+
this.sequence("[", "]", vectorItems(value).map((value) => [
|
|
53
|
+
{ value, recipe: recipe.members[0] },
|
|
54
|
+
]));
|
|
53
55
|
break;
|
|
54
56
|
}
|
|
55
57
|
case "map": {
|
|
@@ -210,7 +210,8 @@ export function admitPackage(pkg, bridges = {}) {
|
|
|
210
210
|
const supervisor = pkg.supervisors?.find((layout) => layout.id === id);
|
|
211
211
|
const config = term.args.length -
|
|
212
212
|
(supervisor?.children.some((child) => child.is_pool) ? 1 : 0) -
|
|
213
|
-
|
|
213
|
+
2 *
|
|
214
|
+
(supervisor?.children.filter((child) => child.backoff).length ?? 0);
|
|
214
215
|
if (!supervisor ||
|
|
215
216
|
config < 0 ||
|
|
216
217
|
supervisor.children.some((child) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UNIT, mutableVector, 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
|
}
|
|
@@ -346,15 +347,15 @@ export function* collectionOperation(pkg, request, args, callbacks, shape, eleme
|
|
|
346
347
|
if (index?.kind !== "i64")
|
|
347
348
|
throw new Error("vector mutation has no index");
|
|
348
349
|
const at = Number(index.value);
|
|
349
|
-
if (at < 0 || at >= receiver
|
|
350
|
-
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${receiver
|
|
350
|
+
if (at < 0 || at >= vectorLength(receiver))
|
|
351
|
+
throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${vectorLength(receiver)}`);
|
|
351
352
|
mutableVector(receiver);
|
|
352
353
|
const old = receiver.items[at];
|
|
353
354
|
receiver.items[at] = args[2];
|
|
354
355
|
yield { kind: "release", value: old, retainedType: elementType };
|
|
355
356
|
return receiver;
|
|
356
357
|
}
|
|
357
|
-
for (const item of receiver
|
|
358
|
+
for (const item of vectorItems(receiver)) {
|
|
358
359
|
const equal = yield* selectedValue(pkg, callbacks[0], [item, args[1]]);
|
|
359
360
|
if (equal.kind !== "bool")
|
|
360
361
|
throw new Error("Eq returned a non-boolean value");
|
|
@@ -31,6 +31,8 @@ export interface VmVector {
|
|
|
31
31
|
items: VmValue[];
|
|
32
32
|
storage?: VectorStorage;
|
|
33
33
|
offset?: number;
|
|
34
|
+
/** Normal Vec live length; absent for materialized vectors and byte views. */
|
|
35
|
+
length?: number;
|
|
34
36
|
closed?: boolean;
|
|
35
37
|
prepareMutation?: (capacity: number, detached: boolean, appended?: readonly VmValue[]) => void;
|
|
36
38
|
}
|
|
@@ -156,12 +158,19 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | import("./v
|
|
|
156
158
|
export declare const UNIT: VmValue;
|
|
157
159
|
export declare function compareUtf8(a: string, b: string): number;
|
|
158
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[];
|
|
159
166
|
export declare function vectorStorage(value: VmVector): VectorStorage;
|
|
160
167
|
export declare function byteVectorValue(items: VmValue[]): VmVector;
|
|
161
168
|
export declare function sliceByteVector(value: VmVector, start: number, end: number): VmVector;
|
|
162
169
|
/** Establish uniqueness before any mutation, preserving native capacity rules. */
|
|
163
170
|
export declare function mutableVector(value: VmVector, needed?: number, appended?: readonly VmValue[]): VmVector;
|
|
164
|
-
/**
|
|
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. */
|
|
165
174
|
export declare function releaseVector(value: VmVector): VmValue[];
|
|
166
175
|
export declare function clearByteVector(value: VmVector): VmVector;
|
|
167
176
|
export declare function valueFromLiteral(value: JsonValue): VmValue;
|
|
@@ -110,6 +110,21 @@ 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
|
+
}
|
|
113
128
|
export function vectorStorage(value) {
|
|
114
129
|
if (value.closed)
|
|
115
130
|
throw new Error("cannot read a released vector");
|
|
@@ -140,11 +155,11 @@ function cloneVector(value) {
|
|
|
140
155
|
const storage = vectorStorage(value);
|
|
141
156
|
// Native Vec clones discard an empty receiver's spare capacity. Bytes
|
|
142
157
|
// clones retain their allocation even when the visible length is zero.
|
|
143
|
-
if (storage.bytes || (storage.shareOnCopy && value
|
|
158
|
+
if (storage.bytes || (storage.shareOnCopy && vectorLength(value) !== 0)) {
|
|
144
159
|
storage.owners++;
|
|
145
160
|
return { ...value, storage, closed: false };
|
|
146
161
|
}
|
|
147
|
-
const items = value.
|
|
162
|
+
const items = vectorItems(value).map(cloneValue);
|
|
148
163
|
return {
|
|
149
164
|
kind: "vector",
|
|
150
165
|
elementType: value.elementType,
|
|
@@ -168,12 +183,12 @@ export function sliceByteVector(value, start, end) {
|
|
|
168
183
|
};
|
|
169
184
|
}
|
|
170
185
|
/** Establish uniqueness before any mutation, preserving native capacity rules. */
|
|
171
|
-
export function mutableVector(value, needed = value
|
|
186
|
+
export function mutableVector(value, needed = vectorLength(value), appended) {
|
|
172
187
|
let storage = vectorStorage(value);
|
|
173
188
|
let capacity = storage.capacity;
|
|
174
189
|
const detached = storage.owners !== 1;
|
|
175
190
|
if (detached && storage.bytes)
|
|
176
|
-
capacity = Math.max(value
|
|
191
|
+
capacity = Math.max(vectorLength(value), 16);
|
|
177
192
|
if (needed > capacity) {
|
|
178
193
|
if (storage.bytes)
|
|
179
194
|
capacity = Math.max(Math.min(capacity * 2, 0xffffffff), needed, 16);
|
|
@@ -189,12 +204,24 @@ export function mutableVector(value, needed = value.items.length, appended) {
|
|
|
189
204
|
if (detached || capacity !== storage.capacity || appended?.length)
|
|
190
205
|
value.prepareMutation?.(capacity, detached, appended);
|
|
191
206
|
if (detached) {
|
|
192
|
-
const items = value.
|
|
207
|
+
const items = vectorItems(value).map(cloneValue);
|
|
193
208
|
storage.owners--;
|
|
194
209
|
storage = { ...storage, items, capacity, owners: 1 };
|
|
195
210
|
value.storage = storage;
|
|
196
211
|
value.items = items;
|
|
197
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;
|
|
198
225
|
}
|
|
199
226
|
if (capacity !== storage.capacity) {
|
|
200
227
|
storage.capacity = capacity;
|
|
@@ -205,13 +232,42 @@ export function mutableVector(value, needed = value.items.length, appended) {
|
|
|
205
232
|
}
|
|
206
233
|
return value;
|
|
207
234
|
}
|
|
208
|
-
/**
|
|
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. */
|
|
209
260
|
export function releaseVector(value) {
|
|
210
261
|
if (value.closed)
|
|
211
262
|
return [];
|
|
212
263
|
const storage = vectorStorage(value);
|
|
264
|
+
const items = --storage.owners === 0
|
|
265
|
+
? storage.bytes
|
|
266
|
+
? storage.items
|
|
267
|
+
: vectorItems(value)
|
|
268
|
+
: [];
|
|
213
269
|
value.closed = true;
|
|
214
|
-
return
|
|
270
|
+
return items;
|
|
215
271
|
}
|
|
216
272
|
export function clearByteVector(value) {
|
|
217
273
|
const storage = vectorStorage(value);
|
|
@@ -370,7 +426,7 @@ export function renderStdout(value) {
|
|
|
370
426
|
payload: value.payload.map((field) => toJsonValue(field)),
|
|
371
427
|
});
|
|
372
428
|
case "vector":
|
|
373
|
-
return canonicalJson(value.
|
|
429
|
+
return canonicalJson(vectorItems(value).map((item) => toJsonValue(item)));
|
|
374
430
|
case "rc":
|
|
375
431
|
return "Rc";
|
|
376
432
|
case "weak":
|
|
@@ -428,7 +484,7 @@ export function toJsonValue(value) {
|
|
|
428
484
|
payload: value.payload.map(toJsonValue),
|
|
429
485
|
};
|
|
430
486
|
case "vector":
|
|
431
|
-
return value.
|
|
487
|
+
return vectorItems(value).map(toJsonValue);
|
|
432
488
|
case "rc":
|
|
433
489
|
return "Rc";
|
|
434
490
|
case "weak":
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hew-lang/sandbox-vm",
|
|
3
|
-
"version": "0.10.0
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"hewSource": {
|
|
5
|
-
"commit": "
|
|
6
|
-
"tag": "v0.10.0
|
|
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.10.0
|
|
24
|
+
"@hew-lang/wasm": "0.10.0"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"hew",
|