@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.
@@ -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, 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.
@@ -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,68 +445,68 @@ 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: [] }),
444
- BytesIsEmpty: (_host, args) => bool(vec(args, 0).items.length === 0),
445
- BytesLen: (_host, args) => int(BigInt(vec(args, 0).items.length)),
448
+ BytesNew: () => byteVectorValue([]),
449
+ BytesIsEmpty: (_host, args) => bool(vectorLength(vec(args, 0)) === 0),
450
+ BytesLen: (_host, args) => int(BigInt(vectorLength(vec(args, 0)))),
446
451
  BytesContains: (_host, args) => {
447
452
  const byte = integer(args, 1);
448
- 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));
449
454
  },
450
455
  BytesIndex: (_host, args) => {
451
- const items = vec(args, 0).items;
456
+ const items = vectorItems(vec(args, 0));
452
457
  const at = index(args, 1);
453
458
  if (at < 0 || at >= items.length) {
454
459
  throw new ShimFault("vector_bounds", `bytes index ${at} out of bounds for length ${items.length}`);
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
- const items = vec(args, 0).items;
465
+ const items = vectorItems(vec(args, 0));
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
- const items = vec(args, 0).items;
472
+ const items = vectorItems(vec(args, 0));
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
- BytesConcat: (_host, args) => byteVector([...vec(args, 0).items, ...vec(args, 1).items]),
474
- 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)),
475
479
  BytesEndsWith: (_host, args) => {
476
- const items = vec(args, 0).items;
477
- const run = vec(args, 1).items;
480
+ const items = vectorItems(vec(args, 0));
481
+ const run = vectorItems(vec(args, 1));
478
482
  return bool(runAt(items, run, items.length - run.length));
479
483
  },
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, vectorLength(target) + 1);
483
488
  target.items.push(cloneValue(arg(args, 1)));
484
489
  return target;
485
490
  },
486
491
  BytesSet: (_host, args) => {
487
492
  const target = vec(args, 0);
488
493
  const at = index(args, 1);
489
- if (at < 0 || at >= target.items.length) {
490
- 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)}`);
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) {
502
507
  throw new TypeError("BytesGet names no result shape to build its Option against");
503
508
  }
504
- const items = vec(args, 0).items;
509
+ const items = vectorItems(vec(args, 0));
505
510
  const at = index(args, 1);
506
511
  return at >= 0 && at < items.length
507
512
  ? host.enumValue(shape, "OptionSome", [cloneValue(items[at])])
@@ -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 = vectorItems(vec(args, 1)).map(cloneValue);
520
+ mutableVector(target, vectorLength(target) + 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 (vectorLength(target))
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"),
@@ -701,11 +708,12 @@ const VECTOR_SHIMS = {
701
708
  const left = index(args, 1);
702
709
  const right = index(args, 2);
703
710
  if (left < 0 ||
704
- left >= target.items.length ||
711
+ left >= vectorLength(target) ||
705
712
  right < 0 ||
706
- right >= target.items.length) {
713
+ right >= vectorLength(target)) {
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],
@@ -714,13 +722,13 @@ const VECTOR_SHIMS = {
714
722
  },
715
723
  Pop: (_host, args) => {
716
724
  const target = vec(args, 0);
717
- if (target.items.length === 0) {
725
+ if (vectorLength(target) === 0) {
718
726
  throw new ShimFault("vector_bounds", "pop from empty vector");
719
727
  }
720
728
  return {
721
729
  kind: "record",
722
730
  typeId: "",
723
- fields: [target, target.items.pop()],
731
+ fields: [target, takeVectorEnd(target, true)],
724
732
  };
725
733
  },
726
734
  New: () => ({ kind: "vector", elementType: "", items: [] }),
@@ -729,69 +737,83 @@ const VECTOR_SHIMS = {
729
737
  // ownership decision SIR proved, so it moves in as it arrives.
730
738
  Push: (_host, args) => {
731
739
  const target = vec(args, 0);
740
+ mutableVector(target, vectorLength(target) + 1);
732
741
  target.items.push(arg(args, 1));
733
742
  return target;
734
743
  },
744
+ Append: (_host, args) => {
745
+ const target = vec(args, 0);
746
+ const added = [...vectorItems(vec(args, 1))];
747
+ if (added.length) {
748
+ mutableVector(target, vectorLength(target) + added.length, added);
749
+ target.items.push(...added.map(cloneValue));
750
+ }
751
+ return target;
752
+ },
735
753
  Remove: (_host, args) => {
736
754
  const target = vec(args, 0);
737
755
  const at = index(args, 1);
738
- if (at < 0 || at >= target.items.length) {
739
- 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)}`);
740
758
  }
