@hew-lang/sandbox-vm 0.9.2 → 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,4 +1,5 @@
1
1
  import { BrowserDirectories, } from "./browser-directory.js";
2
+ import { encodingStorage, releaseEncoding } from "./encoding-storage.js";
2
3
  /// The `hew.sandbox.bytecode.v1` executor.
3
4
  ///
4
5
  /// The package is a projection of verified ownership SIR, so this executor
@@ -10,8 +11,8 @@ import { executeCodec } from "./codecs.js";
10
11
  import { actorIdentical } from "./actor-identity.js";
11
12
  import { ActorWaits } from "./actor-waits.js";
12
13
  import { TraceBuilder, runtimeFailure } from "../trace.js";
13
- import { UNIT, cloneValue, stringValue, toJsonValue, } from "../values.js";
14
- import { runtimeTag } from "./package.js";
14
+ import { UNIT, cloneValue, releaseVector, byteVectorValue, vectorStorage, stringValue, toJsonValue, } from "../values.js";
15
+ import { runtimeTag, staticOperations } from "./package.js";
15
16
  import { StructuralRenderer } from "./structural.js";
16
17
  import { timestampBinary } from "./timestamp.js";
17
18
  import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
@@ -22,6 +23,7 @@ import { DeterministicIds } from "../../scheduler/ids.js";
22
23
  import { Mt19937 } from "./mt19937.js";
23
24
  import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
24
25
  import { AdmittedPackage } from "./validate.js";
26
+ import { HeapExceeded, ManagedBudget, ManagedLayouts, } from "./managed-budget.js";
25
27
  const DEFAULT_STEP_BUDGET = 1_000_000;
26
28
  const DEFAULT_ACTOR_STOP_NS = 5_000_000_000;
27
29
  /// Ends the run. The status is already recorded on the trace.
