@hew-lang/sandbox-vm 0.9.3 → 0.10.0-rc1

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.
@@ -1,7 +1,9 @@
1
- import type { TermV1, VariantShape, RuntimeVariantRole } from "./instructions.generated.js";
2
- export type { Own, BoundaryDecision, TrapName, SpanV1, BoundaryOperand, Edge, ValueDef, CallResult, OpV1, OpName, TermV1, TermName, SwitchArm, CheckedFailure, RequestShapes, ActorProtocol, ActorOperation, RuntimeFamilyEntry, AggregateShape, VariantCase, VariantShape, RuntimeVariantRole, ExternEntry, PlaceDecl, ValueCapability, ValueComponentCapabilities, ClosureShape, VtableShape, FunctionV1, BlockV1, EntryPlan, ActorShape, SupervisorShape, PackageV1, Resource, StructuralRecipe, } from "./instructions.generated.js";
1
+ import type { TermV1, BlockV1, OpV1, VariantShape, RuntimeVariantRole } from "./instructions.generated.js";
2
+ export type { Own, BoundaryDecision, TrapName, SpanV1, BoundaryOperand, Edge, ValueDef, CallResult, OpV1, OpName, TermV1, TermName, SwitchArm, CheckedFailure, RequestShapes, ActorProtocol, ActorOperation, RuntimeFamilyEntry, AggregateShape, VariantCase, VariantShape, RuntimeVariantRole, ExternEntry, PlaceDecl, ValueCapability, ValueComponentCapabilities, ClosureShape, VtableShape, FunctionV1, BlockV1, EntryPlan, ActorShape, SupervisorShape, PackageV1, Resource, StructuralRecipe, ManagedType, } from "./instructions.generated.js";
3
3
  export type Operand = number;
4
4
  export type CallShape = Pick<Extract<TermV1, {
5
5
  op: "call";
6
6
  }>, "args" | "result" | "normal" | "unwind" | "request_shapes">;
7
7
  export declare function runtimeTag(shape: VariantShape, role: RuntimeVariantRole): number;
8
+ /** Static inventory includes a checked operation; execution follows its edge. */
9
+ export declare function staticOperations(block: BlockV1): Iterable<OpV1>;
@@ -7,3 +7,9 @@ export function runtimeTag(shape, role) {
7
7
  throw new Error(`variant shape ${shape.id} has no checked ${role} tag`);
8
8
  return tag;
9
9
  }
10
+ /** Static inventory includes a checked operation; execution follows its edge. */
11
+ export function* staticOperations(block) {
12
+ yield* block.ops;
13
+ if (block.term.op === "materialize")
14
+ yield block.term.operation;
15
+ }
@@ -1,11 +1,22 @@
1
1
  import { type VmValue } from "../values.js";
2
2
  interface PendingRead {
3
- complete(value: VmValue | null, fault: string | null): void;
3
+ complete(value: VmValue | null, fault: string | null, released?: () => void): void;
4
+ }
5
+ interface Envelope {
6
+ value: VmValue;
7
+ release?: () => void;
8
+ }
9
+ interface Receiver {
10
+ owner: object;
11
+ acquire(envelope: Envelope): () => void;
12
+ refused(): void;
4
13
  }
