@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.
@@ -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).items.length === 0),
450
- BytesLen: (_host, args) => int(BigInt(vec(args, 0).items.length)),
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).items.some((item) => integer([item], 0) === byte));
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).items;
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).items;
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).items;
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).items, ...vec(args, 1).items]),
478
- BytesStartsWith: (_host, args) => bool(runAt(vec(args, 0).items, vec(args, 1).items, 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).items;
481
- const run = vec(args, 1).items;
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.items.length + 1);
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.items.length) {
495
- throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${target.items.length}`);
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).items;
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).items.map(cloneValue);
520
- mutableVector(target, target.items.length + added.length);
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.items.length)
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.items.length ||
711
+ left >= vectorLength(target) ||
712
712
  right < 0 ||
713
- right >= target.items.length) {
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.items.length === 0) {
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.items.pop()],
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.items.length + 1);
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).items];
746
+ const added = [...vectorItems(vec(args, 1))];
748
747
  if (added.length) {
749
- mutableVector(target, target.items.length + added.length, added);
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.items.length) {
758
- throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${target.items.length}`);
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
- mutableVector(target);
761
- const [item] = target.items.splice(at, 1);
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 items = vec(args, 0).items;
769
- const at = index(args, 1);
770
- return at >= 0 && at < items.length
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).items.length)),
775
+ Len: (_host, args) => int(BigInt(vectorLength(vec(args, 0)))),
777
776
  Join: (_host, args) => {
778
- const items = vec(args, 0).items;
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).items;
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).items;
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 items = vec(args, 0).items;
807
+ const target = vec(args, 0);
809
808
  const at = index(args, 1);
810
- if (at < 0 || at >= items.length) {
811
- throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${items.length}`);
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 items[at];
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.items.map((item) => Number(integer([item], 0))));
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.items.map((item) => (item.kind === "i64" ? Number(item.value) : 0))));
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.items.map((value) => [{ value, recipe: recipe.members[0] }]));
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
- (supervisor?.backoff ? 2 : 0);
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.items.map((value) => Number(bits(value, "u8")));
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.items.length &&
118
- bytes.every((value, i) => BigInt(value) === bits(right.items[i], "u8")));
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.items;
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" || left.items.length !== right.items.length)
163
+ if (right?.kind !== "vector" ||
164
+ vectorLength(left) !== vectorLength(right))
164
165
  return boolean(false);
165
- others = right.items;
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.items.length)
350
- throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${receiver.items.length}`);
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.items) {
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
- /** Consume one handle; only the last backing owner releases its elements. */
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.items.length !== 0)) {
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.items.map(cloneValue);
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.items.length, appended) {
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.items.length, 16);
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.items.map(cloneValue);
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
- /** Consume one handle; only the last backing owner releases its elements. */
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 --storage.owners === 0 ? storage.items : [];
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.items.map((item) => toJsonValue(item)));
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.items.map(toJsonValue);
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-rc1",
3
+ "version": "0.10.0",
4
4
  "hewSource": {
5
- "commit": "5672a4d39318d99785a863cd5002d33952eb269e",
6
- "tag": "v0.10.0-rc1",
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-rc1"
24
+ "@hew-lang/wasm": "0.10.0"
25
25
  },
26
26
  "keywords": [
27
27
  "hew",