@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.
- package/dist/interpreter/run-program.js +5 -0
- package/dist/interpreter/strings.d.ts +6 -0
- package/dist/interpreter/strings.js +8 -0
- package/dist/interpreter/types.d.ts +1 -1
- package/dist/interpreter/v1/browser-directory.js +2 -2
- package/dist/interpreter/v1/codecs.js +2 -2
- package/dist/interpreter/v1/encoding-storage.d.ts +33 -0
- package/dist/interpreter/v1/encoding-storage.js +113 -0
- package/dist/interpreter/v1/encoding-values.js +28 -18
- package/dist/interpreter/v1/exec.js +1506 -130
- package/dist/interpreter/v1/instructions.generated.d.ts +100 -8
- package/dist/interpreter/v1/instructions.generated.js +1 -0
- package/dist/interpreter/v1/managed-budget.d.ts +88 -0
- package/dist/interpreter/v1/managed-budget.js +561 -0
- package/dist/interpreter/v1/package-graph.js +77 -2
- package/dist/interpreter/v1/package-validator.generated.js +22796 -17531
- package/dist/interpreter/v1/package.d.ts +4 -2
- package/dist/interpreter/v1/package.js +6 -0
- package/dist/interpreter/v1/pipes.d.ts +24 -1
- package/dist/interpreter/v1/pipes.js +91 -8
- package/dist/interpreter/v1/shims.d.ts +1 -0
- package/dist/interpreter/v1/shims.js +88 -62
- package/dist/interpreter/v1/structural.js +4 -2
- package/dist/interpreter/v1/validate.js +4 -2
- package/dist/interpreter/v1/value-operations.d.ts +3 -1
- package/dist/interpreter/v1/value-operations.js +32 -18
- package/dist/interpreter/values.d.ts +45 -11
- package/dist/interpreter/values.js +176 -8
- package/package.json +4 -4
|
@@ -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, vectorItems, vectorLength, vectorElement, 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,8 +23,14 @@ 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;
|
|
29
|
+
/// Deepest activation a task may reach below its root before a call fails
|
|
30
|
+
/// with `StackOverflow`. HEW-SPEC-2026 §2.2 guarantees 10,000 frames. Twice
|
|
31
|
+
/// that matches what a native stack admits for small frames and leaves an
|
|
32
|
+
/// unbounded recursion failing well inside the default step budget.
|
|
33
|
+
const MAX_FRAME_DEPTH = 20_000;
|
|
27
34
|
/// Ends the run. The status is already recorded on the trace.
|
|
28
35
|
class Halt extends Error {
|
|
29
36
|
status;
|
|
@@ -119,6 +126,7 @@ class ExecutorV1 {
|
|
|
119
126
|
tasks = new Map();
|
|
120
127
|
completedTasks = 0;
|
|
121
128
|
actorIdentity = 0n;
|
|
129
|
+
arenaIdentity = 0n;
|
|
122
130
|
faultDebts = new Set();
|
|
123
131
|
nextFaultDebt = 0;
|
|
124
132
|
closingResources = new WeakSet();
|
|
@@ -128,6 +136,7 @@ class ExecutorV1 {
|
|
|
128
136
|
terminating = false;
|
|
129
137
|
rootFault = null;
|
|
130
138
|
pkg;
|
|
139
|
+
managed;
|
|
131
140
|
directories;
|
|
132
141
|
semaphores;
|
|
133
142
|
constructor(admission, trace, stdin, policy, options) {
|
|
@@ -136,12 +145,14 @@ class ExecutorV1 {
|
|
|
136
145
|
this.options = options;
|
|
137
146
|
const pkg = admission.bytecode;
|
|
138
147
|
this.pkg = pkg;
|
|
148
|
+
this.managed = new ManagedLayouts(pkg.managed_types ?? [], (value, type) => this.managedRetained(value, type));
|
|
139
149
|
this.directories = new BrowserDirectories(options.directoryRoots);
|
|
140
150
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
141
151
|
this.semaphores = new Semaphores((nanos, ready) => this.scheduler.after(nanos, ready));
|
|
142
152
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
143
153
|
this.host = {
|
|
144
154
|
argv: [...(options.argv ?? ["main.hew"])],
|
|
155
|
+
arenaInstanceId: () => this.arenaIdentity === 0x7fffffffffffffffn ? 0n : ++this.arenaIdentity,
|
|
145
156
|
unixTimeMillis: () => this.trace.virtualTimeMs,
|
|
146
157
|
monotonicTimeNanos: () => {
|
|
147
158
|
const millis = this.trace.virtualTimeMs;
|
|
@@ -189,6 +200,7 @@ class ExecutorV1 {
|
|
|
189
200
|
pending: null,
|
|
190
201
|
fault: null,
|
|
191
202
|
receiving: false,
|
|
203
|
+
observers: new Set(),
|
|
192
204
|
});
|
|
193
205
|
return { kind: "stream", channelId: id };
|
|
194
206
|
},
|
|
@@ -226,12 +238,13 @@ class ExecutorV1 {
|
|
|
226
238
|
this.current = this.activate(this.functionAt(pkg.entry.function), [], null, null, undefined, null, {
|
|
227
239
|
id: "actor:root",
|
|
228
240
|
actor: null,
|
|
229
|
-
returned: (value) => {
|
|
241
|
+
returned: (value, released) => {
|
|
230
242
|
this.completeRoot();
|
|
231
243
|
this.trace.exitCode =
|
|
232
244
|
pkg.entry?.exit === "status" && value.kind === "i64"
|
|
233
245
|
? Number(value.value)
|
|
234
246
|
: 0;
|
|
247
|
+
released();
|
|
235
248
|
},
|
|
236
249
|
failed: (fault) => {
|
|
237
250
|
this.rootFault = fault;
|
|
@@ -288,6 +301,7 @@ class ExecutorV1 {
|
|
|
288
301
|
this.running = true;
|
|
289
302
|
while (this.running) {
|
|
290
303
|
const act = this.current;
|
|
304
|
+
act.context.activeFrame = act;
|
|
291
305
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
292
306
|
if (this.scopeDeadline(act, fault) !== fault)
|
|
293
307
|
act.context.pendingCancellation ??= fault;
|
|
@@ -337,29 +351,42 @@ class ExecutorV1 {
|
|
|
337
351
|
: parentContext;
|
|
338
352
|
const env = new Map();
|
|
339
353
|
for (const [at, param] of fn.params.entries()) {
|
|
340
|
-
env.set(param.value, ownedRef(args[at] ?? UNIT));
|
|
354
|
+
env.set(param.value, ownedRef(args[at] ?? UNIT, param.own === "owned" ? executionContext.actor?.budget : undefined, param.managed_type ?? undefined));
|
|
341
355
|
}
|
|
342
356
|
const places = new Map();
|
|
343
357
|
const trivial = new Set();
|
|
358
|
+
const owned = new Set();
|
|
359
|
+
const managedTypes = new Map();
|
|
344
360
|
const record = (value) => {
|
|
345
361
|
if (value.own === "none")
|
|
346
362
|
trivial.add(value.value);
|
|
363
|
+
if (value.own === "owned")
|
|
364
|
+
owned.add(value.value);
|
|
365
|
+
if (value.managed_type != null)
|
|
366
|
+
managedTypes.set(value.value, value.managed_type);
|
|
347
367
|
};
|
|
348
368
|
fn.params.forEach(record);
|
|
349
369
|
for (const block of fn.blocks) {
|
|
350
370
|
block.params.forEach(record);
|
|
351
|
-
for (const op of block
|
|
371
|
+
for (const op of staticOperations(block)) {
|
|
372
|
+
op.definitions?.forEach(record);
|
|
352
373
|
if ("dst" in op && op.dst != null && op.own === "none")
|
|
353
374
|
trivial.add(op.dst);
|
|
354
375
|
}
|
|
355
376
|
const term = block.term;
|
|
356
377
|
if ("result" in term && term.result && term.result !== "never")
|
|
357
378
|
record(term.result);
|
|
379
|
+
if (term.op === "call" && term.handback)
|
|
380
|
+
record(term.handback);
|
|
358
381
|
if (term.op === "switch.variant")
|
|
359
382
|
for (const arm of term.arms)
|
|
360
383
|
arm.fields.forEach(record);
|
|
361
384
|
}
|
|
385
|
+
for (const [index, param] of fn.params.entries())
|
|
386
|
+
if (param.managed_type != null && args[index])
|
|
387
|
+
this.stampManaged(args[index], param.managed_type, executionContext.actor);
|
|
362
388
|
return {
|
|
389
|
+
accountActor: executionContext.actor,
|
|
363
390
|
rcOwner: fn.actor_construction
|
|
364
391
|
? { blocks: new Set(), closed: false }
|
|
365
392
|
: (caller?.rcOwner ??
|
|
@@ -372,11 +399,17 @@ class ExecutorV1 {
|
|
|
372
399
|
opIndex: 0,
|
|
373
400
|
env,
|
|
374
401
|
trivial,
|
|
402
|
+
owned,
|
|
403
|
+
managedTypes,
|
|
404
|
+
managedInputs: [],
|
|
405
|
+
managedCleanup: new Set(),
|
|
406
|
+
managedChildren: new Set(),
|
|
375
407
|
scopes: new Map(),
|
|
376
408
|
places,
|
|
377
409
|
fault: null,
|
|
378
410
|
parked: [],
|
|
379
411
|
caller,
|
|
412
|
+
depth: caller ? caller.depth + 1 : 0,
|
|
380
413
|
result,
|
|
381
414
|
normal: normal ?? undefined,
|
|
382
415
|
unwind,
|
|
@@ -414,7 +447,11 @@ class ExecutorV1 {
|
|
|
414
447
|
}
|
|
415
448
|
read(act, id) {
|
|
416
449
|
try {
|
|
417
|
-
|
|
450
|
+
const value = readRef(this.refOf(act, id));
|
|
451
|
+
const type = act.managedTypes.get(id);
|
|
452
|
+
if (type != null)
|
|
453
|
+
this.stampManaged(value, type, act.context.actor);
|
|
454
|
+
return value;
|
|
418
455
|
}
|
|
419
456
|
catch (error) {
|
|
420
457
|
throw new Error(`${act.fn.name} block ${act.block.id} value ${id}: ${error.message}`);
|
|
@@ -424,7 +461,416 @@ class ExecutorV1 {
|
|
|
424
461
|
if (id === null || id === undefined) {
|
|
425
462
|
return;
|
|
426
463
|
}
|
|
427
|
-
act.
|
|
464
|
+
const type = act.managedTypes.get(id);
|
|
465
|
+
if (type != null)
|
|
466
|
+
this.stampManaged(value, type, act.context.actor);
|
|
467
|
+
const budget = act.owned.has(id) ? act.context.actor?.budget : undefined;
|
|
468
|
+
act.env.set(id, ownedRef(value, budget, type));
|
|
469
|
+
}
|
|
470
|
+
stampManaged(value, type, actor) {
|
|
471
|
+
const budget = actor?.budget;
|
|
472
|
+
this.managed.stamp(value, type);
|
|
473
|
+
if (value.kind === "rc" &&
|
|
474
|
+
!value.closed &&
|
|
475
|
+
value.cell?.owner &&
|
|
476
|
+
budget &&
|
|
477
|
+
!value.cell.managedLease) {
|
|
478
|
+
const row = this.managed.row(type);
|
|
479
|
+
if (row.kind !== "shared")
|
|
480
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
481
|
+
value.cell.managedPayload = row.payload;
|
|
482
|
+
value.cell.managedLease = budget.retain({
|
|
483
|
+
value: { kind: "rc", cell: value.cell, closed: false },
|
|
484
|
+
type,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (value.kind === "task") {
|
|
488
|
+
const task = this.taskFor(value);
|
|
489
|
+
task.managedType = type;
|
|
490
|
+
task.handles.add(value);
|
|
491
|
+
}
|
|
492
|
+
if (value.kind === "stream" && !this.generatorStreams.has(value.channelId))
|
|
493
|
+
this.bindManagedStream(value, type, actor);
|
|
494
|
+
if (value.kind === "generator")
|
|
495
|
+
this.generatorFor(value).managedType = type;
|
|
496
|
+
if (value.kind === "encoding") {
|
|
497
|
+
value.prepareMutation = budget
|
|
498
|
+
? (replacement, detached) => budget.checkMutation(value, replacement, detached)
|
|
499
|
+
: undefined;
|
|
500
|
+
}
|
|
501
|
+
else if (value.kind === "string") {
|
|
502
|
+
value.prepareGrowth = budget
|
|
503
|
+
? (capacity, detached) => budget.checkStringGrowth(value, capacity, detached)
|
|
504
|
+
: undefined;
|
|
505
|
+
}
|
|
506
|
+
else if (value.kind === "map") {
|
|
507
|
+
value.prepareInsert = budget
|
|
508
|
+
? (staged, slot, key, item, copyKey) => {
|
|
509
|
+
const row = this.managed.row(type);
|
|
510
|
+
if (row.kind !== "map" && row.kind !== "set")
|
|
511
|
+
throw new Error("map has no canonical entry descriptor");
|
|
512
|
+
const entries = new Map(staged.entries);
|
|
513
|
+
entries.set(slot, {
|
|
514
|
+
key: copyKey
|
|
515
|
+
? this.managed.copy(key, row.kind === "map" ? row.key : row.element)
|
|
516
|
+
: key,
|
|
517
|
+
value: item,
|
|
518
|
+
});
|
|
519
|
+
budget.checkMutation(value, { ...staged, entries });
|
|
520
|
+
}
|
|
521
|
+
: undefined;
|
|
522
|
+
}
|
|
523
|
+
else if (value.kind === "vector") {
|
|
524
|
+
value.prepareMutation = budget
|
|
525
|
+
? (capacity, detached, appended) => {
|
|
526
|
+
const row = this.managed.row(type);
|
|
527
|
+
const storage = vectorStorage(value);
|
|
528
|
+
let items = detached &&
|
|
529
|
+
(row.kind === "vector" ||
|
|
530
|
+
row.kind === "array" ||
|
|
531
|
+
row.kind === "slice")
|
|
532
|
+
? vectorItems(value).map((item) => this.managed.copy(item, row.element))
|
|
533
|
+
: vectorItems(value);
|
|
534
|
+
if (appended?.length) {
|
|
535
|
+
if (row.kind !== "vector" &&
|
|
536
|
+
row.kind !== "array" &&
|
|
537
|
+
row.kind !== "slice")
|
|
538
|
+
throw new Error("vector append lacks its canonical element type");
|
|
539
|
+
items = [
|
|
540
|
+
...items,
|
|
541
|
+
...appended.map((item) => this.managed.copy(item, row.element)),
|
|
542
|
+
];
|
|
543
|
+
}
|
|
544
|
+
budget.checkMutation(value, {
|
|
545
|
+
...value,
|
|
546
|
+
items,
|
|
547
|
+
offset: 0,
|
|
548
|
+
length: undefined,
|
|
549
|
+
storage: { ...storage, capacity, items, owners: 1 },
|
|
550
|
+
}, detached);
|
|
551
|
+
}
|
|
552
|
+
: undefined;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
frameManagedRoots(frame) {
|
|
556
|
+
const roots = [];
|
|
557
|
+
for (const [id, ref] of frame.env) {
|
|
558
|
+
const type = frame.managedTypes.get(id);
|
|
559
|
+
if (type != null && frame.owned.has(id) && liveRef(ref))
|
|
560
|
+
roots.push({ value: readRef(ref), type });
|
|
561
|
+
}
|
|
562
|
+
for (const [id, cell] of frame.places) {
|
|
563
|
+
const type = frame.fn.places[id]?.managed_type;
|
|
564
|
+
if (type != null && cell.valid)
|
|
565
|
+
roots.push({ value: cell.value, type });
|
|
566
|
+
}
|
|
567
|
+
for (const scope of frame.scopes.values())
|
|
568
|
+
for (const task of scope.tasks)
|
|
569
|
+
if (task.managedType != null)
|
|
570
|
+
roots.push({
|
|
571
|
+
value: { kind: "task", id: task.id },
|
|
572
|
+
type: task.managedType,
|
|
573
|
+
});
|
|
574
|
+
for (const lease of frame.managedInputs)
|
|
575
|
+
roots.push(...(lease.roots ?? []));
|
|
576
|
+
for (const cleanup of frame.managedCleanup)
|
|
577
|
+
roots.push(...cleanup.roots);
|
|
578
|
+
for (const child of frame.managedChildren)
|
|
579
|
+
roots.push(...this.frameManagedRoots(child.context.activeFrame ?? child));
|
|
580
|
+
if (frame.caller)
|
|
581
|
+
roots.push(...this.frameManagedRoots(frame.caller));
|
|
582
|
+
return roots;
|
|
583
|
+
}
|
|
584
|
+
bindManagedStream(value, type, actor) {
|
|
585
|
+
const row = this.managed.row(type);
|
|
586
|
+
if (row.kind !== "provider" ||
|
|
587
|
+
row.provider !== "stream" ||
|
|
588
|
+
value.kind !== "stream" ||
|
|
589
|
+
this.generatorStreams.has(value.channelId))
|
|
590
|
+
return;
|
|
591
|
+
const budget = actor?.budget;
|
|
592
|
+
this.pipes.bind(value, budget && actor
|
|
593
|
+
? {
|
|
594
|
+
owner: actor,
|
|
595
|
+
acquire: (envelope) => {
|
|
596
|
+
const lease = budget.retain({
|
|
597
|
+
value: envelope.value,
|
|
598
|
+
type: row.children[0],
|
|
599
|
+
slots: true,
|
|
600
|
+
identity: envelope,
|
|
601
|
+
});
|
|
602
|
+
return () => lease.release();
|
|
603
|
+
},
|
|
604
|
+
refused: () => this.failActorBudget(actor),
|
|
605
|
+
}
|
|
606
|
+
: undefined);
|
|
607
|
+
}
|
|
608
|
+
adoptManaged(root, actor) {
|
|
609
|
+
if (!this.managed.needsAdoption(root.type))
|
|
610
|
+
return;
|
|
611
|
+
this.managed.collect(root, new Map(), undefined, undefined, (value, type) => {
|
|
612
|
+
this.bindManagedStream(value, type, actor);
|
|
613
|
+
const row = this.managed.row(type);
|
|
614
|
+
if (value.kind === "rc" && value.cell && !value.closed) {
|
|
615
|
+
if (row.kind !== "shared")
|
|
616
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
617
|
+
const cell = value.cell;
|
|
618
|
+
if (cell.owner !== actor.rcOwner && !cell.dying) {
|
|
619
|
+
cell.owner?.blocks.delete(cell);
|
|
620
|
+
cell.owner = actor.rcOwner;
|
|
621
|
+
cell.managedPayload = row.payload;
|
|
622
|
+
actor.rcOwner.blocks.add(cell);
|
|
623
|
+
cell.managedLease = this.rehomeLease(cell.managedLease, actor.budget);
|
|
624
|
+
if (!cell.managedLease && actor.budget)
|
|
625
|
+
cell.managedLease = actor.budget.retain({ value, type });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else if (row.kind === "generator")
|
|
629
|
+
this.rehomeFrame(this.generatorFor(value).frame, actor);
|
|
630
|
+
else if (value.kind === "task")
|
|
631
|
+
this.rehomeTask(this.taskFor(value), actor);
|
|
632
|
+
}, (value, type) => {
|
|
633
|
+
const row = this.managed.row(type);
|
|
634
|
+
// Encoding trees and opaque host providers cannot contain receiving
|
|
635
|
+
// stream handles. This traversal changes queue ownership only;
|
|
636
|
+
// their payload accounting keeps its separate canonical visitor.
|
|
637
|
+
return row.kind === "provider" &&
|
|
638
|
+
["json", "yaml", "toml", "opaque"].includes(row.provider)
|
|
639
|
+
? { identity: value, bytes: 0n, children: [] }
|
|
640
|
+
: this.managedRetained(value, type);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
rehomeLease(lease, budget) {
|
|
644
|
+
if (!lease?.roots)
|
|
645
|
+
return lease;
|
|
646
|
+
const replacement = this.retainRoots(lease.roots, budget);
|
|
647
|
+
lease.release();
|
|
648
|
+
return replacement;
|
|
649
|
+
}
|
|
650
|
+
retainRoots(roots, budget) {
|
|
651
|
+
// Unbounded frames keep typed ownership metadata for a later generator
|
|
652
|
+
// transfer, without building an allocation graph for an account.
|
|
653
|
+
return budget?.retainMany(roots) ?? { roots, release() { } };
|
|
654
|
+
}
|
|
655
|
+
retainCleanup(frame, roots) {
|
|
656
|
+
const cleanup = {
|
|
657
|
+
roots,
|
|
658
|
+
lease: this.retainRoots(roots, frame.accountActor?.budget),
|
|
659
|
+
};
|
|
660
|
+
frame.managedCleanup.add(cleanup);
|
|
661
|
+
return {
|
|
662
|
+
roots,
|
|
663
|
+
release: () => {
|
|
664
|
+
if (!frame.managedCleanup.delete(cleanup))
|
|
665
|
+
return;
|
|
666
|
+
cleanup.lease.release();
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
rehomeTask(task, actor) {
|
|
671
|
+
const frame = task.frame?.context.activeFrame ?? task.frame;
|
|
672
|
+
if (task.budget === actor.budget && frame?.accountActor === actor)
|
|
673
|
+
return;
|
|
674
|
+
task.budget = actor.budget;
|
|
675
|
+
if (frame)
|
|
676
|
+
this.rehomeFrame(frame, actor);
|
|
677
|
+
task.managedLease = this.rehomeLease(task.managedLease, actor.budget);
|
|
678
|
+
}
|
|
679
|
+
rehomeFrame(frame, actor) {
|
|
680
|
+
if (frame.accountActor === actor)
|
|
681
|
+
return;
|
|
682
|
+
const previous = frame.accountActor;
|
|
683
|
+
frame.accountActor = actor;
|
|
684
|
+
frame.rcOwner = actor.rcOwner;
|
|
685
|
+
frame.context.actor = actor;
|
|
686
|
+
frame.context.id = actor.id;
|
|
687
|
+
const cells = new Set();
|
|
688
|
+
const rehome = (cell) => {
|
|
689
|
+
if (cells.has(cell))
|
|
690
|
+
return;
|
|
691
|
+
cells.add(cell);
|
|
692
|
+
cell.budget = actor.budget;
|
|
693
|
+
if (cell.valid && cell.type != null)
|
|
694
|
+
this.stampManaged(cell.value, cell.type, actor);
|
|
695
|
+
cell.lease = this.rehomeLease(cell.lease, actor.budget);
|
|
696
|
+
if (!cell.lease && cell.valid && cell.type != null)
|
|
697
|
+
cell.lease = actor.budget?.retain({
|
|
698
|
+
value: cell.value,
|
|
699
|
+
type: cell.type,
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
const ref = (value) => {
|
|
703
|
+
if (value.kind === "cell")
|
|
704
|
+
rehome(value.cell);
|
|
705
|
+
else
|
|
706
|
+
ref(value.parent);
|
|
707
|
+
};
|
|
708
|
+
for (const value of frame.env.values())
|
|
709
|
+
ref(value);
|
|
710
|
+
for (const cell of frame.places.values())
|
|
711
|
+
rehome(cell);
|
|
712
|
+
frame.managedInputs = frame.managedInputs.flatMap((lease) => {
|
|
713
|
+
const replacement = this.rehomeLease(lease, actor.budget);
|
|
714
|
+
return replacement ? [replacement] : [];
|
|
715
|
+
});
|
|
716
|
+
for (const cleanup of frame.managedCleanup)
|
|
717
|
+
cleanup.lease = this.rehomeLease(cleanup.lease, actor.budget);
|
|
718
|
+
for (const child of frame.managedChildren)
|
|
719
|
+
this.rehomeFrame(child.context.activeFrame ?? child, actor);
|
|
720
|
+
for (const scope of frame.scopes.values())
|
|
721
|
+
for (const task of scope.tasks)
|
|
722
|
+
this.rehomeTask(task, actor);
|
|
723
|
+
if (frame.caller)
|
|
724
|
+
this.rehomeFrame(frame.caller, actor);
|
|
725
|
+
previous?.budget?.changed();
|
|
726
|
+
actor.budget?.changed();
|
|
727
|
+
}
|
|
728
|
+
failActorBudget(actor) {
|
|
729
|
+
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
730
|
+
if (!actor.active)
|
|
731
|
+
this.crashActor(actor, fault);
|
|
732
|
+
else {
|
|
733
|
+
// A suspended turn owns its frame until authored unwind and lexical
|
|
734
|
+
// task drains finish. Admission must not retire its state underneath it.
|
|
735
|
+
actor.closing = true;
|
|
736
|
+
actor.crashing ??= fault;
|
|
737
|
+
actor.active.pendingCancellation ??= fault;
|
|
738
|
+
actor.active.cancel?.(fault);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
managedRetained(value, type) {
|
|
742
|
+
const row = this.managed.row(type);
|
|
743
|
+
const typed = (value) => ({
|
|
744
|
+
value,
|
|
745
|
+
type: this.managed.type(value),
|
|
746
|
+
});
|
|
747
|
+
if (row.kind === "generator") {
|
|
748
|
+
const generator = this.generatorFor(value);
|
|
749
|
+
return {
|
|
750
|
+
identity: generator,
|
|
751
|
+
bytes: BigInt(row.payload_bytes),
|
|
752
|
+
children: [
|
|
753
|
+
typed(generator.callable),
|
|
754
|
+
...this.frameManagedRoots(generator.frame),
|
|
755
|
+
],
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
if (row.kind === "actor_call") {
|
|
759
|
+
const task = this.taskFor(value);
|
|
760
|
+
const reply = !task.taken && !task.fault && task.managedReply;
|
|
761
|
+
return {
|
|
762
|
+
identity: task,
|
|
763
|
+
bytes: reply ? this.managed.width(row.reply) : 0n,
|
|
764
|
+
children: reply ? [{ value: reply, type: row.reply }] : [],
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (row.kind !== "provider")
|
|
768
|
+
throw new Error("managed retained visitor received no provider");
|
|
769
|
+
switch (row.provider) {
|
|
770
|
+
case "json":
|
|
771
|
+
case "yaml":
|
|
772
|
+
case "toml": {
|
|
773
|
+
if (value.kind !== "encoding")
|
|
774
|
+
throw new Error("managed encoding has no tree");
|
|
775
|
+
const retained = encodingStorage(value);
|
|
776
|
+
return {
|
|
777
|
+
identity: retained,
|
|
778
|
+
bytes: value.value === null ? 0n : retained.bytes,
|
|
779
|
+
children: [],
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
case "task": {
|
|
783
|
+
const task = this.taskFor(value);
|
|
784
|
+
const reply = task.resultInitialized &&
|
|
785
|
+
!task.taken &&
|
|
786
|
+
!task.fault &&
|
|
787
|
+
(!task.actorError || task.actorError === "Failed");
|
|
788
|
+
const children = task.frame && !task.done
|
|
789
|
+
? this.frameManagedRoots(task.frame.context.activeFrame ?? task.frame)
|
|
790
|
+
: [];
|
|
791
|
+
if (task.callable?.kind === "closure" && !task.done)
|
|
792
|
+
children.push(typed(task.callable));
|
|
793
|
+
if (reply)
|
|
794
|
+
children.push({
|
|
795
|
+
value: task.managedResult ?? task.value,
|
|
796
|
+
type: row.children[0],
|
|
797
|
+
});
|
|
798
|
+
return {
|
|
799
|
+
identity: task,
|
|
800
|
+
bytes: (task.invocationPending
|
|
801
|
+
? BigInt(row.invocation_slot_bytes ??
|
|
802
|
+
(() => {
|
|
803
|
+
throw new Error("task invocation has no canonical carrier width");
|
|
804
|
+
})())
|
|
805
|
+
: 0n) +
|
|
806
|
+
this.managed.width(row.children[0]) +
|
|
807
|
+
(task.memberBytes ?? 0n),
|
|
808
|
+
children,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
case "sink": {
|
|
812
|
+
if (value.kind !== "sink")
|
|
813
|
+
throw new Error("typed sink has no endpoint");
|
|
814
|
+
const sender = this.senderEndpoints.get(value.channelId);
|
|
815
|
+
if (sender) {
|
|
816
|
+
const children = [
|
|
817
|
+
typed(sender.owner.sink),
|
|
818
|
+
typed(sender.owner.encoder),
|
|
819
|
+
];
|
|
820
|
+
return {
|
|
821
|
+
identity: sender.owner,
|
|
822
|
+
bytes: children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n),
|
|
823
|
+
children,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
identity: this.pipes.managedRetained(value).identity,
|
|
828
|
+
bytes: 0n,
|
|
829
|
+
children: [],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
case "stream": {
|
|
833
|
+
if (value.kind !== "stream")
|
|
834
|
+
throw new Error("typed stream has no endpoint");
|
|
835
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
836
|
+
if (adapter) {
|
|
837
|
+
const generatorType = adapter.generator.managedType;
|
|
838
|
+
if (generatorType == null)
|
|
839
|
+
throw new Error("generator stream lacks its producer descriptor");
|
|
840
|
+
const children = [
|
|
841
|
+
{
|
|
842
|
+
value: { kind: "generator", id: adapter.generator.id },
|
|
843
|
+
type: generatorType,
|
|
844
|
+
},
|
|
845
|
+
];
|
|
846
|
+
if (adapter.pending)
|
|
847
|
+
children.push({
|
|
848
|
+
value: adapter.pending,
|
|
849
|
+
type: row.children[0],
|
|
850
|
+
slots: true,
|
|
851
|
+
identity: adapter,
|
|
852
|
+
});
|
|
853
|
+
return {
|
|
854
|
+
identity: adapter,
|
|
855
|
+
bytes: this.managed.width(generatorType),
|
|
856
|
+
children,
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
const graph = this.pipes.managedRetained(value);
|
|
860
|
+
return {
|
|
861
|
+
identity: graph.identity,
|
|
862
|
+
bytes: 0n,
|
|
863
|
+
children: graph.queued.map((item) => ({
|
|
864
|
+
value: item.value,
|
|
865
|
+
type: row.children[0],
|
|
866
|
+
slots: true,
|
|
867
|
+
identity: item,
|
|
868
|
+
})),
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
default:
|
|
872
|
+
throw new HeapExceeded();
|
|
873
|
+
}
|
|
428
874
|
}
|
|
429
875
|
invalidate(act, id) {
|
|
430
876
|
// A boundary's transfer of a trivial value has no ownership to end. SIR
|
|
@@ -476,11 +922,26 @@ class ExecutorV1 {
|
|
|
476
922
|
/// and a borrow passes the live value.
|
|
477
923
|
boundary(act, operand) {
|
|
478
924
|
const value = this.read(act, operand.value);
|
|
925
|
+
const budget = act.context.actor?.budget;
|
|
926
|
+
const type = act.managedTypes.get(operand.value);
|
|
927
|
+
const transferable = act.context.generator || act.context.retainsTransferableFrame;
|
|
928
|
+
if (!budget && transferable && type != null)
|
|
929
|
+
this.managed.stampGraph({ value, type });
|
|
479
930
|
switch (operand.decision) {
|
|
480
931
|
case "copy":
|
|
481
|
-
case "snapshot":
|
|
482
|
-
|
|
932
|
+
case "snapshot": {
|
|
933
|
+
if (budget && type != null)
|
|
934
|
+
budget.check([{ value: this.managed.copy(value, type), type }]);
|
|
935
|
+
const copy = cloneValue(value);
|
|
936
|
+
if (!budget && transferable && type != null)
|
|
937
|
+
this.managed.stampGraph({ value: copy, type });
|
|
938
|
+
if (type != null)
|
|
939
|
+
act.managedInputs.push(this.retainRoots([{ value: copy, type }], budget));
|
|
940
|
+
return copy;
|
|
941
|
+
}
|
|
483
942
|
case "move":
|
|
943
|
+
if (type != null)
|
|
944
|
+
act.managedInputs.push(this.retainRoots([{ value, type }], budget));
|
|
484
945
|
this.invalidate(act, operand.value);
|
|
485
946
|
return value;
|
|
486
947
|
default:
|
|
@@ -489,6 +950,24 @@ class ExecutorV1 {
|
|
|
489
950
|
}
|
|
490
951
|
// ── instructions ─────────────────────────────────────────────────────────
|
|
491
952
|
execute(act, op) {
|
|
953
|
+
try {
|
|
954
|
+
this.executeOperation(act, op);
|
|
955
|
+
}
|
|
956
|
+
finally {
|
|
957
|
+
switch (op.op) {
|
|
958
|
+
case "store.init":
|
|
959
|
+
case "store.assign":
|
|
960
|
+
case "load.take":
|
|
961
|
+
case "destructure":
|
|
962
|
+
case "variant.destructure":
|
|
963
|
+
case "destroy_value":
|
|
964
|
+
case "end_lifetime":
|
|
965
|
+
case "task_scope.close":
|
|
966
|
+
act.context.actor?.budget?.changed();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
executeOperation(act, op) {
|
|
492
971
|
switch (op.op) {
|
|
493
972
|
case "coverage.hit": {
|
|
494
973
|
const key = String(op.counter);
|
|
@@ -532,8 +1011,10 @@ class ExecutorV1 {
|
|
|
532
1011
|
return;
|
|
533
1012
|
case "destroy_value": {
|
|
534
1013
|
const value = this.read(act, op.value);
|
|
1014
|
+
const type = act.managedTypes.get(op.value);
|
|
1015
|
+
const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
|
|
535
1016
|
this.invalidate(act, op.value);
|
|
536
|
-
this.releaseOperation(act, value);
|
|
1017
|
+
this.releaseOperation(act, value, () => cleanup?.release());
|
|
537
1018
|
return;
|
|
538
1019
|
}
|
|
539
1020
|
case "begin_borrow":
|
|
@@ -543,20 +1024,34 @@ class ExecutorV1 {
|
|
|
543
1024
|
case "finish_linear_receiver":
|
|
544
1025
|
return;
|
|
545
1026
|
case "alloc_place":
|
|
546
|
-
act.places.set(op.place, {
|
|
1027
|
+
act.places.set(op.place, {
|
|
1028
|
+
value: UNIT,
|
|
1029
|
+
valid: false,
|
|
1030
|
+
budget: act.context.actor?.budget,
|
|
1031
|
+
type: act.fn.places[op.place]?.managed_type ?? undefined,
|
|
1032
|
+
});
|
|
547
1033
|
return;
|
|
548
1034
|
case "store.init":
|
|
549
1035
|
writeRef(this.placeRef(act, op.place), this.read(act, op.value));
|
|
1036
|
+
this.invalidate(act, op.value);
|
|
550
1037
|
return;
|
|
551
1038
|
case "store.assign": {
|
|
552
1039
|
const ref = this.placeRef(act, op.place);
|
|
553
1040
|
const replacement = this.read(act, op.value);
|
|
554
1041
|
const previous = liveRef(ref) ? readRef(ref) : UNIT;
|
|
1042
|
+
const type = act.fn.places[op.place]?.managed_type;
|
|
1043
|
+
const cleanup = type == null
|
|
1044
|
+
? undefined
|
|
1045
|
+
: this.retainCleanup(act, [{ value: previous, type }]);
|
|
555
1046
|
// Retire the old owner before invoking user close code. Publish the
|
|
556
1047
|
// replacement after cleanup, including when close records a fault;
|
|
557
1048
|
// the following cleanup.dispatch then drains the new owner once.
|
|
558
1049
|
invalidateRef(ref);
|
|
559
|
-
this.releaseOperation(act, previous, () =>
|
|
1050
|
+
this.releaseOperation(act, previous, () => {
|
|
1051
|
+
writeRef(ref, replacement);
|
|
1052
|
+
this.invalidate(act, op.value);
|
|
1053
|
+
cleanup?.release();
|
|
1054
|
+
});
|
|
560
1055
|
return;
|
|
561
1056
|
}
|
|
562
1057
|
case "load.copy":
|
|
@@ -574,8 +1069,10 @@ class ExecutorV1 {
|
|
|
574
1069
|
case "end_lifetime": {
|
|
575
1070
|
const ref = this.placeRef(act, op.place);
|
|
576
1071
|
const value = liveRef(ref) ? readRef(ref) : UNIT;
|
|
1072
|
+
const type = act.fn.places[op.place]?.managed_type;
|
|
1073
|
+
const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
|
|
577
1074
|
invalidateRef(ref);
|
|
578
|
-
this.releaseOperation(act, value);
|
|
1075
|
+
this.releaseOperation(act, value, () => cleanup?.release());
|
|
579
1076
|
return;
|
|
580
1077
|
}
|
|
581
1078
|
case "tuple.make":
|
|
@@ -584,6 +1081,7 @@ class ExecutorV1 {
|
|
|
584
1081
|
typeId: "",
|
|
585
1082
|
fields: op.elements.map((id) => this.read(act, id)),
|
|
586
1083
|
});
|
|
1084
|
+
op.elements.forEach((id) => this.invalidate(act, id));
|
|
587
1085
|
return;
|
|
588
1086
|
case "tuple.get":
|
|
589
1087
|
this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.tuple))[op.index] ?? UNIT));
|
|
@@ -595,6 +1093,7 @@ class ExecutorV1 {
|
|
|
595
1093
|
shape: op.shape ?? undefined,
|
|
596
1094
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
597
1095
|
});
|
|
1096
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
598
1097
|
return;
|
|
599
1098
|
case "aggregate.project_copy":
|
|
600
1099
|
this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.aggregate))[op.field] ?? UNIT));
|
|
@@ -616,14 +1115,19 @@ class ExecutorV1 {
|
|
|
616
1115
|
elementType: "",
|
|
617
1116
|
items: op.elements.map((id) => this.read(act, id)),
|
|
618
1117
|
});
|
|
1118
|
+
op.elements.forEach((id) => this.invalidate(act, id));
|
|
619
1119
|
return;
|
|
620
|
-
case "array.repeat":
|
|
1120
|
+
case "array.repeat": {
|
|
1121
|
+
const seed = this.read(act, op.value);
|
|
1122
|
+
const count = this.arrayRepeatLength(op);
|
|
621
1123
|
this.define(act, op.dst, {
|
|
622
1124
|
kind: "vector",
|
|
623
1125
|
elementType: "",
|
|
624
|
-
items: Array.from({ length:
|
|
1126
|
+
items: Array.from({ length: count }, (_, index) => index === 0 ? seed : cloneValue(seed)),
|
|
625
1127
|
});
|
|
1128
|
+
this.invalidate(act, op.value);
|
|
626
1129
|
return;
|
|
1130
|
+
}
|
|
627
1131
|
case "variant.make":
|
|
628
1132
|
this.define(act, op.dst, {
|
|
629
1133
|
kind: "enum",
|
|
@@ -632,6 +1136,7 @@ class ExecutorV1 {
|
|
|
632
1136
|
tag: op.variant,
|
|
633
1137
|
payload: op.fields.map((id) => this.read(act, id)),
|
|
634
1138
|
});
|
|
1139
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
635
1140
|
return;
|
|
636
1141
|
case "variant.is":
|
|
637
1142
|
this.define(act, op.dst, {
|
|
@@ -691,6 +1196,7 @@ class ExecutorV1 {
|
|
|
691
1196
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
692
1197
|
},
|
|
693
1198
|
});
|
|
1199
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
694
1200
|
return;
|
|
695
1201
|
}
|
|
696
1202
|
case "callable.coerce": {
|
|
@@ -716,6 +1222,7 @@ class ExecutorV1 {
|
|
|
716
1222
|
vtable: op.vtable,
|
|
717
1223
|
value: this.read(act, op.value),
|
|
718
1224
|
});
|
|
1225
|
+
this.invalidate(act, op.value);
|
|
719
1226
|
return;
|
|
720
1227
|
// The registration reserves the free places the body reaches; the drain
|
|
721
1228
|
// order is already the CFG's, so there is nothing to record here.
|
|
@@ -771,9 +1278,12 @@ class ExecutorV1 {
|
|
|
771
1278
|
id: task.id,
|
|
772
1279
|
})),
|
|
773
1280
|
}, () => {
|
|
774
|
-
for (const task of group?.tasks ?? [])
|
|
775
|
-
this.tasks.delete(task.id);
|
|
776
1281
|
act.scopes.delete(op.scope);
|
|
1282
|
+
act.context.actor?.budget?.changed();
|
|
1283
|
+
for (const task of group?.tasks ?? [])
|
|
1284
|
+
task.scopeAlive = false;
|
|
1285
|
+
for (const task of group?.tasks ?? [])
|
|
1286
|
+
this.releaseTaskState(task);
|
|
777
1287
|
});
|
|
778
1288
|
return;
|
|
779
1289
|
}
|
|
@@ -786,7 +1296,10 @@ class ExecutorV1 {
|
|
|
786
1296
|
actor: act.context.actor,
|
|
787
1297
|
rcOwner: act.rcOwner,
|
|
788
1298
|
requestDeadline: this.requestDeadline(act),
|
|
789
|
-
returned: (value) => this.closeValueAsync(value, null, (fault) =>
|
|
1299
|
+
returned: (value, released) => this.closeValueAsync(value, null, (fault) => {
|
|
1300
|
+
released();
|
|
1301
|
+
this.finishGenerator(generator, fault);
|
|
1302
|
+
}, act.context.actor, act.rcOwner),
|
|
790
1303
|
failed: (fault) => this.finishGenerator(generator, fault),
|
|
791
1304
|
};
|
|
792
1305
|
const generator = {
|
|
@@ -807,8 +1320,9 @@ class ExecutorV1 {
|
|
|
807
1320
|
const scope = act.scopes.get(op.scope);
|
|
808
1321
|
if (!scope)
|
|
809
1322
|
throw new Error("race has no task scope");
|
|
810
|
-
const
|
|
811
|
-
|
|
1323
|
+
const memberValues = op.members.map((operand) => this.read(act, operand));
|
|
1324
|
+
const members = op.members.map((operand, index) => {
|
|
1325
|
+
const member = this.taskFor(memberValues[index]);
|
|
812
1326
|
this.invalidate(act, operand);
|
|
813
1327
|
if (member.grouped)
|
|
814
1328
|
throw new Error("race reuses a task handle");
|
|
@@ -817,7 +1331,8 @@ class ExecutorV1 {
|
|
|
817
1331
|
});
|
|
818
1332
|
if (!members.length)
|
|
819
1333
|
throw new Error("race requires members");
|
|
820
|
-
const task = this.newTask();
|
|
1334
|
+
const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), BigInt(op.member_slot_bytes) * BigInt(members.length), true);
|
|
1335
|
+
task.members = memberValues;
|
|
821
1336
|
scope.tasks.push(task);
|
|
822
1337
|
let selected = false;
|
|
823
1338
|
let winner = null;
|
|
@@ -839,6 +1354,9 @@ class ExecutorV1 {
|
|
|
839
1354
|
winner = first;
|
|
840
1355
|
value = first.value;
|
|
841
1356
|
first.value = UNIT;
|
|
1357
|
+
first.taken = true;
|
|
1358
|
+
task.value = value;
|
|
1359
|
+
task.resultInitialized = true;
|
|
842
1360
|
}
|
|
843
1361
|
}
|
|
844
1362
|
if (!cancelled) {
|
|
@@ -869,13 +1387,18 @@ class ExecutorV1 {
|
|
|
869
1387
|
.filter((member) => member !== winner)
|
|
870
1388
|
.map((member) => {
|
|
871
1389
|
const result = member.value;
|
|
872
|
-
member.value = UNIT;
|
|
873
1390
|
return result;
|
|
874
1391
|
});
|
|
875
1392
|
this.closeValueAsync({ kind: "vector", elementType: "race result", items: losers }, this.faultText(failure), (closeFault) => {
|
|
1393
|
+
for (const member of members)
|
|
1394
|
+
if (member !== winner) {
|
|
1395
|
+
member.value = UNIT;
|
|
1396
|
+
member.taken = true;
|
|
1397
|
+
}
|
|
876
1398
|
failure = combineFaults(failure, closeFault);
|
|
877
1399
|
const finish = (releaseFault = null) => {
|
|
878
1400
|
failure = combineFaults(failure, releaseFault);
|
|
1401
|
+
this.releaseRaceMembers(task);
|
|
879
1402
|
this.settleTask(task, failure ? UNIT : value, failure);
|
|
880
1403
|
for (const changed of [...scope.changed])
|
|
881
1404
|
changed();
|
|
@@ -941,8 +1464,9 @@ class ExecutorV1 {
|
|
|
941
1464
|
const specialization = op.op === "task.invoke" && "specialization" in op.callee
|
|
942
1465
|
? op.callee.specialization
|
|
943
1466
|
: null;
|
|
944
|
-
const task = this.newTask();
|
|
1467
|
+
const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), 0n, true);
|
|
945
1468
|
group.tasks.push(task);
|
|
1469
|
+
let releaseReturn = () => { };
|
|
946
1470
|
const settle = (value, fault) => {
|
|
947
1471
|
if (task.done || task.finishing)
|
|
948
1472
|
return;
|
|
@@ -955,6 +1479,7 @@ class ExecutorV1 {
|
|
|
955
1479
|
if (!fault || (fault.kind === "panic" && fault.cancelled))
|
|
956
1480
|
fault = closeFault ?? fault;
|
|
957
1481
|
this.settleTask(task, value, fault);
|
|
1482
|
+
releaseReturn();
|
|
958
1483
|
if (!task.grouped &&
|
|
959
1484
|
fault &&
|
|
960
1485
|
!(fault.kind === "panic" && fault.cancelled)) {
|
|
@@ -1008,9 +1533,11 @@ class ExecutorV1 {
|
|
|
1008
1533
|
const context = {
|
|
1009
1534
|
id: task.id,
|
|
1010
1535
|
actor: act.context.actor,
|
|
1536
|
+
retainsTransferableFrame: !!act.context.generator || act.context.retainsTransferableFrame,
|
|
1011
1537
|
rcOwner: act.rcOwner,
|
|
1012
1538
|
requestDeadline: this.requestDeadline(act),
|
|
1013
|
-
returned: (value) => {
|
|
1539
|
+
returned: (value, released) => {
|
|
1540
|
+
releaseReturn = released;
|
|
1014
1541
|
if (dynamicEntry?.receiver_handback &&
|
|
1015
1542
|
dynamicReceiver?.kind === "dyn") {
|
|
1016
1543
|
if (value.kind !== "record" || value.fields.length !== 2)
|
|
@@ -1039,6 +1566,9 @@ class ExecutorV1 {
|
|
|
1039
1566
|
},
|
|
1040
1567
|
};
|
|
1041
1568
|
const frame = this.activate(this.functionAt(calleeFunction(callable, specialization)), callable.kind === "closure" ? [callable.environment, ...args] : args, null, null, undefined, null, context);
|
|
1569
|
+
task.frame = frame;
|
|
1570
|
+
task.callable = callable;
|
|
1571
|
+
task.invocationPending = true;
|
|
1042
1572
|
task.cancel = () => {
|
|
1043
1573
|
if (task.done || task.finishing)
|
|
1044
1574
|
return;
|
|
@@ -1052,6 +1582,8 @@ class ExecutorV1 {
|
|
|
1052
1582
|
this.scheduler.enqueue(task.id, () => {
|
|
1053
1583
|
if (task.done || task.finishing)
|
|
1054
1584
|
return;
|
|
1585
|
+
task.invocationPending = false;
|
|
1586
|
+
task.budget?.changed();
|
|
1055
1587
|
this.runFrame(frame);
|
|
1056
1588
|
});
|
|
1057
1589
|
this.trace.allocateId("task", task.id, act.context.id);
|
|
@@ -1091,7 +1623,43 @@ class ExecutorV1 {
|
|
|
1091
1623
|
}
|
|
1092
1624
|
// ── terminators ──────────────────────────────────────────────────────────
|
|
1093
1625
|
terminate(act, term) {
|
|
1626
|
+
const mutable = [
|
|
1627
|
+
"runtime.call",
|
|
1628
|
+
"extern.call",
|
|
1629
|
+
"value.call",
|
|
1630
|
+
"wire.codec",
|
|
1631
|
+
"suspend",
|
|
1632
|
+
].includes(term.op);
|
|
1633
|
+
if (mutable)
|
|
1634
|
+
act.context.actor?.budget?.changed();
|
|
1635
|
+
try {
|
|
1636
|
+
this.terminateOperation(act, term);
|
|
1637
|
+
}
|
|
1638
|
+
finally {
|
|
1639
|
+
if (mutable)
|
|
1640
|
+
act.context.actor?.budget?.changed();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
terminateOperation(act, term) {
|
|
1094
1644
|
switch (term.op) {
|
|
1645
|
+
case "materialize": {
|
|
1646
|
+
const budget = act.context.actor?.budget;
|
|
1647
|
+
try {
|
|
1648
|
+
if (term.operation.op === "array.repeat")
|
|
1649
|
+
this.arrayRepeatLength(term.operation);
|
|
1650
|
+
if (budget)
|
|
1651
|
+
this.prepareMaterialize(act, term.operation, budget);
|
|
1652
|
+
}
|
|
1653
|
+
catch (error) {
|
|
1654
|
+
if (!(error instanceof HeapExceeded))
|
|
1655
|
+
throw error;
|
|
1656
|
+
this.takeEdge(act, term.failure);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
this.execute(act, term.operation);
|
|
1660
|
+
this.takeEdge(act, term.normal);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1095
1663
|
case "goto":
|
|
1096
1664
|
this.takeEdge(act, term.edge);
|
|
1097
1665
|
return;
|
|
@@ -1107,6 +1675,11 @@ class ExecutorV1 {
|
|
|
1107
1675
|
throw new Error(`switch.variant has no arm for tag ${tag}`);
|
|
1108
1676
|
}
|
|
1109
1677
|
this.spread(act, payloadOf(scrutinee), arm.fields);
|
|
1678
|
+
if (act.owned.has(term.scrutinee)) {
|
|
1679
|
+
this.invalidate(act, term.scrutinee);
|
|
1680
|
+
this.managed.retire(scrutinee);
|
|
1681
|
+
act.context.actor?.budget?.changed();
|
|
1682
|
+
}
|
|
1110
1683
|
this.takeEdge(act, arm.edge);
|
|
1111
1684
|
return;
|
|
1112
1685
|
}
|
|
@@ -1130,7 +1703,13 @@ class ExecutorV1 {
|
|
|
1130
1703
|
case "checked.binary": {
|
|
1131
1704
|
const failure = checkedFailure(term.binary_op, this.read(act, term.lhs), this.read(act, term.rhs), term.ty);
|
|
1132
1705
|
if (failure) {
|
|
1133
|
-
|
|
1706
|
+
// Earlier v1 packages fold the signed minimum by -1 into their
|
|
1707
|
+
// overflow edge, so they keep the IntegerOverflow trap their
|
|
1708
|
+
// compiler emitted.
|
|
1709
|
+
const edge = term.failures.find((candidate) => candidate.trap === failure) ??
|
|
1710
|
+
(failure === "signed_min_div_neg_one"
|
|
1711
|
+
? term.failures.find((candidate) => candidate.trap === "integer_overflow")
|
|
1712
|
+
: undefined);
|
|
1134
1713
|
if (!edge) {
|
|
1135
1714
|
throw new Error(`checked.binary names no ${failure} edge`);
|
|
1136
1715
|
}
|
|
@@ -1247,12 +1826,30 @@ class ExecutorV1 {
|
|
|
1247
1826
|
return;
|
|
1248
1827
|
}
|
|
1249
1828
|
case "collection": {
|
|
1829
|
+
const receiverType = term.args[0] && act.managedTypes.get(term.args[0].value);
|
|
1830
|
+
const receiverLayout = receiverType == null ? undefined : this.managed.row(receiverType);
|
|
1831
|
+
const elementType = receiverLayout &&
|
|
1832
|
+
(receiverLayout.kind === "vector" ||
|
|
1833
|
+
receiverLayout.kind === "array")
|
|
1834
|
+
? receiverLayout.element
|
|
1835
|
+
: undefined;
|
|
1250
1836
|
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);
|
|
1837
|
+
const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape, elementType);
|
|
1252
1838
|
this.runValueProgram(act, term, args, iterator);
|
|
1253
1839
|
return;
|
|
1254
1840
|
}
|
|
1255
1841
|
case "shared": {
|
|
1842
|
+
if (act.context.actor?.budget &&
|
|
1843
|
+
(registration.operation === "RcNew" ||
|
|
1844
|
+
registration.operation === "RcSet")) {
|
|
1845
|
+
const constructing = registration.operation === "RcNew";
|
|
1846
|
+
const operand = term.args[constructing ? 0 : 1];
|
|
1847
|
+
const type = act.managedTypes.get(operand.value);
|
|
1848
|
+
if (type == null)
|
|
1849
|
+
throw new Error("Rc payload lacks its typed descriptor");
|
|
1850
|
+
if (!this.checkRuntimePayload(act, term, constructing ? this.managed.width(type) : 0n, [{ value: this.read(act, operand.value), type }]))
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1256
1853
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1257
1854
|
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
1855
|
return;
|
|
@@ -1405,6 +2002,26 @@ class ExecutorV1 {
|
|
|
1405
2002
|
tag: deadline ? term.deadline_variant : term.fault_variant,
|
|
1406
2003
|
payload: [stringValue(faultRecoveryMessage(fault))],
|
|
1407
2004
|
};
|
|
2005
|
+
// Scope recovery must preserve the original fault even when the
|
|
2006
|
+
// current actor cannot retain its diagnostic text. Preflight the same
|
|
2007
|
+
// managed graph that completion will retain, then use the canonical
|
|
2008
|
+
// empty string on refusal without publishing an uncharged owner.
|
|
2009
|
+
const result = term.result?.value;
|
|
2010
|
+
const type = result == null ? undefined : act.managedTypes.get(result);
|
|
2011
|
+
const budget = result != null && act.owned.has(result)
|
|
2012
|
+
? act.context.actor?.budget
|
|
2013
|
+
: undefined;
|
|
2014
|
+
if (budget && type != null) {
|
|
2015
|
+
try {
|
|
2016
|
+
this.managed.stampGraph({ value, type });
|
|
2017
|
+
budget.check([{ value, type }]);
|
|
2018
|
+
}
|
|
2019
|
+
catch (error) {
|
|
2020
|
+
if (!(error instanceof HeapExceeded))
|
|
2021
|
+
throw error;
|
|
2022
|
+
value.payload[0] = stringValue("");
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
1408
2025
|
act.fault = null;
|
|
1409
2026
|
if (act.context.actor)
|
|
1410
2027
|
act.context.actor.crashing = null;
|
|
@@ -1425,11 +2042,150 @@ class ExecutorV1 {
|
|
|
1425
2042
|
}
|
|
1426
2043
|
}
|
|
1427
2044
|
}
|
|
2045
|
+
/** Reserve the exact constructor/copy graph before any operand is consumed. */
|
|
2046
|
+
arrayRepeatLength(op) {
|
|
2047
|
+
const type = op.definitions?.[0]?.managed_type;
|
|
2048
|
+
const row = type == null ? undefined : this.managed.row(type);
|
|
2049
|
+
if (row?.kind !== "array")
|
|
2050
|
+
throw new Error("array repeat lacks its canonical length");
|
|
2051
|
+
const count = BigInt(row.length);
|
|
2052
|
+
this.managed.width(row.element, count);
|
|
2053
|
+
if (count > 0xffffffffn)
|
|
2054
|
+
throw new HeapExceeded();
|
|
2055
|
+
return Number(count);
|
|
2056
|
+
}
|
|
2057
|
+
prepareMaterialize(act, op, budget) {
|
|
2058
|
+
const type = op.definitions?.[0]?.managed_type;
|
|
2059
|
+
if (type == null)
|
|
2060
|
+
throw new Error("materialization has no managed result descriptor");
|
|
2061
|
+
const read = (id) => this.read(act, id);
|
|
2062
|
+
const copy = (value) => this.managed.copy(value, type);
|
|
2063
|
+
let value;
|
|
2064
|
+
switch (op.op) {
|
|
2065
|
+
case "const.str":
|
|
2066
|
+
value = stringValue(this.pkg.strings[op.str] ?? "");
|
|
2067
|
+
break;
|
|
2068
|
+
case "const.bytes":
|
|
2069
|
+
value = bytesValue(this.pkg.bytes[op.bytes] ?? []);
|
|
2070
|
+
break;
|
|
2071
|
+
case "copy_value":
|
|
2072
|
+
value = copy(read(op.source));
|
|
2073
|
+
break;
|
|
2074
|
+
case "load.copy":
|
|
2075
|
+
value = copy(readRef(this.placeRef(act, op.place)));
|
|
2076
|
+
break;
|
|
2077
|
+
case "tuple.get":
|
|
2078
|
+
value = copy(fieldsOf(read(op.tuple))[op.index]);
|
|
2079
|
+
break;
|
|
2080
|
+
case "aggregate.project_copy":
|
|
2081
|
+
value = copy(fieldsOf(read(op.aggregate))[op.field]);
|
|
2082
|
+
break;
|
|
2083
|
+
case "variant.project_copy":
|
|
2084
|
+
value = copy(payloadOf(read(op.source))[op.field]);
|
|
2085
|
+
break;
|
|
2086
|
+
case "array.make":
|
|
2087
|
+
value = {
|
|
2088
|
+
kind: "vector",
|
|
2089
|
+
elementType: "",
|
|
2090
|
+
items: op.elements.map(read),
|
|
2091
|
+
};
|
|
2092
|
+
break;
|
|
2093
|
+
case "array.repeat": {
|
|
2094
|
+
const count = this.arrayRepeatLength(op);
|
|
2095
|
+
const row = this.managed.row(type);
|
|
2096
|
+
if (row.kind !== "array")
|
|
2097
|
+
throw new Error("array repeat lacks its element descriptor");
|
|
2098
|
+
const empty = [];
|
|
2099
|
+
const storage = {
|
|
2100
|
+
items: empty,
|
|
2101
|
+
capacity: count,
|
|
2102
|
+
owners: 1,
|
|
2103
|
+
bytes: false,
|
|
2104
|
+
shareOnCopy: row.share_on_copy,
|
|
2105
|
+
};
|
|
2106
|
+
value = { kind: "vector", elementType: "", items: empty, storage };
|
|
2107
|
+
budget.check([{ value, type }]);
|
|
2108
|
+
const seed = read(op.value);
|
|
2109
|
+
storage.items = Array.from({ length: count }, (_, index) => index ? this.managed.copy(seed, row.element) : seed);
|
|
2110
|
+
value.items = storage.items;
|
|
2111
|
+
break;
|
|
2112
|
+
}
|
|
2113
|
+
case "variant.make":
|
|
2114
|
+
value = {
|
|
2115
|
+
kind: "enum",
|
|
2116
|
+
typeId: "",
|
|
2117
|
+
shape: op.shape,
|
|
2118
|
+
tag: op.variant,
|
|
2119
|
+
payload: op.fields.map(read),
|
|
2120
|
+
};
|
|
2121
|
+
break;
|
|
2122
|
+
case "closure.make":
|
|
2123
|
+
value = {
|
|
2124
|
+
kind: "closure",
|
|
2125
|
+
entries: this.closureAt(op.closure).entries,
|
|
2126
|
+
specialization: null,
|
|
2127
|
+
environment: {
|
|
2128
|
+
kind: "record",
|
|
2129
|
+
typeId: "",
|
|
2130
|
+
fields: op.fields.map(read),
|
|
2131
|
+
},
|
|
2132
|
+
};
|
|
2133
|
+
break;
|
|
2134
|
+
case "dyn.make":
|
|
2135
|
+
value = { kind: "dyn", vtable: op.vtable, value: read(op.value) };
|
|
2136
|
+
break;
|
|
2137
|
+
case "generator.make": {
|
|
2138
|
+
const row = this.managed.row(type);
|
|
2139
|
+
if (row.kind !== "generator")
|
|
2140
|
+
throw new Error("generator lacks its payload descriptor");
|
|
2141
|
+
const callable = read(op.callable);
|
|
2142
|
+
budget.checkPayload(BigInt(row.payload_bytes), [
|
|
2143
|
+
{ value: callable, type: this.managed.type(callable) },
|
|
2144
|
+
]);
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
case "task.spawn":
|
|
2148
|
+
case "task.invoke":
|
|
2149
|
+
case "task.race": {
|
|
2150
|
+
const row = this.managed.row(type);
|
|
2151
|
+
if (row.kind !== "provider" || row.provider !== "task")
|
|
2152
|
+
throw new Error("checked task lacks its canonical provider descriptor");
|
|
2153
|
+
const carrier = op.op === "task.race"
|
|
2154
|
+
? BigInt(op.member_slot_bytes) * BigInt(op.members.length)
|
|
2155
|
+
: BigInt(row.invocation_slot_bytes);
|
|
2156
|
+
const children = [];
|
|
2157
|
+
if (op.op === "task.invoke") {
|
|
2158
|
+
const operands = [...op.args];
|
|
2159
|
+
if ("receiver" in op.callee)
|
|
2160
|
+
operands.push(op.callee.receiver);
|
|
2161
|
+
for (const operand of operands) {
|
|
2162
|
+
const type = act.managedTypes.get(operand.value);
|
|
2163
|
+
if (type == null)
|
|
2164
|
+
throw new Error("task input lacks its canonical descriptor");
|
|
2165
|
+
const value = read(operand.value);
|
|
2166
|
+
children.push({
|
|
2167
|
+
value: operand.decision === "copy" || operand.decision === "snapshot"
|
|
2168
|
+
? this.managed.copy(value, type)
|
|
2169
|
+
: value,
|
|
2170
|
+
type,
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
budget.checkPayload(this.managed.width(row.children[0]) + carrier, children);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
default:
|
|
2178
|
+
throw new Error(`unexpected managed materialization ${op.op}`);
|
|
2179
|
+
}
|
|
2180
|
+
budget.check([{ value, type }]);
|
|
2181
|
+
}
|
|
1428
2182
|
takeEdge(act, edge) {
|
|
1429
2183
|
// Read every argument before rebinding, so a block that branches to itself
|
|
1430
2184
|
// with permuted parameters binds the incoming values, not the rebound ones.
|
|
1431
2185
|
const args = edge.args.map((id) => this.refOf(act, id));
|
|
1432
2186
|
const target = this.blockAt(act.fn, edge.to);
|
|
2187
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2188
|
+
lease.release();
|
|
1433
2189
|
for (const [at, param] of target.params.entries()) {
|
|
1434
2190
|
act.env.set(param.value, args[at] ?? ownedRef(UNIT));
|
|
1435
2191
|
}
|
|
@@ -1459,9 +2215,53 @@ class ExecutorV1 {
|
|
|
1459
2215
|
throw error;
|
|
1460
2216
|
});
|
|
1461
2217
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
2218
|
+
/** Reserve typed runtime payload before construction or replacement. Refusal
|
|
2219
|
+
* follows the call's existing input ownership and authored cleanup edge. */
|
|
2220
|
+
checkRuntimePayload(act, term, bytes, children) {
|
|
2221
|
+
const budget = act.context.actor?.budget;
|
|
2222
|
+
if (!budget)
|
|
2223
|
+
return true;
|
|
2224
|
+
try {
|
|
2225
|
+
budget.checkPayload(bytes, children);
|
|
2226
|
+
return true;
|
|
2227
|
+
}
|
|
2228
|
+
catch (error) {
|
|
2229
|
+
if (!(error instanceof HeapExceeded))
|
|
2230
|
+
throw error;
|
|
2231
|
+
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
2232
|
+
if (this.keepsInputs(term)) {
|
|
2233
|
+
// No new payload exists and none of the consuming inputs has moved.
|
|
2234
|
+
this.raiseFault(act, fault, term.unwind);
|
|
2235
|
+
return false;
|
|
2236
|
+
}
|
|
2237
|
+
const consumed = term.args
|
|
2238
|
+
.filter((operand) => operand.decision === "move")
|
|
2239
|
+
.map((operand) => this.boundary(act, operand));
|
|
2240
|
+
this.running = false;
|
|
2241
|
+
this.closeValueAsync({ kind: "record", typeId: "", fields: consumed }, error.message, (cleanupFault) => {
|
|
2242
|
+
this.raiseFault(act, combineFaults(fault, cleanupFault), term.unwind);
|
|
2243
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2244
|
+
}, act.context.actor, act.rcOwner, act);
|
|
2245
|
+
return false;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
/// Run a runtime or extern shim through its authored fault continuation.
|
|
1464
2249
|
callShim(act, term, shim) {
|
|
2250
|
+
const family = term.op === "runtime.call"
|
|
2251
|
+
? this.pkg.runtime_families[term.family]?.family
|
|
2252
|
+
: undefined;
|
|
2253
|
+
const budget = act.context.actor?.budget;
|
|
2254
|
+
if (budget &&
|
|
2255
|
+
(family === "SinkFromEncoder" || family === "StreamFromGenerator")) {
|
|
2256
|
+
const children = term.args.map((operand) => {
|
|
2257
|
+
const type = act.managedTypes.get(operand.value);
|
|
2258
|
+
if (type == null)
|
|
2259
|
+
throw new Error("stream adapter input lacks its typed descriptor");
|
|
2260
|
+
return { value: this.read(act, operand.value), type };
|
|
2261
|
+
});
|
|
2262
|
+
if (!this.checkRuntimePayload(act, term, children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n), children))
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
1465
2265
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1466
2266
|
// A call that hands back its updated receiver names its value's shape as
|
|
1467
2267
|
// the pair's second member.
|
|
@@ -1477,9 +2277,13 @@ class ExecutorV1 {
|
|
|
1477
2277
|
value = shim(this.host, args, shape, aggregate);
|
|
1478
2278
|
}
|
|
1479
2279
|
catch (error) {
|
|
1480
|
-
if (error instanceof ShimFault) {
|
|
2280
|
+
if (error instanceof ShimFault || error instanceof HeapExceeded) {
|
|
1481
2281
|
this.keepInputs(act, term, args);
|
|
1482
|
-
this.raiseFault(act, {
|
|
2282
|
+
this.raiseFault(act, {
|
|
2283
|
+
kind: "trap",
|
|
2284
|
+
trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
|
|
2285
|
+
message: error.message,
|
|
2286
|
+
}, term.unwind);
|
|
1483
2287
|
return;
|
|
1484
2288
|
}
|
|
1485
2289
|
throw error;
|
|
@@ -1523,16 +2327,21 @@ class ExecutorV1 {
|
|
|
1523
2327
|
this.keepInputs(act, term, args);
|
|
1524
2328
|
this.raiseFault(act, fault, term.unwind);
|
|
1525
2329
|
resume();
|
|
1526
|
-
}, act.context.actor, act.rcOwner);
|
|
2330
|
+
}, act.context.actor, act.rcOwner, act);
|
|
1527
2331
|
};
|
|
1528
2332
|
const next = (value = UNIT) => {
|
|
1529
2333
|
let step;
|
|
1530
2334
|
try {
|
|
1531
2335
|
step = iterator.next(value);
|
|
2336
|
+
act.context.actor?.budget?.changed();
|
|
1532
2337
|
}
|
|
1533
2338
|
catch (error) {
|
|
1534
|
-
if (error instanceof ShimFault) {
|
|
1535
|
-
fail({
|
|
2339
|
+
if (error instanceof ShimFault || error instanceof HeapExceeded) {
|
|
2340
|
+
fail({
|
|
2341
|
+
kind: "trap",
|
|
2342
|
+
trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
|
|
2343
|
+
message: error.message,
|
|
2344
|
+
});
|
|
1536
2345
|
return;
|
|
1537
2346
|
}
|
|
1538
2347
|
throw error;
|
|
@@ -1546,7 +2355,16 @@ class ExecutorV1 {
|
|
|
1546
2355
|
}
|
|
1547
2356
|
else if (step.value.kind === "release") {
|
|
1548
2357
|
const request = step.value;
|
|
2358
|
+
const lease = act.context.actor?.budget ||
|
|
2359
|
+
act.context.generator ||
|
|
2360
|
+
act.context.retainsTransferableFrame
|
|
2361
|
+
? this.retainCleanup(act, (request.retained ?? [request.value]).map((value) => ({
|
|
2362
|
+
value,
|
|
2363
|
+
type: request.retainedType ?? this.managed.type(value),
|
|
2364
|
+
})))
|
|
2365
|
+
: undefined;
|
|
1549
2366
|
this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
|
|
2367
|
+
lease?.release();
|
|
1550
2368
|
if (fault) {
|
|
1551
2369
|
if (term.op !== "runtime.call" || !term.releases_contents) {
|
|
1552
2370
|
fail(fault, request.onFault);
|
|
@@ -1557,11 +2375,11 @@ class ExecutorV1 {
|
|
|
1557
2375
|
act.fault ??= fault;
|
|
1558
2376
|
}
|
|
1559
2377
|
next();
|
|
1560
|
-
}, act.context.actor, act.rcOwner);
|
|
2378
|
+
}, act.context.actor, act.rcOwner, act);
|
|
1561
2379
|
}
|
|
1562
2380
|
else {
|
|
1563
2381
|
const request = step.value;
|
|
1564
|
-
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner);
|
|
2382
|
+
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner, false, act);
|
|
1565
2383
|
act.context.cancel = (fault) => child.cancel?.(fault);
|
|
1566
2384
|
}
|
|
1567
2385
|
};
|
|
@@ -1575,13 +2393,13 @@ class ExecutorV1 {
|
|
|
1575
2393
|
if (operation === "Len") {
|
|
1576
2394
|
this.completeShim(act, term, {
|
|
1577
2395
|
kind: "i64",
|
|
1578
|
-
value: BigInt(view
|
|
2396
|
+
value: BigInt(vectorLength(view)),
|
|
1579
2397
|
});
|
|
1580
2398
|
return;
|
|
1581
2399
|
}
|
|
1582
2400
|
if (index?.kind !== "i64")
|
|
1583
2401
|
throw new Error("pool operation has no index");
|
|
1584
|
-
const value = view
|
|
2402
|
+
const value = vectorElement(view, Number(index.value));
|
|
1585
2403
|
if (operation === "Get") {
|
|
1586
2404
|
this.completeShim(act, term, this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : []));
|
|
1587
2405
|
return;
|
|
@@ -1618,7 +2436,7 @@ class ExecutorV1 {
|
|
|
1618
2436
|
}, (fault) => {
|
|
1619
2437
|
this.raiseFault(act, fault, term.unwind);
|
|
1620
2438
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1621
|
-
});
|
|
2439
|
+
}, act.context.actor?.rcOwner ?? null, false, undefined, act);
|
|
1622
2440
|
act.context.cancel = (fault) => callback.cancel?.(fault);
|
|
1623
2441
|
};
|
|
1624
2442
|
continueRendering();
|
|
@@ -1629,7 +2447,17 @@ class ExecutorV1 {
|
|
|
1629
2447
|
throw new Error(`${term.op} with a "never" result cannot return in the sandbox`);
|
|
1630
2448
|
}
|
|
1631
2449
|
if (term.result && term.result !== "never") {
|
|
1632
|
-
|
|
2450
|
+
try {
|
|
2451
|
+
this.define(act, term.result.value, value);
|
|
2452
|
+
}
|
|
2453
|
+
catch (error) {
|
|
2454
|
+
if (!(error instanceof HeapExceeded))
|
|
2455
|
+
throw error;
|
|
2456
|
+
this.closeValueAsync(value, error.message, (fault) => {
|
|
2457
|
+
this.raiseFault(act, combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault), "unwind" in term ? term.unwind : null);
|
|
2458
|
+
}, act.context.actor, act.rcOwner);
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
1633
2461
|
}
|
|
1634
2462
|
this.takeEdge(act, term.normal);
|
|
1635
2463
|
}
|
|
@@ -1675,9 +2503,20 @@ class ExecutorV1 {
|
|
|
1675
2503
|
}
|
|
1676
2504
|
const cell = item.value;
|
|
1677
2505
|
const payload = cell.value;
|
|
2506
|
+
let payloadLease;
|
|
2507
|
+
if (actor?.budget && cell.managedPayload != null)
|
|
2508
|
+
payloadLease = actor.budget.retain({
|
|
2509
|
+
value: payload,
|
|
2510
|
+
type: cell.managedPayload,
|
|
2511
|
+
slots: true,
|
|
2512
|
+
identity: cell,
|
|
2513
|
+
});
|
|
2514
|
+
cell.managedLease?.release();
|
|
2515
|
+
cell.managedLease = undefined;
|
|
1678
2516
|
cell.value = UNIT;
|
|
1679
2517
|
blocks.delete(cell);
|
|
1680
2518
|
this.closeValueAsync(payload, this.faultText(firstFault), (fault) => {
|
|
2519
|
+
payloadLease?.release();
|
|
1681
2520
|
firstFault = combineFaults(firstFault, fault);
|
|
1682
2521
|
release();
|
|
1683
2522
|
}, actor, owner);
|
|
@@ -1724,7 +2563,39 @@ class ExecutorV1 {
|
|
|
1724
2563
|
const caller = act.caller;
|
|
1725
2564
|
if (!caller) {
|
|
1726
2565
|
this.running = false;
|
|
1727
|
-
|
|
2566
|
+
let reply;
|
|
2567
|
+
if (act.context.reply &&
|
|
2568
|
+
act.context.actor?.budget &&
|
|
2569
|
+
act.block.term.op === "return" &&
|
|
2570
|
+
act.block.term.value) {
|
|
2571
|
+
const type = act.managedTypes.get(act.block.term.value.value);
|
|
2572
|
+
if (type == null)
|
|
2573
|
+
throw new Error("actor reply lacks its managed descriptor");
|
|
2574
|
+
try {
|
|
2575
|
+
if (type != null)
|
|
2576
|
+
reply = act.context.actor.budget.retain({
|
|
2577
|
+
value,
|
|
2578
|
+
type,
|
|
2579
|
+
slots: true,
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
catch (error) {
|
|
2583
|
+
if (!(error instanceof HeapExceeded))
|
|
2584
|
+
throw error;
|
|
2585
|
+
this.closeValueAsync(value, error.message, (fault) => {
|
|
2586
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2587
|
+
lease.release();
|
|
2588
|
+
act.context.failed(combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault));
|
|
2589
|
+
}, act.context.actor, act.rcOwner);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
const inputs = act.managedInputs.splice(0);
|
|
2594
|
+
act.context.returned(value, () => {
|
|
2595
|
+
reply?.release();
|
|
2596
|
+
for (const lease of inputs)
|
|
2597
|
+
lease.release();
|
|
2598
|
+
});
|
|
1728
2599
|
return;
|
|
1729
2600
|
}
|
|
1730
2601
|
if (!act.normal) {
|
|
@@ -1744,6 +2615,8 @@ class ExecutorV1 {
|
|
|
1744
2615
|
}
|
|
1745
2616
|
this.current = caller;
|
|
1746
2617
|
this.takeEdge(caller, act.normal);
|
|
2618
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2619
|
+
lease.release();
|
|
1747
2620
|
}
|
|
1748
2621
|
dynamicEntry(receiver, slot) {
|
|
1749
2622
|
const table = this.pkg.vtables[receiver.vtable] ??
|
|
@@ -1792,6 +2665,8 @@ class ExecutorV1 {
|
|
|
1792
2665
|
if (!caller) {
|
|
1793
2666
|
this.running = false;
|
|
1794
2667
|
act.context.failed(fault, handback);
|
|
2668
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2669
|
+
lease.release();
|
|
1795
2670
|
return;
|
|
1796
2671
|
}
|
|
1797
2672
|
if (!act.unwind) {
|
|
@@ -1811,6 +2686,8 @@ class ExecutorV1 {
|
|
|
1811
2686
|
caller.fault = fault;
|
|
1812
2687
|
this.current = caller;
|
|
1813
2688
|
this.takeEdge(caller, act.unwind);
|
|
2689
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2690
|
+
lease.release();
|
|
1814
2691
|
}
|
|
1815
2692
|
finishConsumedCallable(act, value, done) {
|
|
1816
2693
|
const callable = act.consumedCallable;
|
|
@@ -1850,6 +2727,7 @@ class ExecutorV1 {
|
|
|
1850
2727
|
const generator = act.context.generator;
|
|
1851
2728
|
if (!generator || !generator.receive)
|
|
1852
2729
|
throw new Error("yield has no generator consumer");
|
|
2730
|
+
generator.frame = act;
|
|
1853
2731
|
const value = this.boundary(act, term.inputs[0]);
|
|
1854
2732
|
const wake = this.park(act, term);
|
|
1855
2733
|
generator.resume = () => wake(UNIT);
|
|
@@ -1895,9 +2773,27 @@ class ExecutorV1 {
|
|
|
1895
2773
|
case "StreamSend": {
|
|
1896
2774
|
const sink = this.boundary(act, term.inputs[0]);
|
|
1897
2775
|
const value = this.boundary(act, term.inputs[1]);
|
|
2776
|
+
const type = act.managedTypes.get(term.inputs[1].value);
|
|
2777
|
+
if (type == null)
|
|
2778
|
+
throw new Error("stream write lacks its typed input descriptor");
|
|
2779
|
+
// The native operation owns one aligned input slot until accepted or
|
|
2780
|
+
// terminal cleanup, independently of the receiving queue's slot.
|
|
1898
2781
|
const wake = this.park(act, term);
|
|
2782
|
+
try {
|
|
2783
|
+
act.managedInputs.push(this.retainRoots([{ value, type, slots: true, identity: {} }], act.context.actor?.budget));
|
|
2784
|
+
}
|
|
2785
|
+
catch (error) {
|
|
2786
|
+
if (!(error instanceof HeapExceeded))
|
|
2787
|
+
throw error;
|
|
2788
|
+
const refused = { kind: "trap", trap: "heap_exceeded" };
|
|
2789
|
+
this.closeValueAsync(value, error.message, (fault) => wake(UNIT, combineFaults(refused, fault)), act.context.actor, act.rcOwner, act);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
1899
2792
|
const cancel = act.context.cancel;
|
|
1900
2793
|
const stop = this.sendStream(sink, value, term.detail.park, act.context.actor, (status, fault) => {
|
|
2794
|
+
if (status === 0)
|
|
2795
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2796
|
+
lease.release();
|
|
1901
2797
|
wake(UNIT, fault, status);
|
|
1902
2798
|
}, act.rcOwner);
|
|
1903
2799
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
@@ -1915,8 +2811,10 @@ class ExecutorV1 {
|
|
|
1915
2811
|
return;
|
|
1916
2812
|
}
|
|
1917
2813
|
let cancelWait = () => { };
|
|
1918
|
-
|
|
1919
|
-
|
|
2814
|
+
let releaseEnvelope = () => { };
|
|
2815
|
+
const wake = this.park(act, term, () => cancelWait(), undefined, () => releaseEnvelope());
|
|
2816
|
+
cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault, released) => {
|
|
2817
|
+
releaseEnvelope = released ?? (() => { });
|
|
1920
2818
|
if (fault !== null)
|
|
1921
2819
|
wake(UNIT, { kind: "panic", message: fault });
|
|
1922
2820
|
else
|
|
@@ -1925,9 +2823,17 @@ class ExecutorV1 {
|
|
|
1925
2823
|
return;
|
|
1926
2824
|
}
|
|
1927
2825
|
case "Await": {
|
|
1928
|
-
const
|
|
2826
|
+
const handle = this.boundary(act, term.inputs[0]);
|
|
2827
|
+
const task = this.taskFor(handle);
|
|
1929
2828
|
let unwatch = () => { };
|
|
1930
|
-
|
|
2829
|
+
let resultLease;
|
|
2830
|
+
const wake = this.park(act, term, () => unwatch(), undefined, () => {
|
|
2831
|
+
resultLease?.release();
|
|
2832
|
+
task.handles.delete(handle);
|
|
2833
|
+
this.managed.retire(handle);
|
|
2834
|
+
task.budget?.changed();
|
|
2835
|
+
this.releaseTaskState(task);
|
|
2836
|
+
});
|
|
1931
2837
|
const ready = () => {
|
|
1932
2838
|
if (!task.done) {
|
|
1933
2839
|
this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
|
|
@@ -1937,7 +2843,9 @@ class ExecutorV1 {
|
|
|
1937
2843
|
wake(UNIT, task.fault);
|
|
1938
2844
|
else {
|
|
1939
2845
|
const value = task.value;
|
|
2846
|
+
resultLease = this.retainTaskResult(task, act.context.actor?.budget);
|
|
1940
2847
|
task.value = UNIT;
|
|
2848
|
+
task.taken = true;
|
|
1941
2849
|
wake(value);
|
|
1942
2850
|
}
|
|
1943
2851
|
};
|
|
@@ -1997,12 +2905,28 @@ class ExecutorV1 {
|
|
|
1997
2905
|
const wake = this.park(act, term, () => unwatch.forEach((remove) => remove()));
|
|
1998
2906
|
let cancelTimer;
|
|
1999
2907
|
const choose = () => {
|
|
2000
|
-
const ready =
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2908
|
+
const ready = [];
|
|
2909
|
+
for (const [index, value] of sources.entries()) {
|
|
2910
|
+
if (value.kind === "stream") {
|
|
2911
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2912
|
+
if (adapter)
|
|
2913
|
+
this.pollGeneratorStream(adapter);
|
|
2914
|
+
const readable = adapter
|
|
2915
|
+
? adapter.pending !== null ||
|
|
2916
|
+
adapter.generator.done ||
|
|
2917
|
+
adapter.closed
|
|
2918
|
+
: this.pipes.readable(value);
|
|
2919
|
+
if (readable)
|
|
2920
|
+
ready.push({ index, order: 0 });
|
|
2921
|
+
}
|
|
2922
|
+
else {
|
|
2923
|
+
const task = this.taskFor(value);
|
|
2924
|
+
if (task.done)
|
|
2925
|
+
ready.push({ index, order: task.completedAt });
|
|
2926
|
+
}
|
|
2927
|
+
if (ready.length && term.detail.order !== "completion")
|
|
2928
|
+
break;
|
|
2929
|
+
}
|
|
2006
2930
|
if (term.detail.order === "completion")
|
|
2007
2931
|
ready.sort((a, b) => a.order - b.order);
|
|
2008
2932
|
if (ready[0]) {
|
|
@@ -2022,8 +2946,15 @@ class ExecutorV1 {
|
|
|
2022
2946
|
cancelTimer = this.scheduler.after(duration.value, () => wake({ kind: "i64", value: BigInt(sources.length) }));
|
|
2023
2947
|
}
|
|
2024
2948
|
sources.forEach((value) => {
|
|
2025
|
-
if (value.kind === "stream")
|
|
2026
|
-
|
|
2949
|
+
if (value.kind === "stream") {
|
|
2950
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2951
|
+
if (adapter) {
|
|
2952
|
+
adapter.observers.add(choose);
|
|
2953
|
+
unwatch.push(() => adapter.observers.delete(choose));
|
|
2954
|
+
}
|
|
2955
|
+
else
|
|
2956
|
+
unwatch.push(this.pipes.observe(value, choose));
|
|
2957
|
+
}
|
|
2027
2958
|
else {
|
|
2028
2959
|
const task = this.taskFor(value);
|
|
2029
2960
|
task.changed.push(choose);
|
|
@@ -2118,6 +3049,16 @@ class ExecutorV1 {
|
|
|
2118
3049
|
return;
|
|
2119
3050
|
}
|
|
2120
3051
|
case "Safepoint": {
|
|
3052
|
+
// A function's entry safepoint is its stack check (HEW-SPEC-2026
|
|
3053
|
+
// §2.2): past the frame limit the call fails with `StackOverflow`
|
|
3054
|
+
// and its parameters follow the cancellation cleanup.
|
|
3055
|
+
if (term.cancel &&
|
|
3056
|
+
act.depth > MAX_FRAME_DEPTH &&
|
|
3057
|
+
act.block.id === act.fn.entry) {
|
|
3058
|
+
act.fault = { kind: "trap", trap: "stack_overflow" };
|
|
3059
|
+
this.takeEdge(act, term.cancel);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
2121
3062
|
if (act.context.pollSuppression) {
|
|
2122
3063
|
this.takeEdge(act, term.resumes[0]);
|
|
2123
3064
|
return;
|
|
@@ -2178,6 +3119,10 @@ class ExecutorV1 {
|
|
|
2178
3119
|
let cancelTimer = () => { };
|
|
2179
3120
|
const wake = this.park(act, term, () => cancelTimer());
|
|
2180
3121
|
cancelTimer = this.scheduler.after(nanos, () => wake(UNIT));
|
|
3122
|
+
// Admission can publish a fault synchronously before this sleep parks.
|
|
3123
|
+
// Deliver that latch after arming, so cancellation also removes the timer.
|
|
3124
|
+
if (act.context.pendingCancellation)
|
|
3125
|
+
act.context.cancel?.(act.context.pendingCancellation);
|
|
2181
3126
|
return;
|
|
2182
3127
|
}
|
|
2183
3128
|
default: {
|
|
@@ -2203,13 +3148,30 @@ class ExecutorV1 {
|
|
|
2203
3148
|
message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
|
|
2204
3149
|
});
|
|
2205
3150
|
}
|
|
2206
|
-
park(act, term, cleanup = () => { }, accepted = () => { }) {
|
|
3151
|
+
park(act, term, cleanup = () => { }, accepted = () => { }, drained = () => { }) {
|
|
2207
3152
|
this.running = false;
|
|
2208
3153
|
act.context.parked = true;
|
|
2209
3154
|
let pending = null;
|
|
3155
|
+
let pendingLease;
|
|
2210
3156
|
const resume = (value, fault = null, edge = 0) => {
|
|
2211
3157
|
if (pending)
|
|
2212
3158
|
return false;
|
|
3159
|
+
if (!fault &&
|
|
3160
|
+
act.context.actor?.budget &&
|
|
3161
|
+
term.result &&
|
|
3162
|
+
term.result !== "never") {
|
|
3163
|
+
const type = term.result.managed_type;
|
|
3164
|
+
if (type == null)
|
|
3165
|
+
throw new Error("suspended result lacks its managed descriptor");
|
|
3166
|
+
try {
|
|
3167
|
+
pendingLease = act.context.actor.budget.retain({ value, type });
|
|
3168
|
+
}
|
|
3169
|
+
catch (error) {
|
|
3170
|
+
if (!(error instanceof HeapExceeded))
|
|
3171
|
+
throw error;
|
|
3172
|
+
fault = { kind: "trap", trap: "heap_exceeded" };
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
2213
3175
|
pending = { value, fault, edge };
|
|
2214
3176
|
act.context.parked = false;
|
|
2215
3177
|
if (act.context.actor?.active === act.context)
|
|
@@ -2236,6 +3198,8 @@ class ExecutorV1 {
|
|
|
2236
3198
|
throw new Error(`${term.kind} has no resume edge`);
|
|
2237
3199
|
this.takeEdge(act, edge);
|
|
2238
3200
|
}
|
|
3201
|
+
pendingLease?.release();
|
|
3202
|
+
drained();
|
|
2239
3203
|
this.runFrame(act);
|
|
2240
3204
|
};
|
|
2241
3205
|
// A close can arrive after readiness but before this frame runs. Keep
|
|
@@ -2440,7 +3404,16 @@ class ExecutorV1 {
|
|
|
2440
3404
|
adapter.waiting = undefined;
|
|
2441
3405
|
this.closeGenerator(generator, (closeFault) => cancel?.(closeFault ?? fault));
|
|
2442
3406
|
};
|
|
2443
|
-
|
|
3407
|
+
this.pollGeneratorStream(adapter);
|
|
3408
|
+
}
|
|
3409
|
+
/** Readiness drives the same producer as recv but leaves a published item
|
|
3410
|
+
* retained until the winning receive. Losing observations only detach. */
|
|
3411
|
+
pollGeneratorStream(adapter) {
|
|
3412
|
+
const generator = adapter.generator;
|
|
3413
|
+
if (adapter.receiving ||
|
|
3414
|
+
adapter.pending !== null ||
|
|
3415
|
+
generator.done ||
|
|
3416
|
+
adapter.closed)
|
|
2444
3417
|
return;
|
|
2445
3418
|
adapter.receiving = true;
|
|
2446
3419
|
generator.receive = (value, fault) => {
|
|
@@ -2457,6 +3430,8 @@ class ExecutorV1 {
|
|
|
2457
3430
|
waiting(value, fault);
|
|
2458
3431
|
else
|
|
2459
3432
|
adapter.pending = value;
|
|
3433
|
+
for (const changed of [...adapter.observers])
|
|
3434
|
+
changed();
|
|
2460
3435
|
};
|
|
2461
3436
|
if (generator.started) {
|
|
2462
3437
|
const resume = generator.resume;
|
|
@@ -2499,7 +3474,7 @@ class ExecutorV1 {
|
|
|
2499
3474
|
act.fault = combineFaults(act.fault, fault);
|
|
2500
3475
|
if (!synchronous)
|
|
2501
3476
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2502
|
-
}, act.context.actor, act.rcOwner);
|
|
3477
|
+
}, act.context.actor, act.rcOwner, act);
|
|
2503
3478
|
synchronous = false;
|
|
2504
3479
|
if (!completed)
|
|
2505
3480
|
this.running = false;
|
|
@@ -2537,7 +3512,7 @@ class ExecutorV1 {
|
|
|
2537
3512
|
else
|
|
2538
3513
|
generator.frame.context.cancel?.();
|
|
2539
3514
|
}
|
|
2540
|
-
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner) {
|
|
3515
|
+
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner, retainedBy) {
|
|
2541
3516
|
const pending = [value];
|
|
2542
3517
|
let firstFault = null;
|
|
2543
3518
|
const next = (failure = null) => {
|
|
@@ -2546,10 +3521,38 @@ class ExecutorV1 {
|
|
|
2546
3521
|
// must disclose that failure instead of publishing a clean end.
|
|
2547
3522
|
const currentFault = fault ?? this.faultText(actor?.crashing ?? firstFault);
|
|
2548
3523
|
while (pending.length) {
|
|
2549
|
-
const
|
|
3524
|
+
const entry = pending.pop();
|
|
3525
|
+
if ("managedRelease" in entry) {
|
|
3526
|
+
entry.managedRelease.release();
|
|
3527
|
+
continue;
|
|
3528
|
+
}
|
|
3529
|
+
if ("taskTaken" in entry) {
|
|
3530
|
+
entry.taskTaken.value = UNIT;
|
|
3531
|
+
entry.taskTaken.taken = true;
|
|
3532
|
+
entry.taskTaken.managedResult = undefined;
|
|
3533
|
+
entry.taskTaken.managedReply = undefined;
|
|
3534
|
+
continue;
|
|
3535
|
+
}
|
|
3536
|
+
if ("retire" in entry) {
|
|
3537
|
+
if (entry.retire.kind === "stream" &&
|
|
3538
|
+
!this.generatorStreams.has(entry.retire.channelId))
|
|
3539
|
+
this.pipes.finishClose(entry.retire);
|
|
3540
|
+
this.managed.retire(entry.retire);
|
|
3541
|
+
if (entry.retire.kind === "stream")
|
|
3542
|
+
this.generatorStreams.delete(entry.retire.channelId);
|
|
3543
|
+
actor?.budget?.changed();
|
|
3544
|
+
if (entry.retire.kind === "task") {
|
|
3545
|
+
const task = this.tasks.get(entry.retire.id);
|
|
3546
|
+
if (task)
|
|
3547
|
+
this.releaseTaskState(task);
|
|
3548
|
+
}
|
|
3549
|
+
continue;
|
|
3550
|
+
}
|
|
3551
|
+
const value = entry;
|
|
3552
|
+
pending.push({ retire: value });
|
|
2550
3553
|
switch (value.kind) {
|
|
2551
3554
|
case "encoding":
|
|
2552
|
-
value
|
|
3555
|
+
releaseEncoding(value);
|
|
2553
3556
|
break;
|
|
2554
3557
|
case "string":
|
|
2555
3558
|
value.release();
|
|
@@ -2558,6 +3561,21 @@ class ExecutorV1 {
|
|
|
2558
3561
|
if (!value.closed) {
|
|
2559
3562
|
value.closed = true;
|
|
2560
3563
|
if (value.cell && !value.cell.dying && --value.cell.refs === 0) {
|
|
3564
|
+
if (actor?.budget) {
|
|
3565
|
+
const row = this.managed.row(this.managed.type(value));
|
|
3566
|
+
if (row.kind !== "shared")
|
|
3567
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
3568
|
+
pending.push({
|
|
3569
|
+
managedRelease: actor.budget.retain({
|
|
3570
|
+
value: value.cell.value,
|
|
3571
|
+
type: row.payload,
|
|
3572
|
+
slots: true,
|
|
3573
|
+
identity: value.cell,
|
|
3574
|
+
}),
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
value.cell.managedLease?.release();
|
|
3578
|
+
value.cell.managedLease = undefined;
|
|
2561
3579
|
value.cell.owner?.blocks.delete(value.cell);
|
|
2562
3580
|
value.cell.owner = null;
|
|
2563
3581
|
pending.push(value.cell.value);
|
|
@@ -2586,17 +3604,18 @@ class ExecutorV1 {
|
|
|
2586
3604
|
const resource = this.pkg.resources?.[value.resource];
|
|
2587
3605
|
if (resource?.kind !== "opaque")
|
|
2588
3606
|
throw new Error("opaque owner lost its selected close");
|
|
2589
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
3607
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
|
|
2590
3608
|
return;
|
|
2591
3609
|
}
|
|
2592
3610
|
case "task": {
|
|
2593
3611
|
// Dropping a handle does not cancel scoped work. Join drains the
|
|
2594
3612
|
// task, and scope close releases any result that was never taken.
|
|
2595
3613
|
const task = this.tasks.get(value.id);
|
|
2596
|
-
|
|
3614
|
+
const owned = task?.handles.delete(value);
|
|
3615
|
+
if (task?.done && (!owned || !task.scopeAlive)) {
|
|
2597
3616
|
const result = task.value;
|
|
2598
|
-
|
|
2599
|
-
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner);
|
|
3617
|
+
pending.push({ taskTaken: task });
|
|
3618
|
+
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner, retainedBy);
|
|
2600
3619
|
return;
|
|
2601
3620
|
}
|
|
2602
3621
|
if (task?.close) {
|
|
@@ -2624,7 +3643,7 @@ class ExecutorV1 {
|
|
|
2624
3643
|
if (this.consumedResources.has(value))
|
|
2625
3644
|
break;
|
|
2626
3645
|
if (!this.closingResources.has(value)) {
|
|
2627
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
3646
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
|
|
2628
3647
|
return;
|
|
2629
3648
|
}
|
|
2630
3649
|
this.consumedResources.add(value);
|
|
@@ -2636,12 +3655,11 @@ class ExecutorV1 {
|
|
|
2636
3655
|
pending.push(...[...value.payload].reverse());
|
|
2637
3656
|
break;
|
|
2638
3657
|
case "vector":
|
|
2639
|
-
pending.push(...
|
|
3658
|
+
pending.push(...releaseVector(value).reverse());
|
|
2640
3659
|
break;
|
|
2641
3660
|
case "stream": {
|
|
2642
3661
|
const adapter = this.generatorStreams.get(value.channelId);
|
|
2643
3662
|
if (adapter) {
|
|
2644
|
-
this.generatorStreams.delete(value.channelId);
|
|
2645
3663
|
adapter.closed = true;
|
|
2646
3664
|
if (adapter.pending !== null)
|
|
2647
3665
|
pending.push(adapter.pending);
|
|
@@ -2687,7 +3705,7 @@ class ExecutorV1 {
|
|
|
2687
3705
|
}
|
|
2688
3706
|
closeValue(value, fault) {
|
|
2689
3707
|
if (value.kind === "encoding")
|
|
2690
|
-
value
|
|
3708
|
+
releaseEncoding(value);
|
|
2691
3709
|
if (value.kind === "host_resource")
|
|
2692
3710
|
this.closeHostResource(value);
|
|
2693
3711
|
if (value.kind === "string")
|
|
@@ -2703,11 +3721,34 @@ class ExecutorV1 {
|
|
|
2703
3721
|
for (const field of value.payload)
|
|
2704
3722
|
this.closeValue(field, fault);
|
|
2705
3723
|
else if (value.kind === "vector")
|
|
2706
|
-
for (const item of value
|
|
3724
|
+
for (const item of releaseVector(value))
|
|
2707
3725
|
this.closeValue(item, fault);
|
|
3726
|
+
else if (value.kind === "rc" && !value.closed) {
|
|
3727
|
+
value.closed = true;
|
|
3728
|
+
const cell = value.cell;
|
|
3729
|
+
if (cell && !cell.dying && --cell.refs === 0) {
|
|
3730
|
+
cell.dying = true;
|
|
3731
|
+
this.closeValue(cell.value, fault);
|
|
3732
|
+
cell.value = UNIT;
|
|
3733
|
+
cell.managedLease?.release();
|
|
3734
|
+
cell.managedLease = undefined;
|
|
3735
|
+
cell.owner?.blocks.delete(cell);
|
|
3736
|
+
cell.owner = null;
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
else if (value.kind === "weak" && !value.closed) {
|
|
3740
|
+
value.closed = true;
|
|
3741
|
+
value.cell.weak--;
|
|
3742
|
+
}
|
|
3743
|
+
this.managed.retire(value);
|
|
2708
3744
|
}
|
|
2709
|
-
newTask() {
|
|
3745
|
+
newTask(budget, type, memberBytes = 0n, scopeAlive = false) {
|
|
2710
3746
|
const task = {
|
|
3747
|
+
handles: new Set(),
|
|
3748
|
+
scopeAlive,
|
|
3749
|
+
budget,
|
|
3750
|
+
memberBytes,
|
|
3751
|
+
managedType: type,
|
|
2711
3752
|
id: this.ids.task(),
|
|
2712
3753
|
grouped: false,
|
|
2713
3754
|
done: false,
|
|
@@ -2720,8 +3761,44 @@ class ExecutorV1 {
|
|
|
2720
3761
|
changed: [],
|
|
2721
3762
|
};
|
|
2722
3763
|
this.tasks.set(task.id, task);
|
|
3764
|
+
if (budget) {
|
|
3765
|
+
if (type == null)
|
|
3766
|
+
throw new Error("task has no managed result descriptor");
|
|
3767
|
+
const row = this.managed.row(type);
|
|
3768
|
+
if (row.kind === "provider" && row.provider === "task")
|
|
3769
|
+
task.managedLease = budget.retain({
|
|
3770
|
+
value: { kind: "task", id: task.id },
|
|
3771
|
+
type,
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
2723
3774
|
return task;
|
|
2724
3775
|
}
|
|
3776
|
+
releaseTaskState(task) {
|
|
3777
|
+
if (task.scopeAlive || task.handles.size || !this.tasks.has(task.id))
|
|
3778
|
+
return;
|
|
3779
|
+
this.releaseRaceMembers(task);
|
|
3780
|
+
task.managedLease?.release();
|
|
3781
|
+
task.managedLease = undefined;
|
|
3782
|
+
this.tasks.delete(task.id);
|
|
3783
|
+
task.budget?.changed();
|
|
3784
|
+
}
|
|
3785
|
+
releaseRaceMembers(task) {
|
|
3786
|
+
const members = task.members;
|
|
3787
|
+
if (!members)
|
|
3788
|
+
return;
|
|
3789
|
+
task.members = undefined;
|
|
3790
|
+
task.memberBytes = 0n;
|
|
3791
|
+
task.budget?.changed();
|
|
3792
|
+
for (const value of members) {
|
|
3793
|
+
if (value.kind !== "task")
|
|
3794
|
+
continue;
|
|
3795
|
+
const member = this.tasks.get(value.id);
|
|
3796
|
+
member?.handles.delete(value);
|
|
3797
|
+
this.managed.retire(value);
|
|
3798
|
+
if (member)
|
|
3799
|
+
this.releaseTaskState(member);
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
2725
3802
|
taskFor(value) {
|
|
2726
3803
|
if (value.kind !== "task")
|
|
2727
3804
|
throw new Error(`expected a task, got ${value.kind}`);
|
|
@@ -2734,21 +3811,80 @@ class ExecutorV1 {
|
|
|
2734
3811
|
if (task.done)
|
|
2735
3812
|
return;
|
|
2736
3813
|
task.done = true;
|
|
3814
|
+
task.resultInitialized = true;
|
|
2737
3815
|
task.value = value;
|
|
2738
3816
|
task.fault = fault;
|
|
3817
|
+
task.budget?.changed();
|
|
2739
3818
|
task.completedAt = ++this.completedTasks;
|
|
2740
3819
|
for (const changed of [...task.changed])
|
|
2741
3820
|
changed();
|
|
2742
3821
|
}
|
|
2743
|
-
|
|
3822
|
+
retainTaskResult(task, budget) {
|
|
3823
|
+
if (!budget)
|
|
3824
|
+
return undefined;
|
|
3825
|
+
if (task.fault || (task.actorError && task.actorError !== "Failed"))
|
|
3826
|
+
return undefined;
|
|
3827
|
+
const row = task.managedType == null ? undefined : this.managed.row(task.managedType);
|
|
3828
|
+
if (row?.kind === "actor_call")
|
|
3829
|
+
return budget.retain({
|
|
3830
|
+
value: task.managedResult,
|
|
3831
|
+
type: row.completion,
|
|
3832
|
+
});
|
|
3833
|
+
if (!row || row.kind !== "provider" || row.children[0] == null)
|
|
3834
|
+
throw new Error("task result lacks its canonical payload descriptor");
|
|
3835
|
+
return budget.retain({
|
|
3836
|
+
value: task.managedResult ?? task.value,
|
|
3837
|
+
type: row.children[0],
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
managedCompletion(task, value, error, envelope) {
|
|
3841
|
+
if (task.managedType == null ||
|
|
3842
|
+
value === null ||
|
|
3843
|
+
(error !== null && error !== "Failed"))
|
|
3844
|
+
return undefined;
|
|
3845
|
+
const provider = this.managed.row(task.managedType);
|
|
3846
|
+
if (provider.kind !== "actor_call")
|
|
3847
|
+
throw new Error("actor completion has no canonical provider descriptor");
|
|
3848
|
+
const type = provider.completion;
|
|
3849
|
+
const result = this.managed.row(type);
|
|
3850
|
+
if (result.kind !== "variant")
|
|
3851
|
+
throw new Error("actor completion has no canonical result cases");
|
|
3852
|
+
const err = runtimeTag(this.pkg.variants[result.shape], "ResultErr");
|
|
3853
|
+
const failure = this.managed.row(result.cases[err][0]);
|
|
3854
|
+
if (failure.kind !== "variant")
|
|
3855
|
+
throw new Error("actor completion has no canonical error cases");
|
|
3856
|
+
if (!envelope)
|
|
3857
|
+
throw new Error("actor completion lost its actual handler envelope");
|
|
3858
|
+
return {
|
|
3859
|
+
value: this.completion(result.shape, failure.shape, value, error),
|
|
3860
|
+
reply: { value: envelope, type: provider.reply },
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false, retainedBy,
|
|
3864
|
+
// The activation this frame runs on behalf of. Its depth carries on, so a
|
|
3865
|
+
// recursion through runtime callbacks reaches the frame limit as a direct
|
|
3866
|
+
// one does.
|
|
3867
|
+
invoker = retainedBy) {
|
|
2744
3868
|
const context = {
|
|
2745
3869
|
id: actor?.id ?? "actor:root",
|
|
2746
3870
|
actor,
|
|
3871
|
+
retainsTransferableFrame: !!retainedBy?.context.generator ||
|
|
3872
|
+
retainedBy?.context.retainsTransferableFrame,
|
|
2747
3873
|
rcOwner,
|
|
2748
|
-
returned,
|
|
2749
|
-
|
|
3874
|
+
returned: (value, released) => {
|
|
3875
|
+
retainedBy?.managedChildren.delete(frame);
|
|
3876
|
+
returned(value);
|
|
3877
|
+
released();
|
|
3878
|
+
},
|
|
3879
|
+
failed: (fault) => {
|
|
3880
|
+
retainedBy?.managedChildren.delete(frame);
|
|
3881
|
+
failed(fault);
|
|
3882
|
+
},
|
|
2750
3883
|
};
|
|
2751
3884
|
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
3885
|
+
if (invoker)
|
|
3886
|
+
frame.depth = invoker.depth + 1;
|
|
3887
|
+
retainedBy?.managedChildren.add(frame);
|
|
2752
3888
|
let cancelled = null;
|
|
2753
3889
|
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
2754
3890
|
cancelled = fault;
|
|
@@ -2840,6 +3976,7 @@ class ExecutorV1 {
|
|
|
2840
3976
|
mailbox: [],
|
|
2841
3977
|
timers: new Set(),
|
|
2842
3978
|
disposals: [],
|
|
3979
|
+
disposalLeases: [],
|
|
2843
3980
|
admission: [],
|
|
2844
3981
|
busy: true,
|
|
2845
3982
|
constructing: true,
|
|
@@ -2883,6 +4020,8 @@ class ExecutorV1 {
|
|
|
2883
4020
|
const retained = combineFaults(primary, stateFault);
|
|
2884
4021
|
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
2885
4022
|
actor.completed = true;
|
|
4023
|
+
actor.stateLease?.release();
|
|
4024
|
+
actor.stateLease = undefined;
|
|
2886
4025
|
for (const closed of actor.closed.splice(0))
|
|
2887
4026
|
closed();
|
|
2888
4027
|
this.raiseFault(act, combineFaults(retained, sweepFault), term.unwind);
|
|
@@ -2919,6 +4058,24 @@ class ExecutorV1 {
|
|
|
2919
4058
|
else
|
|
2920
4059
|
started();
|
|
2921
4060
|
};
|
|
4061
|
+
if (layout.max_heap_bytes != null) {
|
|
4062
|
+
actor.budget = new ManagedBudget(this.managed, BigInt(layout.max_heap_bytes), (root) => this.adoptManaged(root, actor));
|
|
4063
|
+
if (layout.state_type == null)
|
|
4064
|
+
throw new Error("limited actor state lacks a managed descriptor");
|
|
4065
|
+
try {
|
|
4066
|
+
actor.stateLease = actor.budget.retain({
|
|
4067
|
+
value: actor.state,
|
|
4068
|
+
type: layout.state_type,
|
|
4069
|
+
slots: true,
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
catch (error) {
|
|
4073
|
+
if (!(error instanceof HeapExceeded))
|
|
4074
|
+
throw error;
|
|
4075
|
+
failed({ kind: "trap", trap: "heap_exceeded" });
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
2922
4079
|
if (layout.init != null)
|
|
2923
4080
|
actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
|
|
2924
4081
|
else
|
|
@@ -3026,7 +4183,9 @@ class ExecutorV1 {
|
|
|
3026
4183
|
complete: (_value, _error, drained) => drained?.(null),
|
|
3027
4184
|
};
|
|
3028
4185
|
const status = this.admitActorMessage(actor, message);
|
|
3029
|
-
if (status === "full" ||
|
|
4186
|
+
if (status === "full" ||
|
|
4187
|
+
status === "closed" ||
|
|
4188
|
+
status === "trapped") {
|
|
3030
4189
|
actor.disposals.push(payload);
|
|
3031
4190
|
timer.discarded();
|
|
3032
4191
|
this.dispatchActor(actor);
|
|
@@ -3064,7 +4223,21 @@ class ExecutorV1 {
|
|
|
3064
4223
|
const handler = actor.layout.handlers.find((handler) => handler.message_id === operation.message && handler.streams);
|
|
3065
4224
|
if (!handler)
|
|
3066
4225
|
throw new Error("stream request has no producer handler");
|
|
4226
|
+
let managedLease;
|
|
4227
|
+
try {
|
|
4228
|
+
managedLease = this.acquireActorMessage(actor, handler, fields);
|
|
4229
|
+
}
|
|
4230
|
+
catch (error) {
|
|
4231
|
+
if (!(error instanceof HeapExceeded))
|
|
4232
|
+
throw error;
|
|
4233
|
+
this.crashActor(actor, { kind: "trap", trap: "heap_exceeded" }, handler);
|
|
4234
|
+
this.closeValue(payload, error.message);
|
|
4235
|
+
this.completeShim(act, term, UNIT);
|
|
4236
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
3067
4239
|
actor.mailbox.push({
|
|
4240
|
+
managedLease,
|
|
3068
4241
|
caller,
|
|
3069
4242
|
handler,
|
|
3070
4243
|
payload: fields,
|
|
@@ -3085,8 +4258,29 @@ class ExecutorV1 {
|
|
|
3085
4258
|
return;
|
|
3086
4259
|
}
|
|
3087
4260
|
case "call_start": {
|
|
3088
|
-
const task = this.newTask(
|
|
3089
|
-
|
|
4261
|
+
const task = this.newTask(act.context.actor?.budget, term.result && term.result !== "never"
|
|
4262
|
+
? (term.result.managed_type ?? undefined)
|
|
4263
|
+
: undefined);
|
|
4264
|
+
const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope) => {
|
|
4265
|
+
const reply = this.managedCompletion(task, value, error, envelope);
|
|
4266
|
+
if (reply && !closeFault && act.context.actor?.budget) {
|
|
4267
|
+
try {
|
|
4268
|
+
act.context.actor.budget.checkPayload(this.managed.width(reply.reply.type), [reply.reply]);
|
|
4269
|
+
}
|
|
4270
|
+
catch (failure) {
|
|
4271
|
+
if (!(failure instanceof HeapExceeded))
|
|
4272
|
+
throw failure;
|
|
4273
|
+
task.actorError = "Trapped";
|
|
4274
|
+
this.settleTask(task, UNIT, {
|
|
4275
|
+
kind: "trap",
|
|
4276
|
+
trap: "heap_exceeded",
|
|
4277
|
+
});
|
|
4278
|
+
this.failActorBudget(act.context.actor);
|
|
4279
|
+
return false;
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
task.managedResult = reply?.value;
|
|
4283
|
+
task.managedReply = reply?.reply.value;
|
|
3090
4284
|
task.actorError = error;
|
|
3091
4285
|
this.settleTask(task, value ?? UNIT, closeFault ?? null);
|
|
3092
4286
|
return true;
|
|
@@ -3108,8 +4302,20 @@ class ExecutorV1 {
|
|
|
3108
4302
|
throw new Error("actor completion taken before readiness");
|
|
3109
4303
|
act.fault ??= task.fault;
|
|
3110
4304
|
const value = task.value;
|
|
4305
|
+
const lease = this.retainTaskResult(task, act.context.actor?.budget);
|
|
4306
|
+
if (lease)
|
|
4307
|
+
act.managedInputs.push(lease);
|
|
3111
4308
|
task.value = UNIT;
|
|
3112
|
-
|
|
4309
|
+
task.taken = true;
|
|
4310
|
+
const result = task.managedResult ??
|
|
4311
|
+
this.completion(term.result_shape, term.error_shape, value, task.actorError);
|
|
4312
|
+
task.managedResult = undefined;
|
|
4313
|
+
task.managedReply = undefined;
|
|
4314
|
+
this.completeShim(act, term, result);
|
|
4315
|
+
task.handles.delete(args[0]);
|
|
4316
|
+
this.managed.retire(args[0]);
|
|
4317
|
+
task.budget?.changed();
|
|
4318
|
+
this.releaseTaskState(task);
|
|
3113
4319
|
return;
|
|
3114
4320
|
}
|
|
3115
4321
|
case "submit": {
|
|
@@ -3162,6 +4368,15 @@ class ExecutorV1 {
|
|
|
3162
4368
|
complete: (_value, _error, drained) => drained?.(null),
|
|
3163
4369
|
};
|
|
3164
4370
|
const status = this.admitActorMessage(actor, message);
|
|
4371
|
+
if (status === "trapped") {
|
|
4372
|
+
this.closeValueAsync(payload, "actor heap limit exceeded", (fault) => {
|
|
4373
|
+
act.fault ??= fault;
|
|
4374
|
+
finish(this.variant(term.result_shape, "ResultErr", [
|
|
4375
|
+
this.variant(term.error_shape, "ActorErrorTrapped"),
|
|
4376
|
+
]));
|
|
4377
|
+
}, act.context.actor, act.rcOwner);
|
|
4378
|
+
return;
|
|
4379
|
+
}
|
|
3165
4380
|
if (status === "full") {
|
|
3166
4381
|
if (operation.policy === "drop_newest") {
|
|
3167
4382
|
let settled = false;
|
|
@@ -3209,27 +4424,32 @@ class ExecutorV1 {
|
|
|
3209
4424
|
if (isPool && count?.kind !== "i64")
|
|
3210
4425
|
throw new Error("pool construction has no count");
|
|
3211
4426
|
const configured = isPool ? args.slice(0, -1) : args;
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
4427
|
+
// Config, then each backoff child's `(initial, maximum)` pair in
|
|
4428
|
+
// declaration order.
|
|
4429
|
+
const pairs = layout.children.filter((child) => child.backoff).length;
|
|
4430
|
+
const config = configured.slice(0, configured.length - 2 * pairs);
|
|
4431
|
+
let pair = config.length;
|
|
4432
|
+
const declared = layout.children.map((child) => {
|
|
4433
|
+
if (!child.backoff)
|
|
4434
|
+
return null;
|
|
4435
|
+
const initial = configured[pair];
|
|
4436
|
+
const maximum = configured[pair + 1];
|
|
4437
|
+
pair += 2;
|
|
4438
|
+
return initial?.kind === "i64" && maximum?.kind === "i64"
|
|
4439
|
+
? { initial: initial.value, maximum: maximum.value }
|
|
4440
|
+
: undefined;
|
|
4441
|
+
});
|
|
4442
|
+
const validBackoff = declared.every((backoff) => backoff === null ||
|
|
4443
|
+
(backoff !== undefined &&
|
|
4444
|
+
((backoff.initial === 0n && backoff.maximum === 0n) ||
|
|
4445
|
+
(backoff.initial > 0n && backoff.maximum >= backoff.initial))));
|
|
3226
4446
|
if (!validBackoff ||
|
|
3227
4447
|
(count?.kind === "i64" &&
|
|
3228
4448
|
(count.value <= 0n || count.value > 2147483647n))) {
|
|
3229
4449
|
const fault = {
|
|
3230
4450
|
kind: "panic",
|
|
3231
4451
|
message: !validBackoff
|
|
3232
|
-
? "supervisor backoff requires positive initial and maximum >= initial"
|
|
4452
|
+
? "supervisor backoff requires positive initial and maximum >= initial, or both zero"
|
|
3233
4453
|
: "supervisor pool count must be between 1 and 2147483647",
|
|
3234
4454
|
};
|
|
3235
4455
|
this.running = false;
|
|
@@ -3258,7 +4478,7 @@ class ExecutorV1 {
|
|
|
3258
4478
|
teardownChild: null,
|
|
3259
4479
|
teardownCancelTimer: null,
|
|
3260
4480
|
restartTimes: [],
|
|
3261
|
-
backoff,
|
|
4481
|
+
backoffs: declared.flatMap((backoff) => Array.from({ length: members }, () => backoff ?? null)),
|
|
3262
4482
|
restartDelays: new Map(),
|
|
3263
4483
|
restartTimers: new Map(),
|
|
3264
4484
|
closed: [],
|
|
@@ -3463,6 +4683,7 @@ class ExecutorV1 {
|
|
|
3463
4683
|
let admitted = false;
|
|
3464
4684
|
let closing = false;
|
|
3465
4685
|
const owner = this.current.context.actor;
|
|
4686
|
+
const sender = this.current;
|
|
3466
4687
|
const rcOwner = this.current.rcOwner;
|
|
3467
4688
|
const deadline = this.requestDeadline(this.current);
|
|
3468
4689
|
let cancelTimer;
|
|
@@ -3471,11 +4692,12 @@ class ExecutorV1 {
|
|
|
3471
4692
|
? { kind: "Actor", identity: owner.identity }
|
|
3472
4693
|
: { kind: "Task" };
|
|
3473
4694
|
let receiver = null;
|
|
3474
|
-
const finish = (value, error, drained) => {
|
|
4695
|
+
const finish = (value, error, drained, envelope) => {
|
|
3475
4696
|
const alreadySettled = settled;
|
|
3476
4697
|
settled = true;
|
|
3477
4698
|
cancelTimer?.();
|
|
3478
|
-
if (alreadySettled ||
|
|
4699
|
+
if (alreadySettled ||
|
|
4700
|
+
complete(value, error, undefined, envelope) === false) {
|
|
3479
4701
|
// The finishing receiver still owns a reply that its caller no
|
|
3480
4702
|
// longer accepts. Its next turn must wait for this release.
|
|
3481
4703
|
this.closeValueAsync(value ?? UNIT, null, (fault) => {
|
|
@@ -3582,9 +4804,23 @@ class ExecutorV1 {
|
|
|
3582
4804
|
}
|
|
3583
4805
|
return;
|
|
3584
4806
|
}
|
|
4807
|
+
let managedLease;
|
|
4808
|
+
try {
|
|
4809
|
+
managedLease = this.acquireActorMessage(actor, handler, payload);
|
|
4810
|
+
}
|
|
4811
|
+
catch (error) {
|
|
4812
|
+
if (!(error instanceof HeapExceeded))
|
|
4813
|
+
throw error;
|
|
4814
|
+
this.failActorBudget(actor);
|
|
4815
|
+
reject("Trapped");
|
|
4816
|
+
return;
|
|
4817
|
+
}
|
|
3585
4818
|
admitted = true;
|
|
4819
|
+
for (const lease of sender.managedInputs.splice(0))
|
|
4820
|
+
lease.release();
|
|
3586
4821
|
receiver = actor;
|
|
3587
4822
|
message = {
|
|
4823
|
+
managedLease,
|
|
3588
4824
|
caller,
|
|
3589
4825
|
handler,
|
|
3590
4826
|
payload,
|
|
@@ -3607,6 +4843,8 @@ class ExecutorV1 {
|
|
|
3607
4843
|
if (index < 0)
|
|
3608
4844
|
return;
|
|
3609
4845
|
receiver.mailbox.splice(index, 1);
|
|
4846
|
+
message.managedLease?.release();
|
|
4847
|
+
message.managedLease = undefined;
|
|
3610
4848
|
admitted = false;
|
|
3611
4849
|
for (const admit of receiver.admission.splice(0))
|
|
3612
4850
|
admit();
|
|
@@ -3634,13 +4872,56 @@ class ExecutorV1 {
|
|
|
3634
4872
|
closeRequest(done);
|
|
3635
4873
|
};
|
|
3636
4874
|
}
|
|
4875
|
+
acquireActorMessage(actor, handler, payload) {
|
|
4876
|
+
const params = this.functionAt(handler.callable).params.slice(1);
|
|
4877
|
+
if (params.length !== payload.length)
|
|
4878
|
+
throw new Error("typed message payload differs from its handler");
|
|
4879
|
+
if (!actor.budget &&
|
|
4880
|
+
!this.pipes.hasManagedReceivers &&
|
|
4881
|
+
params.every((param) => param.managed_type == null ||
|
|
4882
|
+
!this.managed.needsAdoption(param.managed_type)))
|
|
4883
|
+
return undefined;
|
|
4884
|
+
const roots = params.flatMap((param, index) => {
|
|
4885
|
+
if (param.managed_type == null) {
|
|
4886
|
+
if (!actor.budget)
|
|
4887
|
+
return [];
|
|
4888
|
+
throw new Error("message parameter lacks its managed descriptor");
|
|
4889
|
+
}
|
|
4890
|
+
return [
|
|
4891
|
+
{
|
|
4892
|
+
value: payload[index],
|
|
4893
|
+
type: param.managed_type,
|
|
4894
|
+
slots: true,
|
|
4895
|
+
},
|
|
4896
|
+
];
|
|
4897
|
+
});
|
|
4898
|
+
if (actor.budget)
|
|
4899
|
+
return actor.budget.retainMany(roots);
|
|
4900
|
+
for (const root of roots)
|
|
4901
|
+
this.adoptManaged(root, actor);
|
|
4902
|
+
return undefined;
|
|
4903
|
+
}
|
|
3637
4904
|
admitActorMessage(actor, message) {
|
|
3638
4905
|
if (!actor.alive || actor.closing)
|
|
3639
4906
|
return "closed";
|
|
3640
4907
|
const full = actor.layout.mailbox_capacity != null &&
|
|
3641
4908
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3642
4909
|
let declared = full ? actor.layout.overflow : null;
|
|
4910
|
+
const acquire = () => {
|
|
4911
|
+
try {
|
|
4912
|
+
message.managedLease = this.acquireActorMessage(actor, message.handler, message.payload);
|
|
4913
|
+
}
|
|
4914
|
+
catch (error) {
|
|
4915
|
+
if (!(error instanceof HeapExceeded))
|
|
4916
|
+
throw error;
|
|
4917
|
+
this.failActorBudget(actor);
|
|
4918
|
+
return false;
|
|
4919
|
+
}
|
|
4920
|
+
return true;
|
|
4921
|
+
};
|
|
3643
4922
|
const discard = (lost) => {
|
|
4923
|
+
if (lost.managedLease)
|
|
4924
|
+
actor.disposalLeases.push(lost.managedLease);
|
|
3644
4925
|
actor.disposals.push({
|
|
3645
4926
|
kind: "record",
|
|
3646
4927
|
typeId: "",
|
|
@@ -3658,6 +4939,8 @@ class ExecutorV1 {
|
|
|
3658
4939
|
equals(queued.payload[key.param], message.payload[key.param]))
|
|
3659
4940
|
: -1;
|
|
3660
4941
|
if (queued >= 0) {
|
|
4942
|
+
if (!acquire())
|
|
4943
|
+
return "trapped";
|
|
3661
4944
|
const superseded = actor.mailbox[queued];
|
|
3662
4945
|
actor.mailbox[queued] = message;
|
|
3663
4946
|
discard(superseded);
|
|
@@ -3673,6 +4956,8 @@ class ExecutorV1 {
|
|
|
3673
4956
|
? actor.mailbox.findIndex((queued) => !queued.obligated)
|
|
3674
4957
|
: -1;
|
|
3675
4958
|
if (oldest >= 0) {
|
|
4959
|
+
if (!acquire())
|
|
4960
|
+
return "trapped";
|
|
3676
4961
|
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
3677
4962
|
actor.mailbox.push(message);
|
|
3678
4963
|
discard(evicted);
|
|
@@ -3680,6 +4965,8 @@ class ExecutorV1 {
|
|
|
3680
4965
|
}
|
|
3681
4966
|
if (full)
|
|
3682
4967
|
return "full";
|
|
4968
|
+
if (!acquire())
|
|
4969
|
+
return "trapped";
|
|
3683
4970
|
actor.mailbox.push(message);
|
|
3684
4971
|
this.trace.snapshot("actor.send", {
|
|
3685
4972
|
actor_id: actor.id,
|
|
@@ -3772,7 +5059,9 @@ class ExecutorV1 {
|
|
|
3772
5059
|
if (actor.disposals.length > 0) {
|
|
3773
5060
|
actor.busy = true;
|
|
3774
5061
|
const values = actor.disposals.splice(0);
|
|
5062
|
+
const leases = actor.disposalLeases.splice(0);
|
|
3775
5063
|
this.closeValueAsync({ kind: "vector", elementType: "message", items: values }, this.faultText(actor.crashing), (fault) => {
|
|
5064
|
+
leases.forEach((lease) => lease.release());
|
|
3776
5065
|
actor.busy = false;
|
|
3777
5066
|
if (fault)
|
|
3778
5067
|
this.crashActor(actor, fault);
|
|
@@ -3816,6 +5105,8 @@ class ExecutorV1 {
|
|
|
3816
5105
|
return;
|
|
3817
5106
|
}
|
|
3818
5107
|
actor.mailbox.shift();
|
|
5108
|
+
message.managedLease?.release();
|
|
5109
|
+
message.managedLease = undefined;
|
|
3819
5110
|
for (const admit of actor.admission.splice(0))
|
|
3820
5111
|
admit();
|
|
3821
5112
|
if (message.requestDeadline !== undefined &&
|
|
@@ -3841,10 +5132,21 @@ class ExecutorV1 {
|
|
|
3841
5132
|
caller: message.caller,
|
|
3842
5133
|
requestDeadline: message.requestDeadline,
|
|
3843
5134
|
reply: message.reply,
|
|
3844
|
-
returned: (value) => {
|
|
5135
|
+
returned: (value, released) => {
|
|
3845
5136
|
unwatchStream();
|
|
3846
5137
|
actor.active = null;
|
|
5138
|
+
if (actor.crashing?.kind === "trap" &&
|
|
5139
|
+
actor.crashing.trap === "heap_exceeded") {
|
|
5140
|
+
const fault = actor.crashing;
|
|
5141
|
+
this.closeValueAsync(value, this.faultText(fault), (closeFault) => {
|
|
5142
|
+
released();
|
|
5143
|
+
this.crashActor(actor, combineFaults(fault, closeFault), message.handler);
|
|
5144
|
+
message.complete(null, "Trapped");
|
|
5145
|
+
}, actor, actor.rcOwner);
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
3847
5148
|
const drained = (fault) => {
|
|
5149
|
+
released();
|
|
3848
5150
|
actor.busy = false;
|
|
3849
5151
|
if (fault)
|
|
3850
5152
|
this.crashActor(actor, fault, message.handler);
|
|
@@ -3859,7 +5161,7 @@ class ExecutorV1 {
|
|
|
3859
5161
|
const shape = this.pkg.variants[message.handler.result_shape];
|
|
3860
5162
|
if (shape?.cases[value.tag]?.name === "Err") {
|
|
3861
5163
|
if (message.reply)
|
|
3862
|
-
message.complete(value.payload[0] ?? UNIT, "Failed", drained);
|
|
5164
|
+
message.complete(value.payload[0] ?? UNIT, "Failed", drained, value);
|
|
3863
5165
|
else {
|
|
3864
5166
|
this.crashActor(actor, {
|
|
3865
5167
|
kind: "panic",
|
|
@@ -3870,11 +5172,11 @@ class ExecutorV1 {
|
|
|
3870
5172
|
}
|
|
3871
5173
|
}
|
|
3872
5174
|
else {
|
|
3873
|
-
message.complete(value.payload[0] ?? UNIT, null, drained);
|
|
5175
|
+
message.complete(value.payload[0] ?? UNIT, null, drained, value);
|
|
3874
5176
|
}
|
|
3875
5177
|
}
|
|
3876
5178
|
else {
|
|
3877
|
-
message.complete(value, null, drained);
|
|
5179
|
+
message.complete(value, null, drained, value);
|
|
3878
5180
|
}
|
|
3879
5181
|
},
|
|
3880
5182
|
failed: (fault) => {
|
|
@@ -3950,6 +5252,8 @@ class ExecutorV1 {
|
|
|
3950
5252
|
this.reportFault(actor.closingFault);
|
|
3951
5253
|
}
|
|
3952
5254
|
actor.completed = true;
|
|
5255
|
+
actor.stateLease?.release();
|
|
5256
|
+
actor.stateLease = undefined;
|
|
3953
5257
|
for (const wake of actor.closed.splice(0))
|
|
3954
5258
|
wake();
|
|
3955
5259
|
for (const admit of actor.admission.splice(0))
|
|
@@ -3983,11 +5287,15 @@ class ExecutorV1 {
|
|
|
3983
5287
|
kind: "enum",
|
|
3984
5288
|
typeId: this.variantName(actor.layout.crash_kind),
|
|
3985
5289
|
shape: actor.layout.crash_kind,
|
|
3986
|
-
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code ===
|
|
3987
|
-
? "
|
|
3988
|
-
: code ===
|
|
3989
|
-
? "
|
|
3990
|
-
:
|
|
5290
|
+
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 200
|
|
5291
|
+
? "CrashKindHeapExceeded"
|
|
5292
|
+
: code === 214
|
|
5293
|
+
? "CrashKindTimerOverdue"
|
|
5294
|
+
: code === 213
|
|
5295
|
+
? "CrashKindHandlerFailed"
|
|
5296
|
+
: code === 215
|
|
5297
|
+
? "CrashKindStackOverflow"
|
|
5298
|
+
: "CrashKindFault"),
|
|
3991
5299
|
payload: [],
|
|
3992
5300
|
},
|
|
3993
5301
|
stringValue(String(message)),
|
|
@@ -4169,24 +5477,38 @@ class ExecutorV1 {
|
|
|
4169
5477
|
return;
|
|
4170
5478
|
}
|
|
4171
5479
|
if (owner.restartBusy) {
|
|
4172
|
-
owner.pendingRestarts.push({
|
|
5480
|
+
owner.pendingRestarts.push({
|
|
5481
|
+
child,
|
|
5482
|
+
debts,
|
|
5483
|
+
incarnation: owner.children[child] ?? null,
|
|
5484
|
+
});
|
|
4173
5485
|
return;
|
|
4174
5486
|
}
|
|
4175
5487
|
const now = this.trace.virtualTimeMs;
|
|
4176
|
-
|
|
5488
|
+
const backoff = owner.backoffs[child];
|
|
5489
|
+
if (backoff && backoff.initial > 0n && debts.length > 0 && !delayElapsed) {
|
|
4177
5490
|
const previous = owner.restartDelays.get(child);
|
|
4178
5491
|
const healthy = previous?.publishedAt != null &&
|
|
4179
5492
|
now - previous.publishedAt >= owner.layout.window_secs * 1000;
|
|
4180
|
-
const doubled = previous ? previous.delay * 2n :
|
|
5493
|
+
const doubled = previous ? previous.delay * 2n : backoff.initial;
|
|
4181
5494
|
const delay = !previous || healthy
|
|
4182
|
-
?
|
|
4183
|
-
: doubled <
|
|
5495
|
+
? backoff.initial
|
|
5496
|
+
: doubled < backoff.maximum
|
|
4184
5497
|
? doubled
|
|
4185
|
-
:
|
|
5498
|
+
: backoff.maximum;
|
|
4186
5499
|
owner.restartDelays.set(child, { delay, publishedAt: null });
|
|
4187
5500
|
owner.recovering.add(child);
|
|
4188
5501
|
owner.restartTimers.set(child, this.scheduler.after(delay, () => {
|
|
4189
5502
|
owner.restartTimers.delete(child);
|
|
5503
|
+
// A group restart that already replaced this role recovered its
|
|
5504
|
+
// fault; restarting again would tear down a healthy group and
|
|
5505
|
+
// charge the budget twice.
|
|
5506
|
+
if (owner.restartDelays.get(child)?.publishedAt != null) {
|
|
5507
|
+
for (const debt of debts)
|
|
5508
|
+
this.faultDebts.delete(debt);
|
|
5509
|
+
this.wakeRoles();
|
|
5510
|
+
return;
|
|
5511
|
+
}
|
|
4190
5512
|
this.restartChild(owner, child, debts, true);
|
|
4191
5513
|
}));
|
|
4192
5514
|
return;
|
|
@@ -4233,11 +5555,16 @@ class ExecutorV1 {
|
|
|
4233
5555
|
if (index === undefined) {
|
|
4234
5556
|
for (const debt of debts)
|
|
4235
5557
|
this.faultDebts.delete(debt);
|
|
4236
|
-
|
|
5558
|
+
// This group replaced a queued crash's incarnation, which recovers it.
|
|
5559
|
+
// A replacement that crashed after its own publication still needs
|
|
5560
|
+
// its restart.
|
|
5561
|
+
const replaced = (pending) => affected.includes(pending.child) &&
|
|
5562
|
+
owner.children[pending.child] !== pending.incarnation;
|
|
5563
|
+
for (const pending of owner.pendingRestarts.filter(replaced)) {
|
|
4237
5564
|
for (const debt of pending.debts)
|
|
4238
5565
|
this.faultDebts.delete(debt);
|
|
4239
5566
|
}
|
|
4240
|
-
owner.pendingRestarts = owner.pendingRestarts.filter((pending) => !
|
|
5567
|
+
owner.pendingRestarts = owner.pendingRestarts.filter((pending) => !replaced(pending));
|
|
4241
5568
|
owner.restartBusy = false;
|
|
4242
5569
|
this.wakeRoles();
|
|
4243
5570
|
const pending = owner.pendingRestarts.shift();
|
|
@@ -4249,6 +5576,12 @@ class ExecutorV1 {
|
|
|
4249
5576
|
const delay = owner.restartDelays.get(index);
|
|
4250
5577
|
if (delay)
|
|
4251
5578
|
delay.publishedAt = this.trace.virtualTimeMs;
|
|
5579
|
+
// The crashed role's replacement is the recovery that settles its
|
|
5580
|
+
// fault, as natively; a later sibling's cancelled start does not
|
|
5581
|
+
// reopen it.
|
|
5582
|
+
if (index === child)
|
|
5583
|
+
for (const debt of debts)
|
|
5584
|
+
this.faultDebts.delete(debt);
|
|
4252
5585
|
start();
|
|
4253
5586
|
}, () => {
|
|
4254
5587
|
owner.restartBusy = false;
|
|
@@ -4372,6 +5705,10 @@ class ExecutorV1 {
|
|
|
4372
5705
|
}
|
|
4373
5706
|
drainMailbox(actor, fault, done) {
|
|
4374
5707
|
const messages = actor.mailbox.splice(0);
|
|
5708
|
+
const leases = [
|
|
5709
|
+
...actor.disposalLeases.splice(0),
|
|
5710
|
+
...messages.flatMap((message) => message.managedLease ? [message.managedLease] : []),
|
|
5711
|
+
];
|
|
4375
5712
|
actor.drainingMailbox = true;
|
|
4376
5713
|
this.closeValueAsync({
|
|
4377
5714
|
kind: "vector",
|
|
@@ -4385,6 +5722,7 @@ class ExecutorV1 {
|
|
|
4385
5722
|
})),
|
|
4386
5723
|
],
|
|
4387
5724
|
}, fault, (closeFault) => {
|
|
5725
|
+
leases.forEach((lease) => lease.release());
|
|
4388
5726
|
actor.drainingMailbox = false;
|
|
4389
5727
|
for (const message of messages)
|
|
4390
5728
|
message.complete(null, "Dead");
|
|
@@ -4464,6 +5802,8 @@ class ExecutorV1 {
|
|
|
4464
5802
|
}
|
|
4465
5803
|
actor.completed = true;
|
|
4466
5804
|
actor.alive = false;
|
|
5805
|
+
actor.stateLease?.release();
|
|
5806
|
+
actor.stateLease = undefined;
|
|
4467
5807
|
actor.busy = false;
|
|
4468
5808
|
this.trace.snapshot(actor.terminating ? "actor.terminate" : "actor.stop", { actor_id: actor.id });
|
|
4469
5809
|
this.settleSupervisedEnd(actor, actor.closingFault
|
|
@@ -4570,14 +5910,19 @@ function faultInfo(fault) {
|
|
|
4570
5910
|
return [-2, "Deadline"];
|
|
4571
5911
|
return [212, "UserPanic"];
|
|
4572
5912
|
}
|
|
4573
|
-
return
|
|
4574
|
-
integer_overflow: [201, "IntegerOverflow"],
|
|
4575
|
-
divide_by_zero: [202, "DivideByZero"],
|
|
4576
|
-
shift_out_of_range: [204, "ShiftOutOfRange"],
|
|
4577
|
-
vector_bounds: [205, "IndexOutOfBounds"],
|
|
4578
|
-
actor_refused: [-4, "Refused"],
|
|
4579
|
-
}[fault.trap];
|
|
5913
|
+
return TRAP_FAULTS[fault.trap];
|
|
4580
5914
|
}
|
|
5915
|
+
/// The native exit reason each package trap reports, as code and name.
|
|
5916
|
+
const TRAP_FAULTS = {
|
|
5917
|
+
integer_overflow: [201, "IntegerOverflow"],
|
|
5918
|
+
divide_by_zero: [202, "DivideByZero"],
|
|
5919
|
+
signed_min_div_neg_one: [203, "SignedMinDivNegOne"],
|
|
5920
|
+
shift_out_of_range: [204, "ShiftOutOfRange"],
|
|
5921
|
+
vector_bounds: [205, "IndexOutOfBounds"],
|
|
5922
|
+
actor_refused: [-4, "Refused"],
|
|
5923
|
+
heap_exceeded: [200, "HeapExceeded"],
|
|
5924
|
+
stack_overflow: [215, "StackOverflow"],
|
|
5925
|
+
};
|
|
4581
5926
|
function faultApplicationMessage(fault) {
|
|
4582
5927
|
return fault.kind === "panic" &&
|
|
4583
5928
|
(fault.cancelled || fault.deadline || fault.timerOverdue)
|
|
@@ -4606,12 +5951,30 @@ function faultReport(fault, secondary = false) {
|
|
|
4606
5951
|
(fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
|
|
4607
5952
|
}
|
|
4608
5953
|
// ── refs ────────────────────────────────────────────────────────────────────
|
|
4609
|
-
function ownedRef(value) {
|
|
4610
|
-
|
|
5954
|
+
function ownedRef(value, budget, type) {
|
|
5955
|
+
if (budget && type == null)
|
|
5956
|
+
throw new Error("owned value has no managed descriptor");
|
|
5957
|
+
return {
|
|
5958
|
+
kind: "cell",
|
|
5959
|
+
cell: {
|
|
5960
|
+
value,
|
|
5961
|
+
valid: true,
|
|
5962
|
+
budget,
|
|
5963
|
+
type,
|
|
5964
|
+
lease: budget?.retain({ value, type: type }),
|
|
5965
|
+
},
|
|
5966
|
+
};
|
|
4611
5967
|
}
|
|
4612
5968
|
function writeRef(ref, value) {
|
|
4613
5969
|
switch (ref.kind) {
|
|
4614
5970
|
case "cell":
|
|
5971
|
+
if (ref.cell.budget) {
|
|
5972
|
+
if (ref.cell.type == null)
|
|
5973
|
+
throw new Error("owned place has no managed descriptor");
|
|
5974
|
+
const lease = ref.cell.budget.retain({ value, type: ref.cell.type });
|
|
5975
|
+
ref.cell.lease?.release();
|
|
5976
|
+
ref.cell.lease = lease;
|
|
5977
|
+
}
|
|
4615
5978
|
ref.cell.value = value;
|
|
4616
5979
|
ref.cell.valid = true;
|
|
4617
5980
|
return;
|
|
@@ -4626,8 +5989,11 @@ function writeRef(ref, value) {
|
|
|
4626
5989
|
/// Only a cell of its own can be ended; a projection lives as long as the
|
|
4627
5990
|
/// storage it reaches through.
|
|
4628
5991
|
function invalidateRef(ref) {
|
|
4629
|
-
if (ref.kind === "cell")
|
|
5992
|
+
if (ref.kind === "cell") {
|
|
4630
5993
|
ref.cell.valid = false;
|
|
5994
|
+
ref.cell.lease?.release();
|
|
5995
|
+
ref.cell.lease = undefined;
|
|
5996
|
+
}
|
|
4631
5997
|
else
|
|
4632
5998
|
writeRef(ref, UNIT);
|
|
4633
5999
|
}
|
|
@@ -4734,11 +6100,7 @@ function truth(value) {
|
|
|
4734
6100
|
: value.kind === "i64" && value.value !== 0n;
|
|
4735
6101
|
}
|
|
4736
6102
|
function bytesValue(bytes) {
|
|
4737
|
-
return {
|
|
4738
|
-
kind: "vector",
|
|
4739
|
-
elementType: "u8",
|
|
4740
|
-
items: bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })),
|
|
4741
|
-
};
|
|
6103
|
+
return byteVectorValue(bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })));
|
|
4742
6104
|
}
|
|
4743
6105
|
function renderMessage(value) {
|
|
4744
6106
|
return value.kind === "string" ? value.value : canonical(value);
|
|
@@ -4758,7 +6120,7 @@ function toComparable(value) {
|
|
|
4758
6120
|
case "enum":
|
|
4759
6121
|
return [value.tag, value.payload.map(toComparable)];
|
|
4760
6122
|
case "vector":
|
|
4761
|
-
return value.
|
|
6123
|
+
return vectorItems(value).map(toComparable);
|
|
4762
6124
|
case "rc":
|
|
4763
6125
|
return "Rc";
|
|
4764
6126
|
case "weak":
|
|
@@ -4798,12 +6160,18 @@ function trapMessage(trap) {
|
|
|
4798
6160
|
return "integer overflow";
|
|
4799
6161
|
case "divide_by_zero":
|
|
4800
6162
|
return "divide by zero";
|
|
6163
|
+
case "signed_min_div_neg_one":
|
|
6164
|
+
return "signed minimum divided by -1";
|
|
4801
6165
|
case "shift_out_of_range":
|
|
4802
6166
|
return "shift amount out of range";
|
|
4803
6167
|
case "vector_bounds":
|
|
4804
6168
|
return "index out of bounds";
|
|
4805
6169
|
case "actor_refused":
|
|
4806
6170
|
return "delivery refused by interceptor";
|
|
6171
|
+
case "heap_exceeded":
|
|
6172
|
+
return "actor heap limit exceeded";
|
|
6173
|
+
case "stack_overflow":
|
|
6174
|
+
return "stack overflow";
|
|
4807
6175
|
}
|
|
4808
6176
|
}
|
|
4809
6177
|
/// The operand type the package records. `isize` and `usize` are 64-bit in the
|
|
@@ -4853,8 +6221,16 @@ function checkedFailure(op, lhs, rhs, ty) {
|
|
|
4853
6221
|
return null;
|
|
4854
6222
|
}
|
|
4855
6223
|
const scalar = intTy(ty);
|
|
4856
|
-
if (
|
|
4857
|
-
|
|
6224
|
+
if (op === "Divide" || op === "Modulo") {
|
|
6225
|
+
if (rhs.value === 0n)
|
|
6226
|
+
return "divide_by_zero";
|
|
6227
|
+
// Both the quotient and the remainder of the signed minimum by -1 trap,
|
|
6228
|
+
// although the remainder alone would be representable.
|
|
6229
|
+
if (scalar.signed &&
|
|
6230
|
+
rhs.value === -1n &&
|
|
6231
|
+
lhs.value === -(1n << (scalar.bits - 1n))) {
|
|
6232
|
+
return "signed_min_div_neg_one";
|
|
6233
|
+
}
|
|
4858
6234
|
}
|
|
4859
6235
|
if ((op === "Shl" || op === "Shr") &&
|
|
4860
6236
|
(rhs.value < 0n || rhs.value >= scalar.bits)) {
|
|
@@ -5038,8 +6414,8 @@ function equals(lhs, rhs) {
|
|
|
5038
6414
|
lhs.payload.every((field, at) => equals(field, rhs.payload[at])));
|
|
5039
6415
|
}
|
|
5040
6416
|
if (lhs.kind === "vector" && rhs.kind === "vector") {
|
|
5041
|
-
return (lhs
|
|
5042
|
-
lhs.
|
|
6417
|
+
return (vectorLength(lhs) === vectorLength(rhs) &&
|
|
6418
|
+
vectorItems(lhs).every((item, at) => equals(item, vectorElement(rhs, at))));
|
|
5043
6419
|
}
|
|
5044
6420
|
return canonical(lhs) === canonical(rhs);
|
|
5045
6421
|
}
|