5
14
  export declare class Pipes {
6
15
  private readonly pairs;
7
16
  private readonly halves;
8
17
  private next;
18
+ private managedReceivers;
19
+ get hasManagedReceivers(): boolean;
9
20
  create(id: string, capacity: number): VmValue;
10
21
  freePair(pair: VmValue): void;
11
22
  extract(pair: VmValue, kind: "sink" | "stream"): VmValue;
@@ -14,6 +25,11 @@ export declare class Pipes {
14
25
  cloneSink(value: VmValue): VmValue;
15
26
  isClosed(value: VmValue): boolean;
16
27
  close(value: VmValue, fault?: string | null): VmValue[];
28
+ finishClose(value: VmValue): void;
29
+ /** Called only after ownership acquisition succeeds. The receiving half
30
+ * carries queued envelopes; producer aliases never acquire them. */
31
+ bind(value: VmValue, receiver?: Receiver): void;
32
+ private admit;
17
33
  peerClosed(value: VmValue): boolean;
18
34
  readable(value: VmValue): boolean;
19
35
  observe(value: VmValue, changed: () => void): () => void;
@@ -21,5 +37,12 @@ export declare class Pipes {
21
37
  receive(value: VmValue, park: boolean, complete: PendingRead["complete"]): () => void;
22
38
  private flush;
23
39
  private notify;
40
+ /** Only the unique receiving half owns admitted typed envelopes. */
41
+ managedRetained(value: VmValue): {
42
+ identity: object;
43
+ queued: readonly {
44
+ value: VmValue;
45
+ }[];
46
+ };
24
47
  }
25
48
  export {};
@@ -1,9 +1,14 @@
1
+ import { HeapExceeded } from "./managed-budget.js";
1
2
  /// The VM's one bounded pipe substrate. Selection observes readiness; only
2
3
  /// the winning receive removes an item. Values transfer without a snapshot.
3
4
  export class Pipes {
4
5
  pairs = new Map();
5
6
  halves = new Map();
6
7
  next = 0;
8
+ managedReceivers = 0;
9
+ get hasManagedReceivers() {
10
+ return this.managedReceivers !== 0;
11
+ }
7
12
  create(id, capacity) {
8
13
  this.pairs.set(id, {
9
14
  capacity,
@@ -65,11 +70,58 @@ export class Pipes {
65
70
  }
66
71
  else {
67
72
  half.pipe.readerOpen = false;
68
- discarded.push(...half.pipe.queue.splice(0));
73
+ if (half.pipe.receiver)
74
+ this.managedReceivers--;
75
+ half.pipe.receiver = undefined;
76
+ for (const item of half.pipe.queue.splice(0)) {
77
+ discarded.push(item.value);
78
+ if (item.release)
79
+ (half.retiring ??= []).push(item.release);
80
+ }
69
81
  }
70
82
  this.flush(half.pipe);
71
83
  return discarded;
72
84
  }
85
+ finishClose(value) {
86
+ if (value.kind !== "stream" && value.kind !== "sink")
87
+ return;
88
+ for (const release of this.halves
89
+ .get(value.channelId)
90
+ ?.retiring?.splice(0) ?? [])
91
+ release();
92
+ }
93
+ /** Called only after ownership acquisition succeeds. The receiving half
94
+ * carries queued envelopes; producer aliases never acquire them. */
95
+ bind(value, receiver) {
96
+ const half = this.get(value);
97
+ if (half.kind !== "stream" || half.pipe.receiver?.owner === receiver?.owner)
98
+ return;
99
+ this.managedReceivers += Number(!!receiver) - Number(!!half.pipe.receiver);
100
+ half.pipe.receiver = receiver;
101
+ for (const envelope of half.pipe.queue) {
102
+ const release = receiver?.acquire(envelope);
103
+ envelope.release?.();
104
+ envelope.release = release;
105
+ }
106
+ }
107
+ admit(pipe, value) {
108
+ const envelope = { value };
109
+ try {
110
+ envelope.release = pipe.receiver?.acquire(envelope);
111
+ return envelope;
112
+ }
113
+ catch (error) {
114
+ if (!(error instanceof HeapExceeded))
115
+ throw error;
116
+ // Acquisition is atomic. A refused recipient follows its existing
117
+ // checked failure path while the producer still owns the item.
118
+ pipe.readerOpen = false;
119
+ pipe.fault = "actor heap limit exceeded";
120
+ pipe.receiver?.refused();
121
+ this.flush(pipe);
122
+ return null;
123
+ }
124
+ }
73
125
  peerClosed(value) {
74
126
  return !this.get(value).pipe.readerOpen;
75
127
  }
@@ -94,11 +146,21 @@ export class Pipes {
94
146
  return () => { };
95
147
  }
96
148
  if (pipe.reading.length > 0) {
97
- pipe.reading.shift().complete(item, null);
149
+ const envelope = this.admit(pipe, item);
150
+ if (!envelope) {
151
+ complete(1);
152
+ return () => { };
153
+ }
154
+ pipe.reading.shift().complete(item, null, envelope.release);
98
155
  complete(0);
99
156
  }
100
157
  else if (pipe.queue.length < pipe.capacity) {
101
- pipe.queue.push(item);
158
+ const envelope = this.admit(pipe, item);
159
+ if (!envelope) {
160
+ complete(1);
161
+ return () => { };
162
+ }
163
+ pipe.queue.push(envelope);
102
164
  complete(0);
103
165
  }
104
166
  else if (!park) {
@@ -120,13 +182,21 @@ export class Pipes {
120
182
  receive(value, park, complete) {
121
183
  const pipe = this.get(value).pipe;
122
184
  if (pipe.queue.length > 0) {
123
- complete(pipe.queue.shift(), null);
185
+ const envelope = pipe.queue.shift();
186
+ complete(envelope.value, null, envelope.release);
124
187
  this.flush(pipe);
125
188
  }
126
189
  else if (pipe.sending.length > 0) {
127
190
  const sender = pipe.sending.shift();
128
- complete(sender.value, null);
129
- sender.complete(0);
191
+ const envelope = this.admit(pipe, sender.value);
192
+ if (envelope) {
193
+ complete(sender.value, null, envelope.release);
194
+ sender.complete(0);
195
+ }
196
+ else {
197
+ complete(null, pipe.fault);
198
+ sender.complete(1);
199
+ }
130
200
  }
131
201
  else if (pipe.fault !== null ||
132
202
  pipe.sinks === 0 ||
@@ -155,11 +225,16 @@ export class Pipes {
155
225
  while (pipe.sending.length > 0 &&
156
226
  (pipe.reading.length > 0 || pipe.queue.length < pipe.capacity)) {
157
227
  const sender = pipe.sending.shift();
228
+ const envelope = this.admit(pipe, sender.value);
229
+ if (!envelope) {
230
+ sender.complete(1);
231
+ break;
232
+ }
158
233
  const reader = pipe.reading.shift();
159
234
  if (reader)
160
- reader.complete(sender.value, null);
235
+ reader.complete(sender.value, null, envelope.release);
161
236
  else
162
- pipe.queue.push(sender.value);
237
+ pipe.queue.push(envelope);
163
238
  sender.complete(0);
164
239
  }
165
240
  }
@@ -175,4 +250,12 @@ export class Pipes {
175
250
  for (const observer of [...pipe.observers])
176
251
  observer();
177
252
  }
253
+ /** Only the unique receiving half owns admitted typed envelopes. */
254
+ managedRetained(value) {
255
+ const half = this.get(value);
256
+ return {
257
+ identity: half.kind === "stream" ? half.pipe : half,
258
+ queued: half.open && half.kind === "stream" ? half.pipe.queue : [],
259
+ };
260
+ }
178
261
  }
@@ -27,6 +27,7 @@ export interface ShimHost {
27
27
  writeStdout(text: string): void;
28
28
  writeStderr(text: string): void;
29
29
  prng: Mt19937;
30
+ arenaInstanceId(): bigint;
30
31
  regexPatterns: readonly string[];
31
32
  enumValue(shape: VariantShape, role: RuntimeVariantRole, payload: VmValue[]): VmValue;
32
33
  }
@@ -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, cloneValue, compareUtf8, stringValue, renderStdout, } from "../values.js";
7
+ import { UNIT, byteVectorValue, clearByteVector, mutableVector, 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.
@@ -122,7 +122,6 @@ export function resolveRuntime(entry) {
122
122
  });
123
123
  case "GetBorrow":
124
124
  case "TakeFirst":
125
- case "Append":
126
125
  return refused(`Vector::${entry.detail}`);
127
126
  default:
128
127
  return ordinary(VECTOR_SHIMS[entry.detail]);
@@ -295,13 +294,19 @@ const UNIT_FAMILIES = {
295
294
  host.writeStderr(text(args, 0));
296
295
  return UNIT;
297
296
  },
298
- StringToBytes: (_host, args) => ({
299
- kind: "vector",
300
- elementType: "u8",
301
- items: [...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte))),
302
- }),
297
+ StringToBytes: (_host, args) => byteVectorValue([...indexed(args, 0).encoded()].map((byte) => int(BigInt(byte)))),
303
298
  StringToUppercase: (_host, args) => str(text(args, 0).toUpperCase()),
304
299
  StringToLowercase: (_host, args) => str(text(args, 0).toLowerCase()),
300
+ StreamPipe: (host, args) => {
301
+ const pair = host.newPipe(Number(integer(args, 0)));
302
+ const fields = [
303
+ host.pipes.extract(pair, "sink"),
304
+ host.pipes.extract(pair, "stream"),
305
+ bool(true),
306
+ ];
307
+ host.pipes.freePair(pair);
308
+ return { kind: "record", typeId: "", fields };
309
+ },
305
310
  StreamPairSink: (host, args) => host.pipes.extract(arg(args, 0), "sink"),
306
311
  StreamPairStream: (host, args) => host.pipes.extract(arg(args, 0), "stream"),
307
312
  StreamFromGenerator: (host, args) => host.generatorStream(arg(args, 0)),
@@ -440,7 +445,7 @@ const UNIT_FAMILIES = {
440
445
  // TextDecoder replaces each maximal invalid subsequence with U+FFFD, as
441
446
  // native lossy decoding does.
442
447
  BytesDecodeUtf8Lossy: (_host, args) => str(decodeUtf8(arg(args, 0))),
443
- BytesNew: () => ({ kind: "vector", elementType: "u8", items: [] }),
448
+ BytesNew: () => byteVectorValue([]),
444
449
  BytesIsEmpty: (_host, args) => bool(vec(args, 0).items.length === 0),
445
450
  BytesLen: (_host, args) => int(BigInt(vec(args, 0).items.length)),
446
451
  BytesContains: (_host, args) => {
@@ -455,20 +460,19 @@ const UNIT_FAMILIES = {
455
460
  }
456
461
  return cloneValue(items[at]);
457
462
  },
458
- // A native bytes range shares the buffer; a VM value is never aliased, so
459
- // a copy is observably the same.
463
+ // Nonempty views retain the exact original allocation and payload capacity.
460
464
  BytesSlice: (_host, args) => {
461
465
  const items = vec(args, 0).items;
462
466
  const start = index(args, 1);
463
467
  const end = index(args, 2);
464
468
  checkSliceBounds(start, end, items.length);
465
- return byteVector(items.slice(start, end));
469
+ return sliceByteVector(vec(args, 0), start, end);
466
470
  },
467
471
  BytesSliceFrom: (_host, args) => {
468
472
  const items = vec(args, 0).items;
469
473
  const start = index(args, 1);
470
474
  checkSliceBounds(start, items.length, items.length);
471
- return byteVector(items.slice(start));
475
+ return sliceByteVector(vec(args, 0), start, items.length);
472
476
  },
473
477
  BytesConcat: (_host, args) => byteVector([...vec(args, 0).items, ...vec(args, 1).items]),
474
478
  BytesStartsWith: (_host, args) => bool(runAt(vec(args, 0).items, vec(args, 1).items, 0)),
@@ -480,6 +484,7 @@ const UNIT_FAMILIES = {
480
484
  // A mutating call receives its receiver by `move` and returns it.
481
485
  BytesPush: (_host, args) => {
482
486
  const target = vec(args, 0);
487
+ mutableVector(target, target.items.length + 1);
483
488
  target.items.push(cloneValue(arg(args, 1)));
484
489
  return target;
485
490
  },
@@ -489,13 +494,13 @@ const UNIT_FAMILIES = {
489
494
  if (at < 0 || at >= target.items.length) {
490
495
  throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${target.items.length}`);
491
496
  }
497
+ mutableVector(target);
492
498
  target.items[at] = cloneValue(arg(args, 2));
493
499
  return target;
494
500
  },
495
501
  BytesClear: (_host, args) => {
496
502
  const target = vec(args, 0);
497
- target.items.length = 0;
498
- return target;
503
+ return clearByteVector(target);
499
504
  },
500
505
  BytesGet: (host, args, shape) => {
501
506
  if (!shape) {
@@ -511,7 +516,9 @@ const UNIT_FAMILIES = {
511
516
  // append, paired with the removed byte for a pop.
512
517
  BytesAppend: (_host, args) => {
513
518
  const target = vec(args, 0);
514
- target.items.push(...vec(args, 1).items.map(cloneValue));
519
+ const added = vec(args, 1).items.map(cloneValue);
520
+ mutableVector(target, target.items.length + added.length);
521
+ target.items.push(...added);
515
522
  return target;
516
523
  },
517
524
  BytesPop: (host, args, shape) => {
@@ -519,6 +526,8 @@ const UNIT_FAMILIES = {
519
526
  throw new TypeError("BytesPop names no result shape to build its Option against");
520
527
  }
521
528
  const target = vec(args, 0);
529
+ if (target.items.length)
530
+ mutableVector(target);
522
531
  const last = target.items.pop();
523
532
  return {
524
533
  kind: "record",
@@ -645,9 +654,7 @@ const UNIT_FAMILIES = {
645
654
  ReplyPayloadFree: refused("ReplyPayloadFree"),
646
655
  ReplyWait: refused("ReplyWait"),
647
656
  SelectFirst: refused("SelectFirst"),
648
- StreamChunks: refused("StreamChunks"),
649
657
  StreamForward: refused("StreamForward"),
650
- StreamLines: refused("StreamLines"),
651
658
  StreamNextLayout: refused("StreamNextLayout"),
652
659
  StreamSendLayout: refused("StreamSendLayout"),
653
660
  StreamTake: refused("StreamTake"),
@@ -706,6 +713,7 @@ const VECTOR_SHIMS = {
706
713
  right >= target.items.length) {
707
714
  throw new ShimFault("vector_bounds", "vector swap index out of bounds");
708
715
  }
716
+ mutableVector(target);
709
717
  [target.items[left], target.items[right]] = [
710
718
  target.items[right],
711
719
  target.items[left],
@@ -717,6 +725,7 @@ const VECTOR_SHIMS = {
717
725
  if (target.items.length === 0) {
718
726
  throw new ShimFault("vector_bounds", "pop from empty vector");
719
727
  }
728
+ mutableVector(target);
720
729
  return {
721
730
  kind: "record",
722
731
  typeId: "",
@@ -729,15 +738,26 @@ const VECTOR_SHIMS = {
729
738
  // ownership decision SIR proved, so it moves in as it arrives.
730
739
  Push: (_host, args) => {
731
740
  const target = vec(args, 0);
741
+ mutableVector(target, target.items.length + 1);
732
742
  target.items.push(arg(args, 1));
733
743
  return target;
734
744
  },
745
+ Append: (_host, args) => {
746
+ const target = vec(args, 0);
747
+ const added = [...vec(args, 1).items];
748
+ if (added.length) {
749
+ mutableVector(target, target.items.length + added.length, added);
750
+ target.items.push(...added.map(cloneValue));
751
+ }
752
+ return target;
753
+ },
735
754
  Remove: (_host, args) => {
736
755
  const target = vec(args, 0);
737
756
  const at = index(args, 1);
738
757
  if (at < 0 || at >= target.items.length) {
739
758
  throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${target.items.length}`);
740
759
  }
760
+ mutableVector(target);
741
761
  const [item] = target.items.splice(at, 1);
742
762
  return { kind: "record", typeId: "", fields: [target, item] };
743
763
  },
@@ -765,23 +785,25 @@ const VECTOR_SHIMS = {
765
785
  const start = index(args, 1);
766
786
  const end = index(args, 2);
767
787
  checkSliceBounds(start, end, items.length);
768
- return {
769
- kind: "vector",
770
- elementType: "",
771
- items: items.slice(start, end).map(cloneValue),
772
- };
788
+ return vectorSlice(items.slice(start, end));
773
789
  },
774
790
  SliceFrom: (_host, args) => {
775
791
  const items = vec(args, 0).items;
776
792
  const start = index(args, 1);
777
793
  checkSliceBounds(start, items.length, items.length);
778
- return {
779
- kind: "vector",
780
- elementType: "",
781
- items: items.slice(start).map(cloneValue),
782
- };
794
+ return vectorSlice(items.slice(start));
783
795
  },
784
796
  };
797
+ function vectorSlice(items) {
798
+ const result = {
799
+ kind: "vector",
800
+ elementType: "",
801
+ items: [],
802
+ };
803
+ mutableVector(result, items.length);
804
+ result.items.push(...items.map(cloneValue));
805
+ return result;
806
+ }
785
807
  function sequenceElement(args) {
786
808
  const items = vec(args, 0).items;
787
809
  const at = index(args, 1);
@@ -791,7 +813,7 @@ function sequenceElement(args) {
791
813
  return items[at];
792
814
  }
793
815
  function byteVector(items) {
794
- return { kind: "vector", elementType: "u8", items: items.map(cloneValue) };
816
+ return byteVectorValue(items.map(cloneValue));
795
817
  }
796
818
  /// Whether `run` occurs in `items` starting at `at`.
797
819
  function runAt(items, run, at) {
@@ -1092,6 +1114,10 @@ function saturatingI64(value) {
1092
1114
  }
1093
1115
  // ── externs ─────────────────────────────────────────────────────────────────
1094
1116
  const EXTERN_SHIMS = {
1117
+ hew_arena_instance_id_new: (host) => ({
1118
+ kind: "i64",
1119
+ value: host.arenaInstanceId(),
1120
+ }),
1095
1121
  hew_semaphore_new: (host, args) => host.semaphores.create(integer(args, 0)),
1096
1122
  hew_semaphore_clone: (host, args) => host.semaphores.clone(arg(args, 0)),
1097
1123
  hew_semaphore_try_acquire: (host, args) => bool(host.semaphores.tryAcquire(arg(args, 0))),
@@ -294,7 +294,8 @@ export function admitPackage(pkg, bridges = {}) {
294
294
  };
295
295
  if (actor.exit != null || actor.down != null)
296
296
  return unavailable("actor.local_observation");
297
- if (actor.max_heap_bytes != null)
297
+ if (actor.max_heap_bytes != null &&
298
+ (actor.state_type == null || !pkg.managed_types?.length))
298
299
  return unavailable("actor.heap_limit");
299
300
  }
300
301
  for (const supervisor of pkg.supervisors ?? []) {
@@ -12,6 +12,8 @@ export type ValueRequest = {
12
12
  kind: "release";
13
13
  value: VmValue;
14
14
  onFault?: VmValue;
15
+ retained?: readonly VmValue[];
16
+ retainedType?: number;
15
17
  };
16
18
  export type ValueProgram = Generator<ValueRequest, VmValue, VmValue>;
17
19
  /** Execute the exact checked plan, including every derived component selection.
@@ -25,4 +27,4 @@ export declare function sharedOperation(operation: Extract<RuntimeContinuation,
25
27
  * borrow their owners until every fallible callback has completed. */
26
28
  export declare function collectionOperation(pkg: PackageV1, request: Extract<RuntimeContinuation, {
27
29
  handler: "collection";
28
- }>, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
30
+ }>, args: VmValue[], callbacks: number[], shape: number | null | undefined, elementType?: number): ValueProgram;
@@ -1,4 +1,4 @@
1
- import { UNIT, cloneValue, compareUtf8, } from "../values.js";
1
+ import { UNIT, mutableVector, 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";
@@ -324,17 +324,20 @@ export function* sharedOperation(operation, args, owner, typeName, option) {
324
324
  /** Open-addressed collection operations preserve native callback ordering:
325
325
  * growth hashes occupied slots before probing the incoming key. Staged slots
326
326
  * borrow their owners until every fallible callback has completed. */
327
- export function* collectionOperation(pkg, request, args, callbacks, shape) {
327
+ export function* collectionOperation(pkg, request, args, callbacks, shape, elementType) {
328
328
  const { family, operation } = request;
329
329
  if (family === "Vector") {
330
330
  const receiver = args[0];
331
331
  if (receiver?.kind !== "vector")
332
332
  throw new Error("vector operation has no vector");
333
333
  if (operation === "Clear") {
334
+ mutableVector(receiver);
334
335
  const items = receiver.items.splice(0);
335
336
  yield {
336
337
  kind: "release",
337
338
  value: { kind: "vector", elementType: receiver.elementType, items },
339
+ retained: items,
340
+ retainedType: elementType,
338
341
  };
339
342
  return receiver;
340
343
  }
@@ -344,10 +347,11 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
344
347
  throw new Error("vector mutation has no index");
345
348
  const at = Number(index.value);
346
349
  if (at < 0 || at >= receiver.items.length)
347
- throw new ShimFault("vector_bounds", "vector index out of bounds");
350
+ throw new ShimFault("vector_bounds", `vector index ${at} out of bounds for length ${receiver.items.length}`);
351
+ mutableVector(receiver);
348
352
  const old = receiver.items[at];
349
353
  receiver.items[at] = args[2];
350
- yield { kind: "release", value: old };
354
+ yield { kind: "release", value: old, retainedType: elementType };
351
355
  return receiver;
352
356
  }
353
357
  for (const item of receiver.items) {
@@ -377,6 +381,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
377
381
  receiver.tombstones = new Set();
378
382
  yield {
379
383
  kind: "release",
384
+ retained: entries.flatMap((entry) => [entry.key, entry.value]),
380
385
  value: {
381
386
  kind: "vector",
382
387
  elementType: "",
@@ -389,15 +394,22 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
389
394
  operation === "Values" ||
390
395
  operation === "Elements" ||
391
396
  operation === "Entries") {
392
- return {
397
+ const result = {
393
398
  kind: "vector",
394
399
  elementType: "",
395
- items: [...receiver.entries.values()].map(({ key, value }) => operation === "Values"
396
- ? cloneValue(value)
397
- : operation === "Entries"
398
- ? tuple(cloneValue(key), cloneValue(value))
399
- : cloneValue(key)),
400
+ items: [],
400
401
  };
402
+ const items = [...receiver.entries.values()].map(({ key, value }) => operation === "Values"
403
+ ? cloneValue(value)
404
+ : operation === "Entries"
405
+ ? tuple(cloneValue(key), cloneValue(value))
406
+ : cloneValue(key));
407
+ // Native snapshots push into an initially empty Vec, retaining its normal
408
+ // growth capacity. A later semantic clone may use exact live length.
409
+ mutableVector(result, items.length);
410
+ for (const item of items)
411
+ result.items.push(item);
412
+ return result;
401
413
  }
402
414
  let staged = receiver;
403
415
  if (operation === "Insert" &&
@@ -435,6 +447,7 @@ export function* collectionOperation(pkg, request, args, callbacks, shape) {
435
447
  return cloneValue(entry.value);
436
448
  }
437
449
  if (operation === "Insert") {
450
+ receiver.prepareInsert?.(staged, result.slot, entry?.key ?? args[1], family === "Set" ? UNIT : args[2], family !== "Set" && !entry);
438
451
  receiver.entries = staged.entries;
439
452
  receiver.capacity = staged.capacity;
440
453
  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,25 @@ export interface RcCell {
15
16
  owner: RcOwner | null;
16
17
  typeName: string;
17
18
  }
18
- export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
19
- kind: "encoding";
20
- format: EncodingFormat;
21
- value: EncodingNode | null;
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
+ closed?: boolean;
35
+ prepareMutation?: (capacity: number, detached: boolean, appended?: readonly VmValue[]) => void;
36
+ }
37
+ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | import("./v1/encoding-storage.js").EncodingOwner | {
23
38
  kind: "rc";
24
39
  cell: RcCell | null;
25
40
  closed: boolean;
@@ -86,17 +101,14 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
86
101
  typeId: string;
87
102
  shape?: number;
88
103
  fields: VmValue[];
104
+ managedCaptures?: readonly number[];
89
105
  } | {
90
106
  kind: "enum";
91
107
  typeId: string;
92
108
  shape: number;
93
109
  tag: number;
94
110
  payload: VmValue[];
95
- } | {
96
- kind: "vector";
97
- elementType: string;
98
- items: VmValue[];
99
- }
111
+ } | VmVector
100
112
  /** A map or set owns its occupied slots. Selected Hash/Eq operations probe
101
113
  * the table; tombstones preserve collision chains across removal. */
102
114
  | {
@@ -107,6 +119,9 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
107
119
  }>;
108
120
  capacity?: number;
109
121
  tombstones?: Set<number>;
122
+ prepareInsert?: (staged: Extract<VmValue, {
123
+ kind: "map";
124
+ }>, slot: string, key: VmValue, value: VmValue, copyKey: boolean) => void;
110
125
  }
111
126
  /** A trait object: the vtable the checker selected, and the value it wraps.
112
127
  * A `dyn.call` resolves its method through `vtable`, never through the
@@ -115,6 +130,7 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
115
130
  kind: "dyn";
116
131
  vtable: number;
117
132
  value: VmValue;
133
+ managedPayload?: number;
118
134
  }
119
135
  /** A first-class function reference materialised by `const.function`.
120
136
  * `id` is the bytecode function id (e.g. `"fn:my_handler"`).
@@ -135,10 +151,19 @@ export type VmValue = import("./v1/semaphores.js").SemaphoreHandle | {
135
151
  }>;
136
152
  specialization: number | null;
137
153
  environment: VmValue;
154
+ managedCaptures?: readonly number[];
138
155
  };
139
156
  export declare const UNIT: VmValue;
140
157
  export declare function compareUtf8(a: string, b: string): number;
141
158
  export declare function cloneValue(value: VmValue): VmValue;
159
+ export declare function vectorStorage(value: VmVector): VectorStorage;
160
+ export declare function byteVectorValue(items: VmValue[]): VmVector;
161
+ export declare function sliceByteVector(value: VmVector, start: number, end: number): VmVector;
162
+ /** Establish uniqueness before any mutation, preserving native capacity rules. */
163
+ export declare function mutableVector(value: VmVector, needed?: number, appended?: readonly VmValue[]): VmVector;
164
+ /** Consume one handle; only the last backing owner releases its elements. */
165
+ export declare function releaseVector(value: VmVector): VmValue[];
166
+ export declare function clearByteVector(value: VmVector): VmVector;
142
167
  export declare function valueFromLiteral(value: JsonValue): VmValue;
143
168
  export declare function renderF64(value: number): string;
144
169
  export declare function renderStdout(value: VmValue): string;