741
- const [item] = target.items.splice(at, 1);
759
+ const item = at === 0
760
+ ? takeVectorEnd(target, false)
761
+ : mutableVector(target).items.splice(at, 1)[0];
742
762
  return { kind: "record", typeId: "", fields: [target, item] };
743
763
  },
744
764
  Get: (host, args, shape) => {
745
765
  if (!shape) {
746
766
  throw new TypeError("Vector::Get names no result shape to build its Option against");
747
767
  }
748
- const items = vec(args, 0).items;
749
- const at = index(args, 1);
750
- return at >= 0 && at < items.length
751
- ? 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)])
752
771
  : host.enumValue(shape, "OptionNone", []);
753
772
  },
754
773
  Index: (_host, args) => cloneValue(sequenceElement(args)),
755
774
  IndexBorrow: (_host, args) => sequenceElement(args),
756
- Len: (_host, args) => int(BigInt(vec(args, 0).items.length)),
775
+ Len: (_host, args) => int(BigInt(vectorLength(vec(args, 0)))),
757
776
  Join: (_host, args) => {
758
- const items = vec(args, 0).items;
777
+ const items = vectorItems(vec(args, 0));
759
778
  return str(items
760
779
  .map((_, at) => indexed(items, at).value)
761
780
  .join(indexed(args, 1).value));
762
781
  },
763
782
  Slice: (_host, args) => {
764
- const items = vec(args, 0).items;
783
+ const items = vectorItems(vec(args, 0));
765
784
  const start = index(args, 1);
766
785
  const end = index(args, 2);
767
786
  checkSliceBounds(start, end, items.length);
768
- return {
769
- kind: "vector",
770
- elementType: "",
771
- items: items.slice(start, end).map(cloneValue),
772
- };
787
+ return vectorSlice(items.slice(start, end));
773
788
  },
774
789
  SliceFrom: (_host, args) => {
775
- const items = vec(args, 0).items;
790
+ const items = vectorItems(vec(args, 0));
776
791
  const start = index(args, 1);
777
792
  checkSliceBounds(start, items.length, items.length);
778
- return {
779
- kind: "vector",
780
- elementType: "",
781
- items: items.slice(start).map(cloneValue),
782
- };
793
+ return vectorSlice(items.slice(start));
783
794
  },
784
795
  };
796
+ function vectorSlice(items) {
797
+ const result = {
798
+ kind: "vector",
799
+ elementType: "",
800
+ items: [],
801
+ };
802
+ mutableVector(result, items.length);
803
+ result.items.push(...items.map(cloneValue));
804
+ return result;
805
+ }
785
806
  function sequenceElement(args) {
786
- const items = vec(args, 0).items;
807
+ const target = vec(args, 0);
787
808
  const at = index(args, 1);
788
- if (at < 0 || at >= items.length) {
789
- 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)}`);
790
812
  }
791
- return items[at];
813
+ return item;
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))),
@@ -1204,7 +1230,7 @@ const EXTERN_SHIMS = {
1204
1230
  export function decodeUtf8Checked(value) {
1205
1231
  if (value.kind !== "vector")
1206
1232
  throw new TypeError("UTF-8 input must be bytes");
1207
- 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))));
1208
1234
  let at = 0;
1209
1235
  while (at < bytes.length) {
1210
1236
  const first = bytes[at];
@@ -1250,7 +1276,7 @@ function decodeUtf8(value) {
1250
1276
  if (value.kind !== "vector") {
1251
1277
  return renderStdout(value);
1252
1278
  }
1253
- 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)));
1254
1280
  }
1255
1281
  function compileRegex(host, args) {
1256
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) => {
@@ -294,7 +295,8 @@ export function admitPackage(pkg, bridges = {}) {
294
295
  };
295
296
  if (actor.exit != null || actor.down != null)
296
297
  return unavailable("actor.local_observation");
297
- if (actor.max_heap_bytes != null)
298
+ if (actor.max_heap_bytes != null &&
299
+ (actor.state_type == null || !pkg.managed_types?.length))
298
300
  return unavailable("actor.heap_limit");
299
301
  }
300
302
  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;