@@ -119,6 +121,7 @@ class ExecutorV1 {
119
121
  tasks = new Map();
120
122
  completedTasks = 0;
121
123
  actorIdentity = 0n;
124
+ arenaIdentity = 0n;
122
125
  faultDebts = new Set();
123
126
  nextFaultDebt = 0;
124
127
  closingResources = new WeakSet();
@@ -128,6 +131,7 @@ class ExecutorV1 {
128
131
  terminating = false;
129
132
  rootFault = null;
130
133
  pkg;
134
+ managed;
131
135
  directories;
132
136
  semaphores;
133
137
  constructor(admission, trace, stdin, policy, options) {
@@ -136,12 +140,14 @@ class ExecutorV1 {
136
140
  this.options = options;
137
141
  const pkg = admission.bytecode;
138
142
  this.pkg = pkg;
143
+ this.managed = new ManagedLayouts(pkg.managed_types ?? [], (value, type) => this.managedRetained(value, type));
139
144
  this.directories = new BrowserDirectories(options.directoryRoots);
140
145
  this.scheduler = new FrameScheduler(trace, policy);
141
146
  this.semaphores = new Semaphores((nanos, ready) => this.scheduler.after(nanos, ready));
142
147
  this.ids = new DeterministicIds(trace.replay.seed);
143
148
  this.host = {
144
149
  argv: [...(options.argv ?? ["main.hew"])],
150
+ arenaInstanceId: () => this.arenaIdentity === 0x7fffffffffffffffn ? 0n : ++this.arenaIdentity,
145
151
  unixTimeMillis: () => this.trace.virtualTimeMs,
146
152
  monotonicTimeNanos: () => {
147
153
  const millis = this.trace.virtualTimeMs;
@@ -189,6 +195,7 @@ class ExecutorV1 {
189
195
  pending: null,
190
196
  fault: null,
191
197
  receiving: false,
198
+ observers: new Set(),
192
199
  });
193
200
  return { kind: "stream", channelId: id };
194
201
  },
@@ -226,12 +233,13 @@ class ExecutorV1 {
226
233
  this.current = this.activate(this.functionAt(pkg.entry.function), [], null, null, undefined, null, {
227
234
  id: "actor:root",
228
235
  actor: null,
229
- returned: (value) => {
236
+ returned: (value, released) => {
230
237
  this.completeRoot();
231
238
  this.trace.exitCode =
232
239
  pkg.entry?.exit === "status" && value.kind === "i64"
233
240
  ? Number(value.value)
234
241
  : 0;
242
+ released();
235
243
  },
236
244
  failed: (fault) => {
237
245
  this.rootFault = fault;
@@ -288,6 +296,7 @@ class ExecutorV1 {
288
296
  this.running = true;
289
297
  while (this.running) {
290
298
  const act = this.current;
299
+ act.context.activeFrame = act;
291
300
  act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
292
301
  if (this.scopeDeadline(act, fault) !== fault)
293
302
  act.context.pendingCancellation ??= fault;
@@ -337,29 +346,42 @@ class ExecutorV1 {
337
346
  : parentContext;
338
347
  const env = new Map();
339
348
  for (const [at, param] of fn.params.entries()) {
340
- env.set(param.value, ownedRef(args[at] ?? UNIT));
349
+ env.set(param.value, ownedRef(args[at] ?? UNIT, param.own === "owned" ? executionContext.actor?.budget : undefined, param.managed_type ?? undefined));
341
350
  }
342
351
  const places = new Map();
343
352
  const trivial = new Set();
353
+ const owned = new Set();
354
+ const managedTypes = new Map();
344
355
  const record = (value) => {
345
356
  if (value.own === "none")
346
357
  trivial.add(value.value);
358
+ if (value.own === "owned")
359
+ owned.add(value.value);
360
+ if (value.managed_type != null)
361
+ managedTypes.set(value.value, value.managed_type);
347
362
  };
348
363
  fn.params.forEach(record);
349
364
  for (const block of fn.blocks) {
350
365
  block.params.forEach(record);
351
- for (const op of block.ops) {
366
+ for (const op of staticOperations(block)) {
367
+ op.definitions?.forEach(record);
352
368
  if ("dst" in op && op.dst != null && op.own === "none")
353
369
  trivial.add(op.dst);
354
370
  }
355
371
  const term = block.term;
356
372
  if ("result" in term && term.result && term.result !== "never")
357
373
  record(term.result);
374
+ if (term.op === "call" && term.handback)
375
+ record(term.handback);
358
376
  if (term.op === "switch.variant")
359
377
  for (const arm of term.arms)
360
378
  arm.fields.forEach(record);
361
379
  }
380
+ for (const [index, param] of fn.params.entries())
381
+ if (param.managed_type != null && args[index])
382
+ this.stampManaged(args[index], param.managed_type, executionContext.actor);
362
383
  return {
384
+ accountActor: executionContext.actor,
363
385
  rcOwner: fn.actor_construction
364
386
  ? { blocks: new Set(), closed: false }
365
387
  : (caller?.rcOwner ??
@@ -372,6 +394,11 @@ class ExecutorV1 {
372
394
  opIndex: 0,
373
395
  env,
374
396
  trivial,
397
+ owned,
398
+ managedTypes,
399
+ managedInputs: [],
400
+ managedCleanup: new Set(),
401
+ managedChildren: new Set(),
375
402
  scopes: new Map(),
376
403
  places,
377
404
  fault: null,
@@ -414,7 +441,11 @@ class ExecutorV1 {
414
441
  }
415
442
  read(act, id) {
416
443
  try {
417
- return readRef(this.refOf(act, id));
444
+ const value = readRef(this.refOf(act, id));
445
+ const type = act.managedTypes.get(id);
446
+ if (type != null)
447
+ this.stampManaged(value, type, act.context.actor);
448
+ return value;
418
449
  }
419
450
  catch (error) {
420
451
  throw new Error(`${act.fn.name} block ${act.block.id} value ${id}: ${error.message}`);
@@ -424,7 +455,414 @@ class ExecutorV1 {
424
455
  if (id === null || id === undefined) {
425
456
  return;
426
457
  }
427
- act.env.set(id, ownedRef(value));
458
+ const type = act.managedTypes.get(id);
459
+ if (type != null)
460
+ this.stampManaged(value, type, act.context.actor);
461
+ const budget = act.owned.has(id) ? act.context.actor?.budget : undefined;
462
+ act.env.set(id, ownedRef(value, budget, type));
463
+ }
464
+ stampManaged(value, type, actor) {
465
+ const budget = actor?.budget;
466
+ this.managed.stamp(value, type);
467
+ if (value.kind === "rc" &&
468
+ !value.closed &&
469
+ value.cell?.owner &&
470
+ budget &&
471
+ !value.cell.managedLease) {
472
+ const row = this.managed.row(type);
473
+ if (row.kind !== "shared")
474
+ throw new Error("Rc has no canonical payload descriptor");
475
+ value.cell.managedPayload = row.payload;
476
+ value.cell.managedLease = budget.retain({
477
+ value: { kind: "rc", cell: value.cell, closed: false },
478
+ type,
479
+ });
480
+ }
481
+ if (value.kind === "task") {
482
+ const task = this.taskFor(value);
483
+ task.managedType = type;
484
+ task.handles.add(value);
485
+ }
486
+ if (value.kind === "stream" && !this.generatorStreams.has(value.channelId))
487
+ this.bindManagedStream(value, type, actor);
488
+ if (value.kind === "generator")
489
+ this.generatorFor(value).managedType = type;
490
+ if (value.kind === "encoding") {
491
+ value.prepareMutation = budget
492
+ ? (replacement, detached) => budget.checkMutation(value, replacement, detached)
493
+ : undefined;
494
+ }
495
+ else if (value.kind === "string") {
496
+ value.prepareGrowth = budget
497
+ ? (capacity, detached) => budget.checkStringGrowth(value, capacity, detached)
498
+ : undefined;
499
+ }
500
+ else if (value.kind === "map") {
501
+ value.prepareInsert = budget
502
+ ? (staged, slot, key, item, copyKey) => {
503
+ const row = this.managed.row(type);
504
+ if (row.kind !== "map" && row.kind !== "set")
505
+ throw new Error("map has no canonical entry descriptor");
506
+ const entries = new Map(staged.entries);
507
+ entries.set(slot, {
508
+ key: copyKey
509
+ ? this.managed.copy(key, row.kind === "map" ? row.key : row.element)
510
+ : key,
511
+ value: item,
512
+ });
513
+ budget.checkMutation(value, { ...staged, entries });
514
+ }
515
+ : undefined;
516
+ }
517
+ else if (value.kind === "vector") {
518
+ value.prepareMutation = budget
519
+ ? (capacity, detached, appended) => {
520
+ const row = this.managed.row(type);
521
+ const storage = vectorStorage(value);
522
+ let items = detached &&
523
+ (row.kind === "vector" ||
524
+ row.kind === "array" ||
525
+ row.kind === "slice")
526
+ ? value.items.map((item) => this.managed.copy(item, row.element))
527
+ : value.items;
528
+ if (appended?.length) {
529
+ if (row.kind !== "vector" &&
530
+ row.kind !== "array" &&
531
+ row.kind !== "slice")
532
+ throw new Error("vector append lacks its canonical element type");
533
+ items = [
534
+ ...items,
535
+ ...appended.map((item) => this.managed.copy(item, row.element)),
536
+ ];
537
+ }
538
+ budget.checkMutation(value, {
539
+ ...value,
540
+ items,
541
+ storage: { ...storage, capacity, items, owners: 1 },
542
+ }, detached);
543
+ }
544
+ : undefined;
545
+ }
546
+ }
547
+ frameManagedRoots(frame) {
548
+ const roots = [];
549
+ for (const [id, ref] of frame.env) {
550
+ const type = frame.managedTypes.get(id);
551
+ if (type != null && frame.owned.has(id) && liveRef(ref))
552
+ roots.push({ value: readRef(ref), type });
553
+ }
554
+ for (const [id, cell] of frame.places) {
555
+ const type = frame.fn.places[id]?.managed_type;
556
+ if (type != null && cell.valid)
557
+ roots.push({ value: cell.value, type });
558
+ }
559
+ for (const scope of frame.scopes.values())
560
+ for (const task of scope.tasks)
561
+ if (task.managedType != null)
562
+ roots.push({
563
+ value: { kind: "task", id: task.id },
564
+ type: task.managedType,
565
+ });
566
+ for (const lease of frame.managedInputs)
567
+ roots.push(...(lease.roots ?? []));
568
+ for (const cleanup of frame.managedCleanup)
569
+ roots.push(...cleanup.roots);
570
+ for (const child of frame.managedChildren)
571
+ roots.push(...this.frameManagedRoots(child.context.activeFrame ?? child));
572
+ if (frame.caller)
573
+ roots.push(...this.frameManagedRoots(frame.caller));
574
+ return roots;
575
+ }
576
+ bindManagedStream(value, type, actor) {
577
+ const row = this.managed.row(type);
578
+ if (row.kind !== "provider" ||
579
+ row.provider !== "stream" ||
580
+ value.kind !== "stream" ||
581
+ this.generatorStreams.has(value.channelId))
582
+ return;
583
+ const budget = actor?.budget;
584
+ this.pipes.bind(value, budget && actor
585
+ ? {
586
+ owner: actor,
587
+ acquire: (envelope) => {
588
+ const lease = budget.retain({
589
+ value: envelope.value,
590
+ type: row.children[0],
591
+ slots: true,
592
+ identity: envelope,
593
+ });
594
+ return () => lease.release();
595
+ },
596
+ refused: () => this.failActorBudget(actor),
597
+ }
598
+ : undefined);
599
+ }
600
+ adoptManaged(root, actor) {
601
+ if (!this.managed.needsAdoption(root.type))
602
+ return;
603
+ this.managed.collect(root, new Map(), undefined, undefined, (value, type) => {
604
+ this.bindManagedStream(value, type, actor);
605
+ const row = this.managed.row(type);
606
+ if (value.kind === "rc" && value.cell && !value.closed) {
607
+ if (row.kind !== "shared")
608
+ throw new Error("Rc has no canonical payload descriptor");
609
+ const cell = value.cell;
610
+ if (cell.owner !== actor.rcOwner && !cell.dying) {
611
+ cell.owner?.blocks.delete(cell);
612
+ cell.owner = actor.rcOwner;
613
+ cell.managedPayload = row.payload;
614
+ actor.rcOwner.blocks.add(cell);
615
+ cell.managedLease = this.rehomeLease(cell.managedLease, actor.budget);
616
+ if (!cell.managedLease && actor.budget)
617
+ cell.managedLease = actor.budget.retain({ value, type });
618
+ }
619
+ }
620
+ else if (row.kind === "generator")
621
+ this.rehomeFrame(this.generatorFor(value).frame, actor);
622
+ else if (value.kind === "task")
623
+ this.rehomeTask(this.taskFor(value), actor);
624
+ }, (value, type) => {
625
+ const row = this.managed.row(type);
626
+ // Encoding trees and opaque host providers cannot contain receiving
627
+ // stream handles. This traversal changes queue ownership only;
628
+ // their payload accounting keeps its separate canonical visitor.
629
+ return row.kind === "provider" &&
630
+ ["json", "yaml", "toml", "opaque"].includes(row.provider)
631
+ ? { identity: value, bytes: 0n, children: [] }
632
+ : this.managedRetained(value, type);
633
+ });
634
+ }
635
+ rehomeLease(lease, budget) {
636
+ if (!lease?.roots)
637
+ return lease;
638
+ const replacement = this.retainRoots(lease.roots, budget);
639
+ lease.release();
640
+ return replacement;
641
+ }
642
+ retainRoots(roots, budget) {
643
+ // Unbounded frames keep typed ownership metadata for a later generator
644
+ // transfer, without building an allocation graph for an account.
645
+ return budget?.retainMany(roots) ?? { roots, release() { } };
646
+ }
647
+ retainCleanup(frame, roots) {
648
+ const cleanup = {
649
+ roots,
650
+ lease: this.retainRoots(roots, frame.accountActor?.budget),
651
+ };
652
+ frame.managedCleanup.add(cleanup);
653
+ return {
654
+ roots,
655
+ release: () => {
656
+ if (!frame.managedCleanup.delete(cleanup))
657
+ return;
658
+ cleanup.lease.release();
659
+ },
660
+ };
661
+ }
662
+ rehomeTask(task, actor) {
663
+ const frame = task.frame?.context.activeFrame ?? task.frame;
664
+ if (task.budget === actor.budget && frame?.accountActor === actor)
665
+ return;
666
+ task.budget = actor.budget;
667
+ if (frame)
668
+ this.rehomeFrame(frame, actor);
669
+ task.managedLease = this.rehomeLease(task.managedLease, actor.budget);
670
+ }
671
+ rehomeFrame(frame, actor) {
672
+ if (frame.accountActor === actor)
673
+ return;
674
+ const previous = frame.accountActor;
675
+ frame.accountActor = actor;
676
+ frame.rcOwner = actor.rcOwner;
677
+ frame.context.actor = actor;
678
+ frame.context.id = actor.id;
679
+ const cells = new Set();
680
+ const rehome = (cell) => {
681
+ if (cells.has(cell))
682
+ return;
683
+ cells.add(cell);
684
+ cell.budget = actor.budget;
685
+ if (cell.valid && cell.type != null)
686
+ this.stampManaged(cell.value, cell.type, actor);
687
+ cell.lease = this.rehomeLease(cell.lease, actor.budget);
688
+ if (!cell.lease && cell.valid && cell.type != null)
689
+ cell.lease = actor.budget?.retain({
690
+ value: cell.value,
691
+ type: cell.type,
692
+ });
693
+ };
694
+ const ref = (value) => {
695
+ if (value.kind === "cell")
696
+ rehome(value.cell);
697
+ else
698
+ ref(value.parent);
699
+ };
700
+ for (const value of frame.env.values())
701
+ ref(value);
702
+ for (const cell of frame.places.values())
703
+ rehome(cell);
704
+ frame.managedInputs = frame.managedInputs.flatMap((lease) => {
705
+ const replacement = this.rehomeLease(lease, actor.budget);
706
+ return replacement ? [replacement] : [];
707
+ });
708
+ for (const cleanup of frame.managedCleanup)
709
+ cleanup.lease = this.rehomeLease(cleanup.lease, actor.budget);
710
+ for (const child of frame.managedChildren)
711
+ this.rehomeFrame(child.context.activeFrame ?? child, actor);
712
+ for (const scope of frame.scopes.values())
713
+ for (const task of scope.tasks)
714
+ this.rehomeTask(task, actor);
715
+ if (frame.caller)
716
+ this.rehomeFrame(frame.caller, actor);
717
+ previous?.budget?.changed();
718
+ actor.budget?.changed();
719
+ }
720
+ failActorBudget(actor) {
721
+ const fault = { kind: "trap", trap: "heap_exceeded" };
722
+ if (!actor.active)
723
+ this.crashActor(actor, fault);
724
+ else {
725
+ // A suspended turn owns its frame until authored unwind and lexical
726
+ // task drains finish. Admission must not retire its state underneath it.
727
+ actor.closing = true;
728
+ actor.crashing ??= fault;
729
+ actor.active.pendingCancellation ??= fault;
730
+ actor.active.cancel?.(fault);
731
+ }
732
+ }
733
+ managedRetained(value, type) {
734
+ const row = this.managed.row(type);
735
+ const typed = (value) => ({
736
+ value,
737
+ type: this.managed.type(value),
738
+ });
739
+ if (row.kind === "generator") {
740
+ const generator = this.generatorFor(value);
741
+ return {
742
+ identity: generator,
743
+ bytes: BigInt(row.payload_bytes),
744
+ children: [
745
+ typed(generator.callable),
746
+ ...this.frameManagedRoots(generator.frame),
747
+ ],
748
+ };
749
+ }
750
+ if (row.kind === "actor_call") {
751
+ const task = this.taskFor(value);
752
+ const reply = !task.taken && !task.fault && task.managedReply;
753
+ return {
754
+ identity: task,
755
+ bytes: reply ? this.managed.width(row.reply) : 0n,
756
+ children: reply ? [{ value: reply, type: row.reply }] : [],
757
+ };
758
+ }
759
+ if (row.kind !== "provider")
760
+ throw new Error("managed retained visitor received no provider");
761
+ switch (row.provider) {
762
+ case "json":
763
+ case "yaml":
764
+ case "toml": {
765
+ if (value.kind !== "encoding")
766
+ throw new Error("managed encoding has no tree");
767
+ const retained = encodingStorage(value);
768
+ return {
769
+ identity: retained,
770
+ bytes: value.value === null ? 0n : retained.bytes,
771
+ children: [],
772
+ };
773
+ }
774
+ case "task": {
775
+ const task = this.taskFor(value);
776
+ const reply = task.resultInitialized &&
777
+ !task.taken &&
778
+ !task.fault &&
779
+ (!task.actorError || task.actorError === "Failed");
780
+ const children = task.frame && !task.done
781
+ ? this.frameManagedRoots(task.frame.context.activeFrame ?? task.frame)
782
+ : [];
783
+ if (task.callable?.kind === "closure" && !task.done)
784
+ children.push(typed(task.callable));
785
+ if (reply)
786
+ children.push({
787
+ value: task.managedResult ?? task.value,
788
+ type: row.children[0],
789
+ });
790
+ return {
791
+ identity: task,
792
+ bytes: (task.invocationPending
793
+ ? BigInt(row.invocation_slot_bytes ??
794
+ (() => {
795
+ throw new Error("task invocation has no canonical carrier width");
796
+ })())
797
+ : 0n) +
798
+ this.managed.width(row.children[0]) +
799
+ (task.memberBytes ?? 0n),
800
+ children,
801
+ };
802
+ }
803
+ case "sink": {
804
+ if (value.kind !== "sink")
805
+ throw new Error("typed sink has no endpoint");
806
+ const sender = this.senderEndpoints.get(value.channelId);
807
+ if (sender) {
808
+ const children = [
809
+ typed(sender.owner.sink),
810
+ typed(sender.owner.encoder),
811
+ ];
812
+ return {
813
+ identity: sender.owner,
814
+ bytes: children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n),
815
+ children,
816
+ };
817
+ }
818
+ return {
819
+ identity: this.pipes.managedRetained(value).identity,
820
+ bytes: 0n,
821
+ children: [],
822
+ };
823
+ }
824
+ case "stream": {
825
+ if (value.kind !== "stream")
826
+ throw new Error("typed stream has no endpoint");
827
+ const adapter = this.generatorStreams.get(value.channelId);
828
+ if (adapter) {
829
+ const generatorType = adapter.generator.managedType;
830
+ if (generatorType == null)
831
+ throw new Error("generator stream lacks its producer descriptor");
832
+ const children = [
833
+ {
834
+ value: { kind: "generator", id: adapter.generator.id },
835
+ type: generatorType,
836
+ },
837
+ ];
838
+ if (adapter.pending)
839
+ children.push({
840
+ value: adapter.pending,
841
+ type: row.children[0],
842
+ slots: true,
843
+ identity: adapter,
844
+ });
845
+ return {
846
+ identity: adapter,
847
+ bytes: this.managed.width(generatorType),
848
+ children,
849
+ };
850
+ }
851
+ const graph = this.pipes.managedRetained(value);
852
+ return {
853
+ identity: graph.identity,
854
+ bytes: 0n,
855
+ children: graph.queued.map((item) => ({
856
+ value: item.value,
857
+ type: row.children[0],
858
+ slots: true,
859
+ identity: item,
860
+ })),
861
+ };
862
+ }
863
+ default:
864
+ throw new HeapExceeded();
865
+ }
428
866
  }
429
867
  invalidate(act, id) {
430
868
  // A boundary's transfer of a trivial value has no ownership to end. SIR
@@ -476,11 +914,26 @@ class ExecutorV1 {
476
914
  /// and a borrow passes the live value.
477
915
  boundary(act, operand) {
478
916
  const value = this.read(act, operand.value);
917
+ const budget = act.context.actor?.budget;
918
+ const type = act.managedTypes.get(operand.value);
919
+ const transferable = act.context.generator || act.context.retainsTransferableFrame;
920
+ if (!budget && transferable && type != null)
921
+ this.managed.stampGraph({ value, type });
479
922
  switch (operand.decision) {
480
923
  case "copy":
481
- case "snapshot":
482
- return cloneValue(value);
924
+ case "snapshot": {
925
+ if (budget && type != null)
926
+ budget.check([{ value: this.managed.copy(value, type), type }]);
927
+ const copy = cloneValue(value);
928
+ if (!budget && transferable && type != null)
929
+ this.managed.stampGraph({ value: copy, type });
930
+ if (type != null)
931
+ act.managedInputs.push(this.retainRoots([{ value: copy, type }], budget));
932
+ return copy;
933
+ }
483
934
  case "move":
935
+ if (type != null)
936
+ act.managedInputs.push(this.retainRoots([{ value, type }], budget));
484
937
  this.invalidate(act, operand.value);
485
938
  return value;
486
939
  default:
@@ -489,6 +942,24 @@ class ExecutorV1 {
489
942
  }
490
943
  // ── instructions ─────────────────────────────────────────────────────────
491
944
  execute(act, op) {
945
+ try {
946
+ this.executeOperation(act, op);
947
+ }
948
+ finally {
949
+ switch (op.op) {
950
+ case "store.init":
951
+ case "store.assign":
952
+ case "load.take":
953
+ case "destructure":
954
+ case "variant.destructure":
955
+ case "destroy_value":
956
+ case "end_lifetime":
957
+ case "task_scope.close":
958
+ act.context.actor?.budget?.changed();
959
+ }
960
+ }
961
+ }
962
+ executeOperation(act, op) {
492
963
  switch (op.op) {
493
964
  case "coverage.hit": {
494
965
  const key = String(op.counter);
@@ -532,8 +1003,10 @@ class ExecutorV1 {
532
1003
  return;
533
1004
  case "destroy_value": {
534
1005
  const value = this.read(act, op.value);
1006
+ const type = act.managedTypes.get(op.value);
1007
+ const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
535
1008
  this.invalidate(act, op.value);
536
- this.releaseOperation(act, value);
1009
+ this.releaseOperation(act, value, () => cleanup?.release());
537
1010
  return;
538
1011
  }
539
1012
  case "begin_borrow":
@@ -543,20 +1016,34 @@ class ExecutorV1 {
543
1016
  case "finish_linear_receiver":
544
1017
  return;
545
1018
  case "alloc_place":
546
- act.places.set(op.place, { value: UNIT, valid: false });
1019
+ act.places.set(op.place, {
1020
+ value: UNIT,
1021
+ valid: false,
1022
+ budget: act.context.actor?.budget,
1023
+ type: act.fn.places[op.place]?.managed_type ?? undefined,
1024
+ });
547
1025
  return;
548
1026
  case "store.init":
549
1027
  writeRef(this.placeRef(act, op.place), this.read(act, op.value));
1028
+ this.invalidate(act, op.value);
550
1029
  return;
551
1030
  case "store.assign": {
552
1031
  const ref = this.placeRef(act, op.place);
553
1032
  const replacement = this.read(act, op.value);
554
1033
  const previous = liveRef(ref) ? readRef(ref) : UNIT;
1034
+ const type = act.fn.places[op.place]?.managed_type;
1035
+ const cleanup = type == null
1036
+ ? undefined
1037
+ : this.retainCleanup(act, [{ value: previous, type }]);
555
1038
  // Retire the old owner before invoking user close code. Publish the
556
1039
  // replacement after cleanup, including when close records a fault;
557
1040
  // the following cleanup.dispatch then drains the new owner once.
558
1041
  invalidateRef(ref);
559
- this.releaseOperation(act, previous, () => writeRef(ref, replacement));
1042
+ this.releaseOperation(act, previous, () => {
1043
+ writeRef(ref, replacement);
1044
+ this.invalidate(act, op.value);
1045
+ cleanup?.release();
1046
+ });
560
1047
  return;
561
1048
  }
562
1049
  case "load.copy":
@@ -574,8 +1061,10 @@ class ExecutorV1 {
574
1061
  case "end_lifetime": {
575
1062
  const ref = this.placeRef(act, op.place);
576
1063
  const value = liveRef(ref) ? readRef(ref) : UNIT;
1064
+ const type = act.fn.places[op.place]?.managed_type;
1065
+ const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
577
1066
  invalidateRef(ref);
578
- this.releaseOperation(act, value);
1067
+ this.releaseOperation(act, value, () => cleanup?.release());
579
1068
  return;
580
1069
  }
581
1070
  case "tuple.make":
@@ -584,6 +1073,7 @@ class ExecutorV1 {
584
1073
  typeId: "",
585
1074
  fields: op.elements.map((id) => this.read(act, id)),
586
1075
  });
1076
+ op.elements.forEach((id) => this.invalidate(act, id));
587
1077
  return;
588
1078
  case "tuple.get":
589
1079
  this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.tuple))[op.index] ?? UNIT));
@@ -595,6 +1085,7 @@ class ExecutorV1 {
595
1085
  shape: op.shape ?? undefined,
596
1086
  fields: op.fields.map((id) => this.read(act, id)),
597
1087
  });
1088
+ op.fields.forEach((id) => this.invalidate(act, id));
598
1089
  return;
599
1090
  case "aggregate.project_copy":
600
1091
  this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.aggregate))[op.field] ?? UNIT));
@@ -616,14 +1107,19 @@ class ExecutorV1 {
616
1107
  elementType: "",
617
1108
  items: op.elements.map((id) => this.read(act, id)),
618
1109
  });
1110
+ op.elements.forEach((id) => this.invalidate(act, id));
619
1111
  return;
620
- case "array.repeat":
1112
+ case "array.repeat": {
1113
+ const seed = this.read(act, op.value);
1114
+ const count = this.arrayRepeatLength(op);
621
1115
  this.define(act, op.dst, {
622
1116
  kind: "vector",
623
1117
  elementType: "",
624
- items: Array.from({ length: op.count ?? 0 }, () => cloneValue(this.read(act, op.value))),
1118
+ items: Array.from({ length: count }, (_, index) => index === 0 ? seed : cloneValue(seed)),
625
1119
  });
1120
+ this.invalidate(act, op.value);
626
1121
  return;
1122
+ }
627
1123
  case "variant.make":
628
1124
  this.define(act, op.dst, {
629
1125
  kind: "enum",
@@ -632,6 +1128,7 @@ class ExecutorV1 {
632
1128
  tag: op.variant,
633
1129
  payload: op.fields.map((id) => this.read(act, id)),
634
1130
  });
1131
+ op.fields.forEach((id) => this.invalidate(act, id));
635
1132
  return;
636
1133
  case "variant.is":
637
1134
  this.define(act, op.dst, {
@@ -691,6 +1188,7 @@ class ExecutorV1 {
691
1188
  fields: op.fields.map((id) => this.read(act, id)),
692
1189
  },
693
1190
  });
1191
+ op.fields.forEach((id) => this.invalidate(act, id));
694
1192
  return;
695
1193
  }
696
1194
  case "callable.coerce": {
@@ -716,6 +1214,7 @@ class ExecutorV1 {
716
1214
  vtable: op.vtable,
717
1215
  value: this.read(act, op.value),
718
1216
  });
1217
+ this.invalidate(act, op.value);
719
1218
  return;
720
1219
  // The registration reserves the free places the body reaches; the drain
721
1220
  // order is already the CFG's, so there is nothing to record here.
@@ -771,9 +1270,12 @@ class ExecutorV1 {
771
1270
  id: task.id,
772
1271
  })),
773
1272
  }, () => {
774
- for (const task of group?.tasks ?? [])
775
- this.tasks.delete(task.id);
776
1273
  act.scopes.delete(op.scope);
1274
+ act.context.actor?.budget?.changed();
1275
+ for (const task of group?.tasks ?? [])
1276
+ task.scopeAlive = false;
1277
+ for (const task of group?.tasks ?? [])
1278
+ this.releaseTaskState(task);
777
1279
  });
778
1280
  return;
779
1281
  }
@@ -786,7 +1288,10 @@ class ExecutorV1 {
786
1288
  actor: act.context.actor,
787
1289
  rcOwner: act.rcOwner,
788
1290
  requestDeadline: this.requestDeadline(act),
789
- returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault), act.context.actor, act.rcOwner),
1291
+ returned: (value, released) => this.closeValueAsync(value, null, (fault) => {
1292
+ released();
1293
+ this.finishGenerator(generator, fault);
1294
+ }, act.context.actor, act.rcOwner),
790
1295
  failed: (fault) => this.finishGenerator(generator, fault),
791
1296
  };
792
1297
  const generator = {
@@ -807,8 +1312,9 @@ class ExecutorV1 {
807
1312
  const scope = act.scopes.get(op.scope);
808
1313
  if (!scope)
809
1314
  throw new Error("race has no task scope");
810
- const members = op.members.map((operand) => {
811
- const member = this.taskFor(this.read(act, operand));
1315
+ const memberValues = op.members.map((operand) => this.read(act, operand));
1316
+ const members = op.members.map((operand, index) => {
1317
+ const member = this.taskFor(memberValues[index]);
812
1318
  this.invalidate(act, operand);
813
1319
  if (member.grouped)
814
1320
  throw new Error("race reuses a task handle");
@@ -817,7 +1323,8 @@ class ExecutorV1 {
817
1323
  });
818
1324
  if (!members.length)
819
1325
  throw new Error("race requires members");
820
- const task = this.newTask();
1326
+ const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), BigInt(op.member_slot_bytes) * BigInt(members.length), true);
1327
+ task.members = memberValues;
821
1328
  scope.tasks.push(task);
822
1329
  let selected = false;
823
1330
  let winner = null;
@@ -839,6 +1346,9 @@ class ExecutorV1 {
839
1346
  winner = first;
840
1347
  value = first.value;
841
1348
  first.value = UNIT;
1349
+ first.taken = true;
1350
+ task.value = value;
1351
+ task.resultInitialized = true;
842
1352
  }
843
1353
  }
844
1354
  if (!cancelled) {
@@ -869,13 +1379,18 @@ class ExecutorV1 {
869
1379
  .filter((member) => member !== winner)
870
1380
  .map((member) => {
871
1381
  const result = member.value;
872
- member.value = UNIT;
873
1382
  return result;
874
1383
  });
875
1384
  this.closeValueAsync({ kind: "vector", elementType: "race result", items: losers }, this.faultText(failure), (closeFault) => {
1385
+ for (const member of members)
1386
+ if (member !== winner) {
1387
+ member.value = UNIT;
1388
+ member.taken = true;
1389
+ }
876
1390
  failure = combineFaults(failure, closeFault);
877
1391
  const finish = (releaseFault = null) => {
878
1392
  failure = combineFaults(failure, releaseFault);
1393
+ this.releaseRaceMembers(task);
879
1394
  this.settleTask(task, failure ? UNIT : value, failure);
880
1395
  for (const changed of [...scope.changed])
881
1396
  changed();
@@ -941,8 +1456,9 @@ class ExecutorV1 {
941
1456
  const specialization = op.op === "task.invoke" && "specialization" in op.callee
942
1457
  ? op.callee.specialization
943
1458
  : null;
944
- const task = this.newTask();
1459
+ const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), 0n, true);
945
1460
  group.tasks.push(task);
1461
+ let releaseReturn = () => { };
946
1462
  const settle = (value, fault) => {
947
1463
  if (task.done || task.finishing)
948
1464
  return;
@@ -955,6 +1471,7 @@ class ExecutorV1 {
955
1471
  if (!fault || (fault.kind === "panic" && fault.cancelled))
956
1472
  fault = closeFault ?? fault;
957
1473
  this.settleTask(task, value, fault);
1474
+ releaseReturn();
958
1475
  if (!task.grouped &&
959
1476
  fault &&
960
1477
  !(fault.kind === "panic" && fault.cancelled)) {
@@ -1008,9 +1525,11 @@ class ExecutorV1 {
1008
1525
  const context = {
1009
1526
  id: task.id,
1010
1527
  actor: act.context.actor,
1528
+ retainsTransferableFrame: !!act.context.generator || act.context.retainsTransferableFrame,
1011
1529
  rcOwner: act.rcOwner,
1012
1530
  requestDeadline: this.requestDeadline(act),
1013
- returned: (value) => {
1531
+ returned: (value, released) => {
1532
+ releaseReturn = released;
1014
1533
  if (dynamicEntry?.receiver_handback &&
1015
1534
  dynamicReceiver?.kind === "dyn") {
1016
1535
  if (value.kind !== "record" || value.fields.length !== 2)
@@ -1039,6 +1558,9 @@ class ExecutorV1 {
1039
1558
  },
1040
1559
  };
1041
1560
  const frame = this.activate(this.functionAt(calleeFunction(callable, specialization)), callable.kind === "closure" ? [callable.environment, ...args] : args, null, null, undefined, null, context);
1561
+ task.frame = frame;
1562
+ task.callable = callable;
1563
+ task.invocationPending = true;
1042
1564
  task.cancel = () => {
1043
1565
  if (task.done || task.finishing)
1044
1566
  return;
@@ -1052,6 +1574,8 @@ class ExecutorV1 {
1052
1574
  this.scheduler.enqueue(task.id, () => {
1053
1575
  if (task.done || task.finishing)
1054
1576
  return;
1577
+ task.invocationPending = false;
1578
+ task.budget?.changed();
1055
1579
  this.runFrame(frame);
1056
1580
  });
1057
1581
  this.trace.allocateId("task", task.id, act.context.id);
@@ -1091,7 +1615,43 @@ class ExecutorV1 {
1091
1615
  }
1092
1616
  // ── terminators ──────────────────────────────────────────────────────────
1093
1617
  terminate(act, term) {
1618
+ const mutable = [
1619
+ "runtime.call",
1620
+ "extern.call",
1621
+ "value.call",
1622
+ "wire.codec",
1623
+ "suspend",
1624
+ ].includes(term.op);
1625
+ if (mutable)
1626
+ act.context.actor?.budget?.changed();
1627
+ try {
1628
+ this.terminateOperation(act, term);
1629
+ }
1630
+ finally {
1631
+ if (mutable)
1632
+ act.context.actor?.budget?.changed();
1633
+ }
1634
+ }
1635
+ terminateOperation(act, term) {
1094
1636
  switch (term.op) {
1637
+ case "materialize": {
1638
+ const budget = act.context.actor?.budget;
1639
+ try {
1640
+ if (term.operation.op === "array.repeat")
1641
+ this.arrayRepeatLength(term.operation);
1642
+ if (budget)
1643
+ this.prepareMaterialize(act, term.operation, budget);
1644
+ }
1645
+ catch (error) {
1646
+ if (!(error instanceof HeapExceeded))
1647
+ throw error;
1648
+ this.takeEdge(act, term.failure);
1649
+ return;
1650
+ }
1651
+ this.execute(act, term.operation);
1652
+ this.takeEdge(act, term.normal);
1653
+ return;
1654
+ }
1095
1655
  case "goto":
1096
1656
  this.takeEdge(act, term.edge);
1097
1657
  return;
@@ -1107,6 +1667,11 @@ class ExecutorV1 {
1107
1667
  throw new Error(`switch.variant has no arm for tag ${tag}`);
1108
1668
  }
1109
1669
  this.spread(act, payloadOf(scrutinee), arm.fields);
1670
+ if (act.owned.has(term.scrutinee)) {
1671
+ this.invalidate(act, term.scrutinee);
1672
+ this.managed.retire(scrutinee);
1673
+ act.context.actor?.budget?.changed();
1674
+ }
1110
1675
  this.takeEdge(act, arm.edge);
1111
1676
  return;
1112
1677
  }
@@ -1247,12 +1812,30 @@ class ExecutorV1 {
1247
1812
  return;
1248
1813
  }
1249
1814
  case "collection": {
1815
+ const receiverType = term.args[0] && act.managedTypes.get(term.args[0].value);
1816
+ const receiverLayout = receiverType == null ? undefined : this.managed.row(receiverType);
1817
+ const elementType = receiverLayout &&
1818
+ (receiverLayout.kind === "vector" ||
1819
+ receiverLayout.kind === "array")
1820
+ ? receiverLayout.element
1821
+ : undefined;
1250
1822
  const args = term.args.map((operand) => this.boundary(act, operand));
1251
- const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape);
1823
+ const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape, elementType);
1252
1824
  this.runValueProgram(act, term, args, iterator);
1253
1825
  return;
1254
1826
  }
1255
1827
  case "shared": {
1828
+ if (act.context.actor?.budget &&
1829
+ (registration.operation === "RcNew" ||
1830
+ registration.operation === "RcSet")) {
1831
+ const constructing = registration.operation === "RcNew";
1832
+ const operand = term.args[constructing ? 0 : 1];
1833
+ const type = act.managedTypes.get(operand.value);
1834
+ if (type == null)
1835
+ throw new Error("Rc payload lacks its typed descriptor");
1836
+ if (!this.checkRuntimePayload(act, term, constructing ? this.managed.width(type) : 0n, [{ value: this.read(act, operand.value), type }]))
1837
+ return;
1838
+ }
1256
1839
  const args = term.args.map((operand) => this.boundary(act, operand));
1257
1840
  this.runValueProgram(act, term, args, sharedOperation(registration.operation, args, act.rcOwner, term.shared_payload_type ?? "", (value) => this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : [])));
1258
1841
  return;
@@ -1425,11 +2008,150 @@ class ExecutorV1 {
1425
2008
  }
1426
2009
  }
1427
2010
  }
2011
+ /** Reserve the exact constructor/copy graph before any operand is consumed. */
2012
+ arrayRepeatLength(op) {
2013
+ const type = op.definitions?.[0]?.managed_type;
2014
+ const row = type == null ? undefined : this.managed.row(type);
2015
+ if (row?.kind !== "array")
2016
+ throw new Error("array repeat lacks its canonical length");
2017
+ const count = BigInt(row.length);
2018
+ this.managed.width(row.element, count);
2019
+ if (count > 0xffffffffn)
2020
+ throw new HeapExceeded();
2021
+ return Number(count);
2022
+ }
2023
+ prepareMaterialize(act, op, budget) {
2024
+ const type = op.definitions?.[0]?.managed_type;
2025
+ if (type == null)
2026
+ throw new Error("materialization has no managed result descriptor");
2027
+ const read = (id) => this.read(act, id);
2028
+ const copy = (value) => this.managed.copy(value, type);
2029
+ let value;
2030
+ switch (op.op) {
2031
+ case "const.str":
2032
+ value = stringValue(this.pkg.strings[op.str] ?? "");
2033
+ break;
2034
+ case "const.bytes":
2035
+ value = bytesValue(this.pkg.bytes[op.bytes] ?? []);
2036
+ break;
2037
+ case "copy_value":
2038
+ value = copy(read(op.source));
2039
+ break;
2040
+ case "load.copy":
2041
+ value = copy(readRef(this.placeRef(act, op.place)));
2042
+ break;
2043
+ case "tuple.get":
2044
+ value = copy(fieldsOf(read(op.tuple))[op.index]);
2045
+ break;
2046
+ case "aggregate.project_copy":
2047
+ value = copy(fieldsOf(read(op.aggregate))[op.field]);
2048
+ break;
2049
+ case "variant.project_copy":
2050
+ value = copy(payloadOf(read(op.source))[op.field]);
2051
+ break;
2052
+ case "array.make":
2053
+ value = {
2054
+ kind: "vector",
2055
+ elementType: "",
2056
+ items: op.elements.map(read),
2057
+ };
2058
+ break;
2059
+ case "array.repeat": {
2060
+ const count = this.arrayRepeatLength(op);
2061
+ const row = this.managed.row(type);
2062
+ if (row.kind !== "array")
2063
+ throw new Error("array repeat lacks its element descriptor");
2064
+ const empty = [];
2065
+ const storage = {
2066
+ items: empty,
2067
+ capacity: count,
2068
+ owners: 1,
2069
+ bytes: false,
2070
+ shareOnCopy: row.share_on_copy,
2071
+ };
2072
+ value = { kind: "vector", elementType: "", items: empty, storage };
2073
+ budget.check([{ value, type }]);
2074
+ const seed = read(op.value);
2075
+ storage.items = Array.from({ length: count }, (_, index) => index ? this.managed.copy(seed, row.element) : seed);
2076
+ value.items = storage.items;
2077
+ break;
2078
+ }
2079
+ case "variant.make":
2080
+ value = {
2081
+ kind: "enum",
2082
+ typeId: "",
2083
+ shape: op.shape,
2084
+ tag: op.variant,
2085
+ payload: op.fields.map(read),
2086
+ };
2087
+ break;
2088
+ case "closure.make":
2089
+ value = {
2090
+ kind: "closure",
2091
+ entries: this.closureAt(op.closure).entries,
2092
+ specialization: null,
2093
+ environment: {
2094
+ kind: "record",
2095
+ typeId: "",
2096
+ fields: op.fields.map(read),
2097
+ },
2098
+ };
2099
+ break;
2100
+ case "dyn.make":
2101
+ value = { kind: "dyn", vtable: op.vtable, value: read(op.value) };
2102
+ break;
2103
+ case "generator.make": {
2104
+ const row = this.managed.row(type);
2105
+ if (row.kind !== "generator")
2106
+ throw new Error("generator lacks its payload descriptor");
2107
+ const callable = read(op.callable);
2108
+ budget.checkPayload(BigInt(row.payload_bytes), [
2109
+ { value: callable, type: this.managed.type(callable) },
2110
+ ]);
2111
+ return;
2112
+ }
2113
+ case "task.spawn":
2114
+ case "task.invoke":
2115
+ case "task.race": {
2116
+ const row = this.managed.row(type);
2117
+ if (row.kind !== "provider" || row.provider !== "task")
2118
+ throw new Error("checked task lacks its canonical provider descriptor");
2119
+ const carrier = op.op === "task.race"
2120
+ ? BigInt(op.member_slot_bytes) * BigInt(op.members.length)
2121
+ : BigInt(row.invocation_slot_bytes);
2122
+ const children = [];
2123
+ if (op.op === "task.invoke") {
2124
+ const operands = [...op.args];
2125
+ if ("receiver" in op.callee)
2126
+ operands.push(op.callee.receiver);
2127
+ for (const operand of operands) {
2128
+ const type = act.managedTypes.get(operand.value);
2129
+ if (type == null)
2130
+ throw new Error("task input lacks its canonical descriptor");
2131
+ const value = read(operand.value);
2132
+ children.push({
2133
+ value: operand.decision === "copy" || operand.decision === "snapshot"
2134
+ ? this.managed.copy(value, type)
2135
+ : value,
2136
+ type,
2137
+ });
2138
+ }
2139
+ }
2140
+ budget.checkPayload(this.managed.width(row.children[0]) + carrier, children);
2141
+ return;
2142
+ }
2143
+ default:
2144
+ throw new Error(`unexpected managed materialization ${op.op}`);
2145
+ }
2146
+ budget.check([{ value, type }]);
2147
+ }
1428
2148
  takeEdge(act, edge) {
1429
2149
  // Read every argument before rebinding, so a block that branches to itself
1430
2150
  // with permuted parameters binds the incoming values, not the rebound ones.
1431
2151
  const args = edge.args.map((id) => this.refOf(act, id));
1432
2152
  const target = this.blockAt(act.fn, edge.to);
2153
+ for (const lease of act.managedInputs.splice(0))
2154
+ lease.release();
1433
2155
  for (const [at, param] of target.params.entries()) {
1434
2156
  act.env.set(param.value, args[at] ?? ownedRef(UNIT));
1435
2157
  }
@@ -1459,9 +2181,53 @@ class ExecutorV1 {
1459
2181
  throw error;
1460
2182
  });
1461
2183
  }
1462
- /// Run a runtime or extern shim. A fault it raises takes the call's `unwind`
1463
- /// edge so the cleanup the package names runs before the program ends.
2184
+ /** Reserve typed runtime payload before construction or replacement. Refusal
2185
+ * follows the call's existing input ownership and authored cleanup edge. */
2186
+ checkRuntimePayload(act, term, bytes, children) {
2187
+ const budget = act.context.actor?.budget;
2188
+ if (!budget)
2189
+ return true;
2190
+ try {
2191
+ budget.checkPayload(bytes, children);
2192
+ return true;
2193
+ }
2194
+ catch (error) {
2195
+ if (!(error instanceof HeapExceeded))
2196
+ throw error;
2197
+ const fault = { kind: "trap", trap: "heap_exceeded" };
2198
+ if (this.keepsInputs(term)) {
2199
+ // No new payload exists and none of the consuming inputs has moved.
2200
+ this.raiseFault(act, fault, term.unwind);
2201
+ return false;
2202
+ }
2203
+ const consumed = term.args
2204
+ .filter((operand) => operand.decision === "move")
2205
+ .map((operand) => this.boundary(act, operand));
2206
+ this.running = false;
2207
+ this.closeValueAsync({ kind: "record", typeId: "", fields: consumed }, error.message, (cleanupFault) => {
2208
+ this.raiseFault(act, combineFaults(fault, cleanupFault), term.unwind);
2209
+ this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
2210
+ }, act.context.actor, act.rcOwner, act);
2211
+ return false;
2212
+ }
2213
+ }
2214
+ /// Run a runtime or extern shim through its authored fault continuation.
1464
2215
  callShim(act, term, shim) {
2216
+ const family = term.op === "runtime.call"
2217
+ ? this.pkg.runtime_families[term.family]?.family
2218
+ : undefined;
2219
+ const budget = act.context.actor?.budget;
2220
+ if (budget &&
2221
+ (family === "SinkFromEncoder" || family === "StreamFromGenerator")) {
2222
+ const children = term.args.map((operand) => {
2223
+ const type = act.managedTypes.get(operand.value);
2224
+ if (type == null)
2225
+ throw new Error("stream adapter input lacks its typed descriptor");
2226
+ return { value: this.read(act, operand.value), type };
2227
+ });
2228
+ if (!this.checkRuntimePayload(act, term, children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n), children))
2229
+ return;
2230
+ }
1465
2231
  const args = term.args.map((operand) => this.boundary(act, operand));
1466
2232
  // A call that hands back its updated receiver names its value's shape as
1467
2233
  // the pair's second member.
@@ -1477,9 +2243,13 @@ class ExecutorV1 {
1477
2243
  value = shim(this.host, args, shape, aggregate);
1478
2244
  }
1479
2245
  catch (error) {
1480
- if (error instanceof ShimFault) {
2246
+ if (error instanceof ShimFault || error instanceof HeapExceeded) {
1481
2247
  this.keepInputs(act, term, args);
1482
- this.raiseFault(act, { kind: "trap", trap: error.trap, message: error.message }, term.unwind);
2248
+ this.raiseFault(act, {
2249
+ kind: "trap",
2250
+ trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
2251
+ message: error.message,
2252
+ }, term.unwind);
1483
2253
  return;
1484
2254
  }
1485
2255
  throw error;
@@ -1523,16 +2293,21 @@ class ExecutorV1 {
1523
2293
  this.keepInputs(act, term, args);
1524
2294
  this.raiseFault(act, fault, term.unwind);
1525
2295
  resume();
1526
- }, act.context.actor, act.rcOwner);
2296
+ }, act.context.actor, act.rcOwner, act);
1527
2297
  };
1528
2298
  const next = (value = UNIT) => {
1529
2299
  let step;
1530
2300
  try {
1531
2301
  step = iterator.next(value);
2302
+ act.context.actor?.budget?.changed();
1532
2303
  }
1533
2304
  catch (error) {
1534
- if (error instanceof ShimFault) {
1535
- fail({ kind: "trap", trap: error.trap, message: error.message });
2305
+ if (error instanceof ShimFault || error instanceof HeapExceeded) {
2306
+ fail({
2307
+ kind: "trap",
2308
+ trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
2309
+ message: error.message,
2310
+ });
1536
2311
  return;
1537
2312
  }
1538
2313
  throw error;
@@ -1546,7 +2321,16 @@ class ExecutorV1 {
1546
2321
  }
1547
2322
  else if (step.value.kind === "release") {
1548
2323
  const request = step.value;
2324
+ const lease = act.context.actor?.budget ||
2325
+ act.context.generator ||
2326
+ act.context.retainsTransferableFrame
2327
+ ? this.retainCleanup(act, (request.retained ?? [request.value]).map((value) => ({
2328
+ value,
2329
+ type: request.retainedType ?? this.managed.type(value),
2330
+ })))
2331
+ : undefined;
1549
2332
  this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
2333
+ lease?.release();
1550
2334
  if (fault) {
1551
2335
  if (term.op !== "runtime.call" || !term.releases_contents) {
1552
2336
  fail(fault, request.onFault);
@@ -1557,11 +2341,11 @@ class ExecutorV1 {
1557
2341
  act.fault ??= fault;
1558
2342
  }
1559
2343
  next();
1560
- }, act.context.actor, act.rcOwner);
2344
+ }, act.context.actor, act.rcOwner, act);
1561
2345
  }
1562
2346
  else {
1563
2347
  const request = step.value;
1564
- const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner);
2348
+ const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner, false, act);
1565
2349
  act.context.cancel = (fault) => child.cancel?.(fault);
1566
2350
  }
1567
2351
  };
@@ -1629,7 +2413,17 @@ class ExecutorV1 {
1629
2413
  throw new Error(`${term.op} with a "never" result cannot return in the sandbox`);
1630
2414
  }
1631
2415
  if (term.result && term.result !== "never") {
1632
- this.define(act, term.result.value, value);
2416
+ try {
2417
+ this.define(act, term.result.value, value);
2418
+ }
2419
+ catch (error) {
2420
+ if (!(error instanceof HeapExceeded))
2421
+ throw error;
2422
+ this.closeValueAsync(value, error.message, (fault) => {
2423
+ this.raiseFault(act, combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault), "unwind" in term ? term.unwind : null);
2424
+ }, act.context.actor, act.rcOwner);
2425
+ return;
2426
+ }
1633
2427
  }
1634
2428
  this.takeEdge(act, term.normal);
1635
2429
  }
@@ -1675,9 +2469,20 @@ class ExecutorV1 {
1675
2469
  }
1676
2470
  const cell = item.value;
1677
2471
  const payload = cell.value;
2472
+ let payloadLease;
2473
+ if (actor?.budget && cell.managedPayload != null)
2474
+ payloadLease = actor.budget.retain({
2475
+ value: payload,
2476
+ type: cell.managedPayload,
2477
+ slots: true,
2478
+ identity: cell,
2479
+ });
2480
+ cell.managedLease?.release();
2481
+ cell.managedLease = undefined;
1678
2482
  cell.value = UNIT;
1679
2483
  blocks.delete(cell);
1680
2484
  this.closeValueAsync(payload, this.faultText(firstFault), (fault) => {
2485
+ payloadLease?.release();
1681
2486
  firstFault = combineFaults(firstFault, fault);
1682
2487
  release();
1683
2488
  }, actor, owner);
@@ -1724,7 +2529,39 @@ class ExecutorV1 {
1724
2529
  const caller = act.caller;
1725
2530
  if (!caller) {
1726
2531
  this.running = false;
1727
- act.context.returned(value);
2532
+ let reply;
2533
+ if (act.context.reply &&
2534
+ act.context.actor?.budget &&
2535
+ act.block.term.op === "return" &&
2536
+ act.block.term.value) {
2537
+ const type = act.managedTypes.get(act.block.term.value.value);
2538
+ if (type == null)
2539
+ throw new Error("actor reply lacks its managed descriptor");
2540
+ try {
2541
+ if (type != null)
2542
+ reply = act.context.actor.budget.retain({
2543
+ value,
2544
+ type,
2545
+ slots: true,
2546
+ });
2547
+ }
2548
+ catch (error) {
2549
+ if (!(error instanceof HeapExceeded))
2550
+ throw error;
2551
+ this.closeValueAsync(value, error.message, (fault) => {
2552
+ for (const lease of act.managedInputs.splice(0))
2553
+ lease.release();
2554
+ act.context.failed(combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault));
2555
+ }, act.context.actor, act.rcOwner);
2556
+ return;
2557
+ }
2558
+ }
2559
+ const inputs = act.managedInputs.splice(0);
2560
+ act.context.returned(value, () => {
2561
+ reply?.release();
2562
+ for (const lease of inputs)
2563
+ lease.release();
2564
+ });
1728
2565
  return;
1729
2566
  }
1730
2567
  if (!act.normal) {
@@ -1744,6 +2581,8 @@ class ExecutorV1 {
1744
2581
  }
1745
2582
  this.current = caller;
1746
2583
  this.takeEdge(caller, act.normal);
2584
+ for (const lease of act.managedInputs.splice(0))
2585
+ lease.release();
1747
2586
  }
1748
2587
  dynamicEntry(receiver, slot) {
1749
2588
  const table = this.pkg.vtables[receiver.vtable] ??
@@ -1792,6 +2631,8 @@ class ExecutorV1 {
1792
2631
  if (!caller) {
1793
2632
  this.running = false;
1794
2633
  act.context.failed(fault, handback);
2634
+ for (const lease of act.managedInputs.splice(0))
2635
+ lease.release();
1795
2636
  return;
1796
2637
  }
1797
2638
  if (!act.unwind) {
@@ -1811,6 +2652,8 @@ class ExecutorV1 {
1811
2652
  caller.fault = fault;
1812
2653
  this.current = caller;
1813
2654
  this.takeEdge(caller, act.unwind);
2655
+ for (const lease of act.managedInputs.splice(0))
2656
+ lease.release();
1814
2657
  }
1815
2658
  finishConsumedCallable(act, value, done) {
1816
2659
  const callable = act.consumedCallable;
@@ -1850,6 +2693,7 @@ class ExecutorV1 {
1850
2693
  const generator = act.context.generator;
1851
2694
  if (!generator || !generator.receive)
1852
2695
  throw new Error("yield has no generator consumer");
2696
+ generator.frame = act;
1853
2697
  const value = this.boundary(act, term.inputs[0]);
1854
2698
  const wake = this.park(act, term);
1855
2699
  generator.resume = () => wake(UNIT);
@@ -1895,9 +2739,27 @@ class ExecutorV1 {
1895
2739
  case "StreamSend": {
1896
2740
  const sink = this.boundary(act, term.inputs[0]);
1897
2741
  const value = this.boundary(act, term.inputs[1]);
2742
+ const type = act.managedTypes.get(term.inputs[1].value);
2743
+ if (type == null)
2744
+ throw new Error("stream write lacks its typed input descriptor");
2745
+ // The native operation owns one aligned input slot until accepted or
2746
+ // terminal cleanup, independently of the receiving queue's slot.
1898
2747
  const wake = this.park(act, term);
2748
+ try {
2749
+ act.managedInputs.push(this.retainRoots([{ value, type, slots: true, identity: {} }], act.context.actor?.budget));
2750
+ }
2751
+ catch (error) {
2752
+ if (!(error instanceof HeapExceeded))
2753
+ throw error;
2754
+ const refused = { kind: "trap", trap: "heap_exceeded" };
2755
+ this.closeValueAsync(value, error.message, (fault) => wake(UNIT, combineFaults(refused, fault)), act.context.actor, act.rcOwner, act);
2756
+ return;
2757
+ }
1899
2758
  const cancel = act.context.cancel;
1900
2759
  const stop = this.sendStream(sink, value, term.detail.park, act.context.actor, (status, fault) => {
2760
+ if (status === 0)
2761
+ for (const lease of act.managedInputs.splice(0))
2762
+ lease.release();
1901
2763
  wake(UNIT, fault, status);
1902
2764
  }, act.rcOwner);
1903
2765
  act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
@@ -1915,8 +2777,10 @@ class ExecutorV1 {
1915
2777
  return;
1916
2778
  }
1917
2779
  let cancelWait = () => { };
1918
- const wake = this.park(act, term, () => cancelWait());
1919
- cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault) => {
2780
+ let releaseEnvelope = () => { };
2781
+ const wake = this.park(act, term, () => cancelWait(), undefined, () => releaseEnvelope());
2782
+ cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault, released) => {
2783
+ releaseEnvelope = released ?? (() => { });
1920
2784
  if (fault !== null)
1921
2785
  wake(UNIT, { kind: "panic", message: fault });
1922
2786
  else
@@ -1925,9 +2789,17 @@ class ExecutorV1 {
1925
2789
  return;
1926
2790
  }
1927
2791
  case "Await": {
1928
- const task = this.taskFor(this.boundary(act, term.inputs[0]));
2792
+ const handle = this.boundary(act, term.inputs[0]);
2793
+ const task = this.taskFor(handle);
1929
2794
  let unwatch = () => { };
1930
- const wake = this.park(act, term, () => unwatch());
2795
+ let resultLease;
2796
+ const wake = this.park(act, term, () => unwatch(), undefined, () => {
2797
+ resultLease?.release();
2798
+ task.handles.delete(handle);
2799
+ this.managed.retire(handle);
2800
+ task.budget?.changed();
2801
+ this.releaseTaskState(task);
2802
+ });
1931
2803
  const ready = () => {
1932
2804
  if (!task.done) {
1933
2805
  this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
@@ -1937,7 +2809,9 @@ class ExecutorV1 {
1937
2809
  wake(UNIT, task.fault);
1938
2810
  else {
1939
2811
  const value = task.value;
2812
+ resultLease = this.retainTaskResult(task, act.context.actor?.budget);
1940
2813
  task.value = UNIT;
2814
+ task.taken = true;
1941
2815
  wake(value);
1942
2816
  }
1943
2817
  };
@@ -1997,12 +2871,28 @@ class ExecutorV1 {
1997
2871
  const wake = this.park(act, term, () => unwatch.forEach((remove) => remove()));
1998
2872
  let cancelTimer;
1999
2873
  const choose = () => {
2000
- const ready = sources.flatMap((value, index) => {
2001
- if (value.kind === "stream")
2002
- return this.pipes.readable(value) ? [{ index, order: 0 }] : [];
2003
- const task = this.taskFor(value);
2004
- return task.done ? [{ index, order: task.completedAt }] : [];
2005
- });
2874
+ const ready = [];
2875
+ for (const [index, value] of sources.entries()) {
2876
+ if (value.kind === "stream") {
2877
+ const adapter = this.generatorStreams.get(value.channelId);
2878
+ if (adapter)
2879
+ this.pollGeneratorStream(adapter);
2880
+ const readable = adapter
2881
+ ? adapter.pending !== null ||
2882
+ adapter.generator.done ||
2883
+ adapter.closed
2884
+ : this.pipes.readable(value);
2885
+ if (readable)
2886
+ ready.push({ index, order: 0 });
2887
+ }
2888
+ else {
2889
+ const task = this.taskFor(value);
2890
+ if (task.done)
2891
+ ready.push({ index, order: task.completedAt });
2892
+ }
2893
+ if (ready.length && term.detail.order !== "completion")
2894
+ break;
2895
+ }
2006
2896
  if (term.detail.order === "completion")
2007
2897
  ready.sort((a, b) => a.order - b.order);
2008
2898
  if (ready[0]) {
@@ -2022,8 +2912,15 @@ class ExecutorV1 {
2022
2912
  cancelTimer = this.scheduler.after(duration.value, () => wake({ kind: "i64", value: BigInt(sources.length) }));
2023
2913
  }
2024
2914
  sources.forEach((value) => {
2025
- if (value.kind === "stream")
2026
- unwatch.push(this.pipes.observe(value, choose));
2915
+ if (value.kind === "stream") {
2916
+ const adapter = this.generatorStreams.get(value.channelId);
2917
+ if (adapter) {
2918
+ adapter.observers.add(choose);
2919
+ unwatch.push(() => adapter.observers.delete(choose));
2920
+ }
2921
+ else
2922
+ unwatch.push(this.pipes.observe(value, choose));
2923
+ }
2027
2924
  else {
2028
2925
  const task = this.taskFor(value);
2029
2926
  task.changed.push(choose);
@@ -2178,6 +3075,10 @@ class ExecutorV1 {
2178
3075
  let cancelTimer = () => { };
2179
3076
  const wake = this.park(act, term, () => cancelTimer());
2180
3077
  cancelTimer = this.scheduler.after(nanos, () => wake(UNIT));
3078
+ // Admission can publish a fault synchronously before this sleep parks.
3079
+ // Deliver that latch after arming, so cancellation also removes the timer.
3080
+ if (act.context.pendingCancellation)
3081
+ act.context.cancel?.(act.context.pendingCancellation);
2181
3082
  return;
2182
3083
  }
2183
3084
  default: {
@@ -2203,13 +3104,30 @@ class ExecutorV1 {
2203
3104
  message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
2204
3105
  });
2205
3106
  }
2206
- park(act, term, cleanup = () => { }, accepted = () => { }) {
3107
+ park(act, term, cleanup = () => { }, accepted = () => { }, drained = () => { }) {
2207
3108
  this.running = false;
2208
3109
  act.context.parked = true;
2209
3110
  let pending = null;
3111
+ let pendingLease;
2210
3112
  const resume = (value, fault = null, edge = 0) => {
2211
3113
  if (pending)
2212
3114
  return false;
3115
+ if (!fault &&
3116
+ act.context.actor?.budget &&
3117
+ term.result &&
3118
+ term.result !== "never") {
3119
+ const type = term.result.managed_type;
3120
+ if (type == null)
3121
+ throw new Error("suspended result lacks its managed descriptor");
3122
+ try {
3123
+ pendingLease = act.context.actor.budget.retain({ value, type });
3124
+ }
3125
+ catch (error) {
3126
+ if (!(error instanceof HeapExceeded))
3127
+ throw error;
3128
+ fault = { kind: "trap", trap: "heap_exceeded" };
3129
+ }
3130
+ }
2213
3131
  pending = { value, fault, edge };
2214
3132
  act.context.parked = false;
2215
3133
  if (act.context.actor?.active === act.context)
@@ -2236,6 +3154,8 @@ class ExecutorV1 {
2236
3154
  throw new Error(`${term.kind} has no resume edge`);
2237
3155
  this.takeEdge(act, edge);
2238
3156
  }
3157
+ pendingLease?.release();
3158
+ drained();
2239
3159
  this.runFrame(act);
2240
3160
  };
2241
3161
  // A close can arrive after readiness but before this frame runs. Keep
@@ -2440,7 +3360,16 @@ class ExecutorV1 {
2440
3360
  adapter.waiting = undefined;
2441
3361
  this.closeGenerator(generator, (closeFault) => cancel?.(closeFault ?? fault));
2442
3362
  };
2443
- if (adapter.receiving)
3363
+ this.pollGeneratorStream(adapter);
3364
+ }
3365
+ /** Readiness drives the same producer as recv but leaves a published item
3366
+ * retained until the winning receive. Losing observations only detach. */
3367
+ pollGeneratorStream(adapter) {
3368
+ const generator = adapter.generator;
3369
+ if (adapter.receiving ||
3370
+ adapter.pending !== null ||
3371
+ generator.done ||
3372
+ adapter.closed)
2444
3373
  return;
2445
3374
  adapter.receiving = true;
2446
3375
  generator.receive = (value, fault) => {
@@ -2457,6 +3386,8 @@ class ExecutorV1 {
2457
3386
  waiting(value, fault);
2458
3387
  else
2459
3388
  adapter.pending = value;
3389
+ for (const changed of [...adapter.observers])
3390
+ changed();
2460
3391
  };
2461
3392
  if (generator.started) {
2462
3393
  const resume = generator.resume;
@@ -2499,7 +3430,7 @@ class ExecutorV1 {
2499
3430
  act.fault = combineFaults(act.fault, fault);
2500
3431
  if (!synchronous)
2501
3432
  this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
2502
- }, act.context.actor, act.rcOwner);
3433
+ }, act.context.actor, act.rcOwner, act);
2503
3434
  synchronous = false;
2504
3435
  if (!completed)
2505
3436
  this.running = false;
@@ -2537,7 +3468,7 @@ class ExecutorV1 {
2537
3468
  else
2538
3469
  generator.frame.context.cancel?.();
2539
3470
  }
2540
- closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner) {
3471
+ closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner, retainedBy) {
2541
3472
  const pending = [value];
2542
3473
  let firstFault = null;
2543
3474
  const next = (failure = null) => {
@@ -2546,10 +3477,38 @@ class ExecutorV1 {
2546
3477
  // must disclose that failure instead of publishing a clean end.
2547
3478
  const currentFault = fault ?? this.faultText(actor?.crashing ?? firstFault);
2548
3479
  while (pending.length) {
2549
- const value = pending.pop();
3480
+ const entry = pending.pop();
3481
+ if ("managedRelease" in entry) {
3482
+ entry.managedRelease.release();
3483
+ continue;
3484
+ }
3485
+ if ("taskTaken" in entry) {
3486
+ entry.taskTaken.value = UNIT;
3487
+ entry.taskTaken.taken = true;
3488
+ entry.taskTaken.managedResult = undefined;
3489
+ entry.taskTaken.managedReply = undefined;
3490
+ continue;
3491
+ }
3492
+ if ("retire" in entry) {
3493
+ if (entry.retire.kind === "stream" &&
3494
+ !this.generatorStreams.has(entry.retire.channelId))
3495
+ this.pipes.finishClose(entry.retire);
3496
+ this.managed.retire(entry.retire);
3497
+ if (entry.retire.kind === "stream")
3498
+ this.generatorStreams.delete(entry.retire.channelId);
3499
+ actor?.budget?.changed();
3500
+ if (entry.retire.kind === "task") {
3501
+ const task = this.tasks.get(entry.retire.id);
3502
+ if (task)
3503
+ this.releaseTaskState(task);
3504
+ }
3505
+ continue;
3506
+ }
3507
+ const value = entry;
3508
+ pending.push({ retire: value });
2550
3509
  switch (value.kind) {
2551
3510
  case "encoding":
2552
- value.value = null;
3511
+ releaseEncoding(value);
2553
3512
  break;
2554
3513
  case "string":
2555
3514
  value.release();
@@ -2558,6 +3517,21 @@ class ExecutorV1 {
2558
3517
  if (!value.closed) {
2559
3518
  value.closed = true;
2560
3519
  if (value.cell && !value.cell.dying && --value.cell.refs === 0) {
3520
+ if (actor?.budget) {
3521
+ const row = this.managed.row(this.managed.type(value));
3522
+ if (row.kind !== "shared")
3523
+ throw new Error("Rc has no canonical payload descriptor");
3524
+ pending.push({
3525
+ managedRelease: actor.budget.retain({
3526
+ value: value.cell.value,
3527
+ type: row.payload,
3528
+ slots: true,
3529
+ identity: value.cell,
3530
+ }),
3531
+ });
3532
+ }
3533
+ value.cell.managedLease?.release();
3534
+ value.cell.managedLease = undefined;
2561
3535
  value.cell.owner?.blocks.delete(value.cell);
2562
3536
  value.cell.owner = null;
2563
3537
  pending.push(value.cell.value);
@@ -2586,17 +3560,18 @@ class ExecutorV1 {
2586
3560
  const resource = this.pkg.resources?.[value.resource];
2587
3561
  if (resource?.kind !== "opaque")
2588
3562
  throw new Error("opaque owner lost its selected close");
2589
- this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
3563
+ this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
2590
3564
  return;
2591
3565
  }
2592
3566
  case "task": {
2593
3567
  // Dropping a handle does not cancel scoped work. Join drains the
2594
3568
  // task, and scope close releases any result that was never taken.
2595
3569
  const task = this.tasks.get(value.id);
2596
- if (task?.done) {
3570
+ const owned = task?.handles.delete(value);
3571
+ if (task?.done && (!owned || !task.scopeAlive)) {
2597
3572
  const result = task.value;
2598
- task.value = UNIT;
2599
- this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner);
3573
+ pending.push({ taskTaken: task });
3574
+ this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner, retainedBy);
2600
3575
  return;
2601
3576
  }
2602
3577
  if (task?.close) {
@@ -2624,7 +3599,7 @@ class ExecutorV1 {
2624
3599
  if (this.consumedResources.has(value))
2625
3600
  break;
2626
3601
  if (!this.closingResources.has(value)) {
2627
- this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
3602
+ this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
2628
3603
  return;
2629
3604
  }
2630
3605
  this.consumedResources.add(value);
@@ -2636,12 +3611,11 @@ class ExecutorV1 {
2636
3611
  pending.push(...[...value.payload].reverse());
2637
3612
  break;
2638
3613
  case "vector":
2639
- pending.push(...[...value.items].reverse());
3614
+ pending.push(...releaseVector(value).reverse());
2640
3615
  break;
2641
3616
  case "stream": {
2642
3617
  const adapter = this.generatorStreams.get(value.channelId);
2643
3618
  if (adapter) {
2644
- this.generatorStreams.delete(value.channelId);
2645
3619
  adapter.closed = true;
2646
3620
  if (adapter.pending !== null)
2647
3621
  pending.push(adapter.pending);
@@ -2687,7 +3661,7 @@ class ExecutorV1 {
2687
3661
  }
2688
3662
  closeValue(value, fault) {
2689
3663
  if (value.kind === "encoding")
2690
- value.value = null;
3664
+ releaseEncoding(value);
2691
3665
  if (value.kind === "host_resource")
2692
3666
  this.closeHostResource(value);
2693
3667
  if (value.kind === "string")
@@ -2703,11 +3677,34 @@ class ExecutorV1 {
2703
3677
  for (const field of value.payload)
2704
3678
  this.closeValue(field, fault);
2705
3679
  else if (value.kind === "vector")
2706
- for (const item of value.items)
3680
+ for (const item of releaseVector(value))
2707
3681
  this.closeValue(item, fault);
3682
+ else if (value.kind === "rc" && !value.closed) {
3683
+ value.closed = true;
3684
+ const cell = value.cell;
3685
+ if (cell && !cell.dying && --cell.refs === 0) {
3686
+ cell.dying = true;
3687
+ this.closeValue(cell.value, fault);
3688
+ cell.value = UNIT;
3689
+ cell.managedLease?.release();
3690
+ cell.managedLease = undefined;
3691
+ cell.owner?.blocks.delete(cell);
3692
+ cell.owner = null;
3693
+ }
3694
+ }
3695
+ else if (value.kind === "weak" && !value.closed) {
3696
+ value.closed = true;
3697
+ value.cell.weak--;
3698
+ }
3699
+ this.managed.retire(value);
2708
3700
  }
2709
- newTask() {
3701
+ newTask(budget, type, memberBytes = 0n, scopeAlive = false) {
2710
3702
  const task = {
3703
+ handles: new Set(),
3704
+ scopeAlive,
3705
+ budget,
3706
+ memberBytes,
3707
+ managedType: type,
2711
3708
  id: this.ids.task(),
2712
3709
  grouped: false,
2713
3710
  done: false,
@@ -2720,8 +3717,44 @@ class ExecutorV1 {
2720
3717
  changed: [],
2721
3718
  };
2722
3719
  this.tasks.set(task.id, task);
3720
+ if (budget) {
3721
+ if (type == null)
3722
+ throw new Error("task has no managed result descriptor");
3723
+ const row = this.managed.row(type);
3724
+ if (row.kind === "provider" && row.provider === "task")
3725
+ task.managedLease = budget.retain({
3726
+ value: { kind: "task", id: task.id },
3727
+ type,
3728
+ });
3729
+ }
2723
3730
  return task;
2724
3731
  }
3732
+ releaseTaskState(task) {
3733
+ if (task.scopeAlive || task.handles.size || !this.tasks.has(task.id))
3734
+ return;
3735
+ this.releaseRaceMembers(task);
3736
+ task.managedLease?.release();
3737
+ task.managedLease = undefined;
3738
+ this.tasks.delete(task.id);
3739
+ task.budget?.changed();
3740
+ }
3741
+ releaseRaceMembers(task) {
3742
+ const members = task.members;
3743
+ if (!members)
3744
+ return;
3745
+ task.members = undefined;
3746
+ task.memberBytes = 0n;
3747
+ task.budget?.changed();
3748
+ for (const value of members) {
3749
+ if (value.kind !== "task")
3750
+ continue;
3751
+ const member = this.tasks.get(value.id);
3752
+ member?.handles.delete(value);
3753
+ this.managed.retire(value);
3754
+ if (member)
3755
+ this.releaseTaskState(member);
3756
+ }
3757
+ }
2725
3758
  taskFor(value) {
2726
3759
  if (value.kind !== "task")
2727
3760
  throw new Error(`expected a task, got ${value.kind}`);
@@ -2734,21 +3767,74 @@ class ExecutorV1 {
2734
3767
  if (task.done)
2735
3768
  return;
2736
3769
  task.done = true;
3770
+ task.resultInitialized = true;
2737
3771
  task.value = value;
2738
3772
  task.fault = fault;
3773
+ task.budget?.changed();
2739
3774
  task.completedAt = ++this.completedTasks;
2740
3775
  for (const changed of [...task.changed])
2741
3776
  changed();
2742
3777
  }
2743
- invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false) {
3778
+ retainTaskResult(task, budget) {
3779
+ if (!budget)
3780
+ return undefined;
3781
+ if (task.fault || (task.actorError && task.actorError !== "Failed"))
3782
+ return undefined;
3783
+ const row = task.managedType == null ? undefined : this.managed.row(task.managedType);
3784
+ if (row?.kind === "actor_call")
3785
+ return budget.retain({
3786
+ value: task.managedResult,
3787
+ type: row.completion,
3788
+ });
3789
+ if (!row || row.kind !== "provider" || row.children[0] == null)
3790
+ throw new Error("task result lacks its canonical payload descriptor");
3791
+ return budget.retain({
3792
+ value: task.managedResult ?? task.value,
3793
+ type: row.children[0],
3794
+ });
3795
+ }
3796
+ managedCompletion(task, value, error, envelope) {
3797
+ if (task.managedType == null ||
3798
+ value === null ||
3799
+ (error !== null && error !== "Failed"))
3800
+ return undefined;
3801
+ const provider = this.managed.row(task.managedType);
3802
+ if (provider.kind !== "actor_call")
3803
+ throw new Error("actor completion has no canonical provider descriptor");
3804
+ const type = provider.completion;
3805
+ const result = this.managed.row(type);
3806
+ if (result.kind !== "variant")
3807
+ throw new Error("actor completion has no canonical result cases");
3808
+ const err = runtimeTag(this.pkg.variants[result.shape], "ResultErr");
3809
+ const failure = this.managed.row(result.cases[err][0]);
3810
+ if (failure.kind !== "variant")
3811
+ throw new Error("actor completion has no canonical error cases");
3812
+ if (!envelope)
3813
+ throw new Error("actor completion lost its actual handler envelope");
3814
+ return {
3815
+ value: this.completion(result.shape, failure.shape, value, error),
3816
+ reply: { value: envelope, type: provider.reply },
3817
+ };
3818
+ }
3819
+ invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false, retainedBy) {
2744
3820
  const context = {
2745
3821
  id: actor?.id ?? "actor:root",
2746
3822
  actor,
3823
+ retainsTransferableFrame: !!retainedBy?.context.generator ||
3824
+ retainedBy?.context.retainsTransferableFrame,
2747
3825
  rcOwner,
2748
- returned,
2749
- failed: (fault) => failed(fault),
3826
+ returned: (value, released) => {
3827
+ retainedBy?.managedChildren.delete(frame);
3828
+ returned(value);
3829
+ released();
3830
+ },
3831
+ failed: (fault) => {
3832
+ retainedBy?.managedChildren.delete(frame);
3833
+ failed(fault);
3834
+ },
2750
3835
  };
2751
3836
  const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
3837
+ retainedBy?.managedChildren.add(frame);
2752
3838
  let cancelled = null;
2753
3839
  context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
2754
3840
  cancelled = fault;
@@ -2840,6 +3926,7 @@ class ExecutorV1 {
2840
3926
  mailbox: [],
2841
3927
  timers: new Set(),
2842
3928
  disposals: [],
3929
+ disposalLeases: [],
2843
3930
  admission: [],
2844
3931
  busy: true,
2845
3932
  constructing: true,
@@ -2883,6 +3970,8 @@ class ExecutorV1 {
2883
3970
  const retained = combineFaults(primary, stateFault);
2884
3971
  this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
2885
3972
  actor.completed = true;
3973
+ actor.stateLease?.release();
3974
+ actor.stateLease = undefined;
2886
3975
  for (const closed of actor.closed.splice(0))
2887
3976
  closed();
2888
3977
  this.raiseFault(act, combineFaults(retained, sweepFault), term.unwind);
@@ -2919,6 +4008,24 @@ class ExecutorV1 {
2919
4008
  else
2920
4009
  started();
2921
4010
  };
4011
+ if (layout.max_heap_bytes != null) {
4012
+ actor.budget = new ManagedBudget(this.managed, BigInt(layout.max_heap_bytes), (root) => this.adoptManaged(root, actor));
4013
+ if (layout.state_type == null)
4014
+ throw new Error("limited actor state lacks a managed descriptor");
4015
+ try {
4016
+ actor.stateLease = actor.budget.retain({
4017
+ value: actor.state,
4018
+ type: layout.state_type,
4019
+ slots: true,
4020
+ });
4021
+ }
4022
+ catch (error) {
4023
+ if (!(error instanceof HeapExceeded))
4024
+ throw error;
4025
+ failed({ kind: "trap", trap: "heap_exceeded" });
4026
+ return;
4027
+ }
4028
+ }
2922
4029
  if (layout.init != null)
2923
4030
  actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
2924
4031
  else
@@ -3026,7 +4133,9 @@ class ExecutorV1 {
3026
4133
  complete: (_value, _error, drained) => drained?.(null),
3027
4134
  };
3028
4135
  const status = this.admitActorMessage(actor, message);
3029
- if (status === "full" || status === "closed") {
4136
+ if (status === "full" ||
4137
+ status === "closed" ||
4138
+ status === "trapped") {
3030
4139
  actor.disposals.push(payload);
3031
4140
  timer.discarded();
3032
4141
  this.dispatchActor(actor);
@@ -3064,7 +4173,21 @@ class ExecutorV1 {
3064
4173
  const handler = actor.layout.handlers.find((handler) => handler.message_id === operation.message && handler.streams);
3065
4174
  if (!handler)
3066
4175
  throw new Error("stream request has no producer handler");
4176
+ let managedLease;
4177
+ try {
4178
+ managedLease = this.acquireActorMessage(actor, handler, fields);
4179
+ }
4180
+ catch (error) {
4181
+ if (!(error instanceof HeapExceeded))
4182
+ throw error;
4183
+ this.crashActor(actor, { kind: "trap", trap: "heap_exceeded" }, handler);
4184
+ this.closeValue(payload, error.message);
4185
+ this.completeShim(act, term, UNIT);
4186
+ this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
4187
+ return;
4188
+ }
3067
4189
  actor.mailbox.push({
4190
+ managedLease,
3068
4191
  caller,
3069
4192
  handler,
3070
4193
  payload: fields,
@@ -3085,8 +4208,29 @@ class ExecutorV1 {
3085
4208
  return;
3086
4209
  }
3087
4210
  case "call_start": {
3088
- const task = this.newTask();
3089
- const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault) => {
4211
+ const task = this.newTask(act.context.actor?.budget, term.result && term.result !== "never"
4212
+ ? (term.result.managed_type ?? undefined)
4213
+ : undefined);
4214
+ const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope) => {
4215
+ const reply = this.managedCompletion(task, value, error, envelope);
4216
+ if (reply && !closeFault && act.context.actor?.budget) {
4217
+ try {
4218
+ act.context.actor.budget.checkPayload(this.managed.width(reply.reply.type), [reply.reply]);
4219
+ }
4220
+ catch (failure) {
4221
+ if (!(failure instanceof HeapExceeded))
4222
+ throw failure;
4223
+ task.actorError = "Trapped";
4224
+ this.settleTask(task, UNIT, {
4225
+ kind: "trap",
4226
+ trap: "heap_exceeded",
4227
+ });
4228
+ this.failActorBudget(act.context.actor);
4229
+ return false;
4230
+ }
4231
+ }
4232
+ task.managedResult = reply?.value;
4233
+ task.managedReply = reply?.reply.value;
3090
4234
  task.actorError = error;
3091
4235
  this.settleTask(task, value ?? UNIT, closeFault ?? null);
3092
4236
  return true;
@@ -3108,8 +4252,20 @@ class ExecutorV1 {
3108
4252
  throw new Error("actor completion taken before readiness");
3109
4253
  act.fault ??= task.fault;
3110
4254
  const value = task.value;
4255
+ const lease = this.retainTaskResult(task, act.context.actor?.budget);
4256
+ if (lease)
4257
+ act.managedInputs.push(lease);
3111
4258
  task.value = UNIT;
3112
- this.completeShim(act, term, this.completion(term.result_shape, term.error_shape, value, task.actorError));
4259
+ task.taken = true;
4260
+ const result = task.managedResult ??
4261
+ this.completion(term.result_shape, term.error_shape, value, task.actorError);
4262
+ task.managedResult = undefined;
4263
+ task.managedReply = undefined;
4264
+ this.completeShim(act, term, result);
4265
+ task.handles.delete(args[0]);
4266
+ this.managed.retire(args[0]);
4267
+ task.budget?.changed();
4268
+ this.releaseTaskState(task);
3113
4269
  return;
3114
4270
  }
3115
4271
  case "submit": {
@@ -3162,6 +4318,15 @@ class ExecutorV1 {
3162
4318
  complete: (_value, _error, drained) => drained?.(null),
3163
4319
  };
3164
4320
  const status = this.admitActorMessage(actor, message);
4321
+ if (status === "trapped") {
4322
+ this.closeValueAsync(payload, "actor heap limit exceeded", (fault) => {
4323
+ act.fault ??= fault;
4324
+ finish(this.variant(term.result_shape, "ResultErr", [
4325
+ this.variant(term.error_shape, "ActorErrorTrapped"),
4326
+ ]));
4327
+ }, act.context.actor, act.rcOwner);
4328
+ return;
4329
+ }
3165
4330
  if (status === "full") {
3166
4331
  if (operation.policy === "drop_newest") {
3167
4332
  let settled = false;
@@ -3463,6 +4628,7 @@ class ExecutorV1 {
3463
4628
  let admitted = false;
3464
4629
  let closing = false;
3465
4630
  const owner = this.current.context.actor;
4631
+ const sender = this.current;
3466
4632
  const rcOwner = this.current.rcOwner;
3467
4633
  const deadline = this.requestDeadline(this.current);
3468
4634
  let cancelTimer;
@@ -3471,11 +4637,12 @@ class ExecutorV1 {
3471
4637
  ? { kind: "Actor", identity: owner.identity }
3472
4638
  : { kind: "Task" };
3473
4639
  let receiver = null;
3474
- const finish = (value, error, drained) => {
4640
+ const finish = (value, error, drained, envelope) => {
3475
4641
  const alreadySettled = settled;
3476
4642
  settled = true;
3477
4643
  cancelTimer?.();
3478
- if (alreadySettled || complete(value, error) === false) {
4644
+ if (alreadySettled ||
4645
+ complete(value, error, undefined, envelope) === false) {
3479
4646
  // The finishing receiver still owns a reply that its caller no
3480
4647
  // longer accepts. Its next turn must wait for this release.
3481
4648
  this.closeValueAsync(value ?? UNIT, null, (fault) => {
@@ -3582,9 +4749,23 @@ class ExecutorV1 {
3582
4749
  }
3583
4750
  return;
3584
4751
  }
4752
+ let managedLease;
4753
+ try {
4754
+ managedLease = this.acquireActorMessage(actor, handler, payload);
4755
+ }
4756
+ catch (error) {
4757
+ if (!(error instanceof HeapExceeded))
4758
+ throw error;
4759
+ this.failActorBudget(actor);
4760
+ reject("Trapped");
4761
+ return;
4762
+ }
3585
4763
  admitted = true;
4764
+ for (const lease of sender.managedInputs.splice(0))
4765
+ lease.release();
3586
4766
  receiver = actor;
3587
4767
  message = {
4768
+ managedLease,
3588
4769
  caller,
3589
4770
  handler,
3590
4771
  payload,
@@ -3607,6 +4788,8 @@ class ExecutorV1 {
3607
4788
  if (index < 0)
3608
4789
  return;
3609
4790
  receiver.mailbox.splice(index, 1);
4791
+ message.managedLease?.release();
4792
+ message.managedLease = undefined;
3610
4793
  admitted = false;
3611
4794
  for (const admit of receiver.admission.splice(0))
3612
4795
  admit();
@@ -3634,13 +4817,56 @@ class ExecutorV1 {
3634
4817
  closeRequest(done);
3635
4818
  };
3636
4819
  }
4820
+ acquireActorMessage(actor, handler, payload) {
4821
+ const params = this.functionAt(handler.callable).params.slice(1);
4822
+ if (params.length !== payload.length)
4823
+ throw new Error("typed message payload differs from its handler");
4824
+ if (!actor.budget &&
4825
+ !this.pipes.hasManagedReceivers &&
4826
+ params.every((param) => param.managed_type == null ||
4827
+ !this.managed.needsAdoption(param.managed_type)))
4828
+ return undefined;
4829
+ const roots = params.flatMap((param, index) => {
4830
+ if (param.managed_type == null) {
4831
+ if (!actor.budget)
4832
+ return [];
4833
+ throw new Error("message parameter lacks its managed descriptor");
4834
+ }
4835
+ return [
4836
+ {
4837
+ value: payload[index],
4838
+ type: param.managed_type,
4839
+ slots: true,
4840
+ },
4841
+ ];
4842
+ });
4843
+ if (actor.budget)
4844
+ return actor.budget.retainMany(roots);
4845
+ for (const root of roots)
4846
+ this.adoptManaged(root, actor);
4847
+ return undefined;
4848
+ }
3637
4849
  admitActorMessage(actor, message) {
3638
4850
  if (!actor.alive || actor.closing)
3639
4851
  return "closed";
3640
4852
  const full = actor.layout.mailbox_capacity != null &&
3641
4853
  actor.mailbox.length >= actor.layout.mailbox_capacity;
3642
4854
  let declared = full ? actor.layout.overflow : null;
4855
+ const acquire = () => {
4856
+ try {
4857
+ message.managedLease = this.acquireActorMessage(actor, message.handler, message.payload);
4858
+ }
4859
+ catch (error) {
4860
+ if (!(error instanceof HeapExceeded))
4861
+ throw error;
4862
+ this.failActorBudget(actor);
4863
+ return false;
4864
+ }
4865
+ return true;
4866
+ };
3643
4867
  const discard = (lost) => {
4868
+ if (lost.managedLease)
4869
+ actor.disposalLeases.push(lost.managedLease);
3644
4870
  actor.disposals.push({
3645
4871
  kind: "record",
3646
4872
  typeId: "",
@@ -3658,6 +4884,8 @@ class ExecutorV1 {
3658
4884
  equals(queued.payload[key.param], message.payload[key.param]))
3659
4885
  : -1;
3660
4886
  if (queued >= 0) {
4887
+ if (!acquire())
4888
+ return "trapped";
3661
4889
  const superseded = actor.mailbox[queued];
3662
4890
  actor.mailbox[queued] = message;
3663
4891
  discard(superseded);
@@ -3673,6 +4901,8 @@ class ExecutorV1 {
3673
4901
  ? actor.mailbox.findIndex((queued) => !queued.obligated)
3674
4902
  : -1;
3675
4903
  if (oldest >= 0) {
4904
+ if (!acquire())
4905
+ return "trapped";
3676
4906
  const [evicted] = actor.mailbox.splice(oldest, 1);
3677
4907
  actor.mailbox.push(message);
3678
4908
  discard(evicted);
@@ -3680,6 +4910,8 @@ class ExecutorV1 {
3680
4910
  }
3681
4911
  if (full)
3682
4912
  return "full";
4913
+ if (!acquire())
4914
+ return "trapped";
3683
4915
  actor.mailbox.push(message);
3684
4916
  this.trace.snapshot("actor.send", {
3685
4917
  actor_id: actor.id,
@@ -3772,7 +5004,9 @@ class ExecutorV1 {
3772
5004
  if (actor.disposals.length > 0) {
3773
5005
  actor.busy = true;
3774
5006
  const values = actor.disposals.splice(0);
5007
+ const leases = actor.disposalLeases.splice(0);
3775
5008
  this.closeValueAsync({ kind: "vector", elementType: "message", items: values }, this.faultText(actor.crashing), (fault) => {
5009
+ leases.forEach((lease) => lease.release());
3776
5010
  actor.busy = false;
3777
5011
  if (fault)
3778
5012
  this.crashActor(actor, fault);
@@ -3816,6 +5050,8 @@ class ExecutorV1 {
3816
5050
  return;
3817
5051
  }
3818
5052
  actor.mailbox.shift();
5053
+ message.managedLease?.release();
5054
+ message.managedLease = undefined;
3819
5055
  for (const admit of actor.admission.splice(0))
3820
5056
  admit();
3821
5057
  if (message.requestDeadline !== undefined &&
@@ -3841,10 +5077,21 @@ class ExecutorV1 {
3841
5077
  caller: message.caller,
3842
5078
  requestDeadline: message.requestDeadline,
3843
5079
  reply: message.reply,
3844
- returned: (value) => {
5080
+ returned: (value, released) => {
3845
5081
  unwatchStream();
3846
5082
  actor.active = null;
5083
+ if (actor.crashing?.kind === "trap" &&
5084
+ actor.crashing.trap === "heap_exceeded") {
5085
+ const fault = actor.crashing;
5086
+ this.closeValueAsync(value, this.faultText(fault), (closeFault) => {
5087
+ released();
5088
+ this.crashActor(actor, combineFaults(fault, closeFault), message.handler);
5089
+ message.complete(null, "Trapped");
5090
+ }, actor, actor.rcOwner);
5091
+ return;
5092
+ }
3847
5093
  const drained = (fault) => {
5094
+ released();
3848
5095
  actor.busy = false;
3849
5096
  if (fault)
3850
5097
  this.crashActor(actor, fault, message.handler);
@@ -3859,7 +5106,7 @@ class ExecutorV1 {
3859
5106
  const shape = this.pkg.variants[message.handler.result_shape];
3860
5107
  if (shape?.cases[value.tag]?.name === "Err") {
3861
5108
  if (message.reply)
3862
- message.complete(value.payload[0] ?? UNIT, "Failed", drained);
5109
+ message.complete(value.payload[0] ?? UNIT, "Failed", drained, value);
3863
5110
  else {
3864
5111
  this.crashActor(actor, {
3865
5112
  kind: "panic",
@@ -3870,11 +5117,11 @@ class ExecutorV1 {
3870
5117
  }
3871
5118
  }
3872
5119
  else {
3873
- message.complete(value.payload[0] ?? UNIT, null, drained);
5120
+ message.complete(value.payload[0] ?? UNIT, null, drained, value);
3874
5121
  }
3875
5122
  }
3876
5123
  else {
3877
- message.complete(value, null, drained);
5124
+ message.complete(value, null, drained, value);
3878
5125
  }
3879
5126
  },
3880
5127
  failed: (fault) => {
@@ -3950,6 +5197,8 @@ class ExecutorV1 {
3950
5197
  this.reportFault(actor.closingFault);
3951
5198
  }
3952
5199
  actor.completed = true;
5200
+ actor.stateLease?.release();
5201
+ actor.stateLease = undefined;
3953
5202
  for (const wake of actor.closed.splice(0))
3954
5203
  wake();
3955
5204
  for (const admit of actor.admission.splice(0))
@@ -3983,11 +5232,13 @@ class ExecutorV1 {
3983
5232
  kind: "enum",
3984
5233
  typeId: this.variantName(actor.layout.crash_kind),
3985
5234
  shape: actor.layout.crash_kind,
3986
- tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 214
3987
- ? "CrashKindTimerOverdue"
3988
- : code === 213
3989
- ? "CrashKindHandlerFailed"
3990
- : "CrashKindFault"),
5235
+ tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 200
5236
+ ? "CrashKindHeapExceeded"
5237
+ : code === 214
5238
+ ? "CrashKindTimerOverdue"
5239
+ : code === 213
5240
+ ? "CrashKindHandlerFailed"
5241
+ : "CrashKindFault"),
3991
5242
  payload: [],
3992
5243
  },
3993
5244
  stringValue(String(message)),
@@ -4372,6 +5623,10 @@ class ExecutorV1 {
4372
5623
  }
4373
5624
  drainMailbox(actor, fault, done) {
4374
5625
  const messages = actor.mailbox.splice(0);
5626
+ const leases = [
5627
+ ...actor.disposalLeases.splice(0),
5628
+ ...messages.flatMap((message) => message.managedLease ? [message.managedLease] : []),
5629
+ ];
4375
5630
  actor.drainingMailbox = true;
4376
5631
  this.closeValueAsync({
4377
5632
  kind: "vector",
@@ -4385,6 +5640,7 @@ class ExecutorV1 {
4385
5640
  })),
4386
5641
  ],
4387
5642
  }, fault, (closeFault) => {
5643
+ leases.forEach((lease) => lease.release());
4388
5644
  actor.drainingMailbox = false;
4389
5645
  for (const message of messages)
4390
5646
  message.complete(null, "Dead");
@@ -4464,6 +5720,8 @@ class ExecutorV1 {
4464
5720
  }
4465
5721
  actor.completed = true;
4466
5722
  actor.alive = false;
5723
+ actor.stateLease?.release();
5724
+ actor.stateLease = undefined;
4467
5725
  actor.busy = false;
4468
5726
  this.trace.snapshot(actor.terminating ? "actor.terminate" : "actor.stop", { actor_id: actor.id });
4469
5727
  this.settleSupervisedEnd(actor, actor.closingFault
@@ -4576,6 +5834,7 @@ function faultInfo(fault) {
4576
5834
  shift_out_of_range: [204, "ShiftOutOfRange"],
4577
5835
  vector_bounds: [205, "IndexOutOfBounds"],
4578
5836
  actor_refused: [-4, "Refused"],
5837
+ heap_exceeded: [200, "HeapExceeded"],
4579
5838
  }[fault.trap];
4580
5839
  }
4581
5840
  function faultApplicationMessage(fault) {
@@ -4606,12 +5865,30 @@ function faultReport(fault, secondary = false) {
4606
5865
  (fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
4607
5866
  }
4608
5867
  // ── refs ────────────────────────────────────────────────────────────────────
4609
- function ownedRef(value) {
4610
- return { kind: "cell", cell: { value, valid: true } };
5868
+ function ownedRef(value, budget, type) {
5869
+ if (budget && type == null)
5870
+ throw new Error("owned value has no managed descriptor");
5871
+ return {
5872
+ kind: "cell",
5873
+ cell: {
5874
+ value,
5875
+ valid: true,
5876
+ budget,
5877
+ type,
5878
+ lease: budget?.retain({ value, type: type }),
5879
+ },
5880
+ };
4611
5881
  }
4612
5882
  function writeRef(ref, value) {
4613
5883
  switch (ref.kind) {
4614
5884
  case "cell":
5885
+ if (ref.cell.budget) {
5886
+ if (ref.cell.type == null)
5887
+ throw new Error("owned place has no managed descriptor");
5888
+ const lease = ref.cell.budget.retain({ value, type: ref.cell.type });
5889
+ ref.cell.lease?.release();
5890
+ ref.cell.lease = lease;
5891
+ }
4615
5892
  ref.cell.value = value;
4616
5893
  ref.cell.valid = true;
4617
5894
  return;
@@ -4626,8 +5903,11 @@ function writeRef(ref, value) {
4626
5903
  /// Only a cell of its own can be ended; a projection lives as long as the
4627
5904
  /// storage it reaches through.
4628
5905
  function invalidateRef(ref) {
4629
- if (ref.kind === "cell")
5906
+ if (ref.kind === "cell") {
4630
5907
  ref.cell.valid = false;
5908
+ ref.cell.lease?.release();
5909
+ ref.cell.lease = undefined;
5910
+ }
4631
5911
  else
4632
5912
  writeRef(ref, UNIT);
4633
5913
  }
@@ -4734,11 +6014,7 @@ function truth(value) {
4734
6014
  : value.kind === "i64" && value.value !== 0n;
4735
6015
  }
4736
6016
  function bytesValue(bytes) {
4737
- return {
4738
- kind: "vector",
4739
- elementType: "u8",
4740
- items: bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })),
4741
- };
6017
+ return byteVectorValue(bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })));
4742
6018
  }
4743
6019
  function renderMessage(value) {
4744
6020
  return value.kind === "string" ? value.value : canonical(value);
@@ -4804,6 +6080,8 @@ function trapMessage(trap) {
4804
6080
  return "index out of bounds";
4805
6081
  case "actor_refused":
4806
6082
  return "delivery refused by interceptor";
6083
+ case "heap_exceeded":
6084
+ return "actor heap limit exceeded";
4807
6085
  }
4808
6086
  }
4809
6087
  /// The operand type the package records. `isize` and `usize` are 64-bit in the