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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,556 @@
1
+ import { vectorStorage } from "../values.js";
2
+ import { cloneEncoding } from "./encoding-storage.js";
3
+ const U64_MAX = 0xffffffffffffffffn;
4
+ export class HeapExceeded extends Error {
5
+ constructor() {
6
+ super("actor heap limit exceeded");
7
+ }
8
+ }
9
+ /** Compiler-authored widths and children describe payloads; VM backings supply
10
+ * identity and capacity. No host-object or allocator size enters this graph. */
11
+ export class ManagedLayouts {
12
+ rows;
13
+ retained;
14
+ types = new WeakMap();
15
+ retired = new WeakSet();
16
+ adoption = new Map();
17
+ constructor(rows, retained) {
18
+ this.rows = rows;
19
+ this.retained = retained;
20
+ }
21
+ row(type) {
22
+ const row = this.rows[type];
23
+ if (!row)
24
+ throw new Error(`managed type ${type} is absent`);
25
+ return row;
26
+ }
27
+ stamp(value, type) {
28
+ this.types.set(value, type);
29
+ const row = this.row(type);
30
+ if (row.kind === "callable" &&
31
+ row.captures &&
32
+ (value.kind === "closure" || value.kind === "record"))
33
+ value.managedCaptures = row.captures;
34
+ if (row.kind === "dynamic" && value.kind === "dyn")
35
+ value.managedPayload ??= this.types.get(value.value);
36
+ if (value.kind === "vector") {
37
+ const storage = value.storage ?? vectorStorage(value);
38
+ if (row.kind === "bytes") {
39
+ storage.bytes = true;
40
+ storage.shareOnCopy = true;
41
+ }
42
+ else if (row.kind === "vector" ||
43
+ row.kind === "array" ||
44
+ row.kind === "slice") {
45
+ storage.shareOnCopy = row.share_on_copy;
46
+ }
47
+ }
48
+ }
49
+ width(type, capacity = 1) {
50
+ return checked(BigInt(this.row(type).slot_bytes) * BigInt(capacity));
51
+ }
52
+ /** Propagate nested compiler descriptors before an unbounded value is copied
53
+ * or suspended. Unknown foreign payloads need no quota graph here; acquiring
54
+ * the same value into a bounded owner still requires its real visitor. */
55
+ stampGraph(root) {
56
+ this.collect(root, new Map(), undefined, undefined, undefined, (value, type) => {
57
+ const row = this.row(type);
58
+ if (row.kind === "provider" && row.provider === "opaque")
59
+ return { identity: value, bytes: 0n, children: [] };
60
+ if (!this.retained)
61
+ throw new Error("managed provider has no retained-value visitor");
62
+ return this.retained(value, type);
63
+ });
64
+ }
65
+ needsAdoption(type) {
66
+ const cached = this.adoption.get(type);
67
+ if (cached != null)
68
+ return cached;
69
+ const active = new Set();
70
+ const contains = (type) => {
71
+ if (active.has(type))
72
+ return false;
73
+ active.add(type);
74
+ const row = this.row(type);
75
+ switch (row.kind) {
76
+ case "generator":
77
+ case "actor_call":
78
+ case "dynamic":
79
+ return true;
80
+ case "provider":
81
+ return ["task", "stream", "sink"].includes(row.provider);
82
+ case "callable":
83
+ return row.captures == null || row.captures.some(contains);
84
+ case "aggregate":
85
+ return row.fields.some(contains);
86
+ case "variant":
87
+ return row.cases.some((fields) => fields.some(contains));
88
+ case "shared":
89
+ return !row.weak;
90
+ case "map":
91
+ return contains(row.key) || contains(row.value);
92
+ case "array":
93
+ case "vector":
94
+ case "slice":
95
+ case "set":
96
+ return contains(row.element);
97
+ default:
98
+ return false;
99
+ }
100
+ };
101
+ const result = contains(type);
102
+ this.adoption.set(type, result);
103
+ return result;
104
+ }
105
+ retire(value) {
106
+ this.retired.add(value);
107
+ }
108
+ type(value) {
109
+ const type = this.types.get(value);
110
+ if (type == null)
111
+ throw new Error("managed payload has no compiler descriptor");
112
+ return type;
113
+ }
114
+ /** Describe native copy allocations without retaining or changing inputs. */
115
+ copy(value, type) {
116
+ const row = this.row(type);
117
+ this.stamp(value, type);
118
+ switch (row.kind) {
119
+ case "scalar":
120
+ case "handle":
121
+ case "string":
122
+ case "bytes":
123
+ case "shared":
124
+ return value;
125
+ case "array":
126
+ case "vector":
127
+ case "slice": {
128
+ if (value.kind !== "vector")
129
+ throw new Error("managed copy has no sequence");
130
+ const storage = vectorStorage(value);
131
+ if (row.share_on_copy && value.items.length)
132
+ return value;
133
+ const items = value.items.map((item) => this.copy(item, row.element));
134
+ return {
135
+ ...value,
136
+ items,
137
+ storage: { ...storage, items, capacity: items.length, owners: 1 },
138
+ };
139
+ }
140
+ case "aggregate":
141
+ if (value.kind !== "record")
142
+ throw new Error("managed copy has no fields");
143
+ return {
144
+ ...value,
145
+ fields: value.fields.map((item, index) => this.copy(item, row.fields[index])),
146
+ };
147
+ case "variant":
148
+ if (value.kind !== "enum")
149
+ throw new Error("managed copy has no variant");
150
+ return {
151
+ ...value,
152
+ payload: value.payload.map((item, index) => this.copy(item, row.cases[value.tag][index])),
153
+ };
154
+ case "callable": {
155
+ if (value.kind === "function")
156
+ return value;
157
+ const environment = value.kind === "record"
158
+ ? value
159
+ : value.kind === "closure" && value.environment.kind === "record"
160
+ ? value.environment
161
+ : null;
162
+ if (!environment ||
163
+ (value.kind !== "closure" && value.kind !== "record"))
164
+ throw new Error("managed copy has no captures");
165
+ const captures = row.captures ?? value.managedCaptures;
166
+ if (!captures)
167
+ throw new Error("callable has no concrete capture descriptor");
168
+ const copied = {
169
+ ...environment,
170
+ fields: environment.fields.map((item, index) => this.copy(item, captures[index])),
171
+ };
172
+ const result = value.kind === "record" ? copied : { ...value, environment: copied };
173
+ if (result.kind === "closure" || result.kind === "record")
174
+ result.managedCaptures = captures;
175
+ return result;
176
+ }
177
+ case "map":
178
+ case "set": {
179
+ if (value.kind !== "map")
180
+ throw new Error("managed copy has no map");
181
+ const key = row.kind === "map" ? row.key : row.element;
182
+ return {
183
+ ...value,
184
+ entries: new Map([...value.entries].map(([slot, entry]) => [
185
+ slot,
186
+ {
187
+ key: this.copy(entry.key, key),
188
+ value: row.kind === "map"
189
+ ? this.copy(entry.value, row.value)
190
+ : entry.value,
191
+ },
192
+ ])),
193
+ };
194
+ }
195
+ case "dynamic":
196
+ if (value.kind !== "dyn")
197
+ throw new Error("managed copy has no box");
198
+ return {
199
+ ...value,
200
+ value: this.copy(value.value, value.managedPayload ?? this.type(value.value)),
201
+ };
202
+ case "generator":
203
+ throw new Error("a generator cannot be copied");
204
+ case "actor_call":
205
+ return value;
206
+ case "provider":
207
+ if (row.provider === "json" ||
208
+ row.provider === "yaml" ||
209
+ row.provider === "toml") {
210
+ if (value.kind !== "encoding")
211
+ throw new Error("managed encoding has no tree");
212
+ return cloneEncoding(value, true);
213
+ }
214
+ if (row.provider === "task" || row.provider === "sink")
215
+ return value;
216
+ throw new HeapExceeded();
217
+ }
218
+ }
219
+ collect(root, allocations, substitution, stringGrowth, retained, providerGraph = this.retained, storageVisitor) {
220
+ const active = new Set();
221
+ const allocation = (identity, bytes, children = () => { }) => {
222
+ checked(bytes);
223
+ if (storageVisitor) {
224
+ storageVisitor(identity, bytes, children);
225
+ return;
226
+ }
227
+ const prior = allocations.get(identity);
228
+ if (prior != null && prior !== bytes)
229
+ throw new Error("managed storage has conflicting widths");
230
+ if (prior != null || active.has(identity))
231
+ return;
232
+ allocations.set(identity, bytes);
233
+ active.add(identity);
234
+ children();
235
+ active.delete(identity);
236
+ };
237
+ const visit = (value, type) => {
238
+ if (substitution?.before === value)
239
+ value = substitution.after;
240
+ if (this.retired.has(value))
241
+ return;
242
+ if (value.kind === "unit")
243
+ return;
244
+ this.stamp(value, type);
245
+ const row = this.row(type);
246
+ switch (row.kind) {
247
+ case "scalar":
248
+ case "handle":
249
+ return;
250
+ case "string": {
251
+ if (value.kind !== "string")
252
+ throw new Error("managed string has no UTF-8 backing");
253
+ if (stringGrowth?.value === value) {
254
+ allocation(stringGrowth.identity, BigInt(stringGrowth.capacity));
255
+ return;
256
+ }
257
+ const storage = value.managedStorage();
258
+ if (storage.capacity)
259
+ allocation(storage.identity, BigInt(storage.capacity));
260
+ return;
261
+ }
262
+ case "bytes": {
263
+ if (value.kind !== "vector")
264
+ throw new Error("managed bytes have no backing");
265
+ const storage = value.storage ?? vectorStorage(value);
266
+ if (storage.capacity)
267
+ allocation(storage, BigInt(storage.capacity));
268
+ return;
269
+ }
270
+ case "vector":
271
+ case "array":
272
+ case "slice": {
273
+ if (value.kind !== "vector")
274
+ throw new Error("managed sequence has no backing");
275
+ const storage = value.storage ?? vectorStorage(value);
276
+ if (storage.capacity)
277
+ allocation(storage, this.width(row.element, storage.capacity), () => storage.items.forEach((item) => visit(item, row.element)));
278
+ return;
279
+ }
280
+ case "aggregate":
281
+ if (value.kind !== "record")
282
+ throw new Error("managed aggregate has no fields");
283
+ row.fields.forEach((child, index) => visit(value.fields[index], child));
284
+ return;
285
+ case "variant":
286
+ if (value.kind !== "enum")
287
+ throw new Error("managed variant has no payload");
288
+ const payload = () => row.cases[value.tag].forEach((child, index) => visit(value.payload[index], child));
289
+ if (row.indirect)
290
+ allocation(value, BigInt(row.payload_bytes), payload);
291
+ else
292
+ payload();
293
+ return;
294
+ case "map":
295
+ case "set": {
296
+ if (value.kind !== "map")
297
+ throw new Error("managed map has no backing");
298
+ const capacity = value.capacity ?? 0;
299
+ const key = row.kind === "map" ? row.key : row.element;
300
+ const slot = this.width(key) + (row.kind === "map" ? this.width(row.value) : 0n);
301
+ if (capacity)
302
+ allocation(value.entries, checked(slot * BigInt(capacity)), () => {
303
+ for (const entry of value.entries.values()) {
304
+ visit(entry.key, key);
305
+ if (row.kind === "map")
306
+ visit(entry.value, row.value);
307
+ }
308
+ });
309
+ return;
310
+ }
311
+ case "callable": {
312
+ if (value.kind === "function")
313
+ return;
314
+ const environment = value.kind === "record"
315
+ ? value
316
+ : value.kind === "closure" && value.environment.kind === "record"
317
+ ? value.environment
318
+ : null;
319
+ if (!environment ||
320
+ (value.kind !== "closure" && value.kind !== "record"))
321
+ throw new Error("managed callable has no capture environment");
322
+ if (this.retired.has(environment))
323
+ return;
324
+ const fields = environment.fields;
325
+ const captures = row.captures ?? value.managedCaptures;
326
+ if (!captures)
327
+ throw new Error("callable has no concrete capture descriptor");
328
+ const width = captures.reduce((total, type) => checked(total + this.width(type)), 0n);
329
+ allocation(environment, width, () => captures.forEach((type, index) => visit(fields[index], type)));
330
+ return;
331
+ }
332
+ case "shared": {
333
+ if (value.kind !== "rc" && value.kind !== "weak")
334
+ throw new Error("managed shared value has no cell");
335
+ if (value.closed || !value.cell || row.weak)
336
+ return;
337
+ retained?.(value, type);
338
+ allocation(value.cell, this.width(row.payload), () => visit(value.cell.value, row.payload));
339
+ return;
340
+ }
341
+ case "dynamic": {
342
+ if (value.kind !== "dyn")
343
+ throw new Error("managed dynamic value has no box");
344
+ const payload = value.managedPayload ?? this.types.get(value.value);
345
+ if (payload == null)
346
+ throw new Error("managed dynamic payload has no compiler descriptor");
347
+ allocation(value, this.width(payload), () => visit(value.value, payload));
348
+ return;
349
+ }
350
+ case "generator":
351
+ case "actor_call":
352
+ case "provider": {
353
+ retained?.(value, type);
354
+ if (!providerGraph)
355
+ throw new Error("managed provider has no retained-value visitor");
356
+ const graph = providerGraph(value, type);
357
+ allocation(graph.identity, graph.bytes, () => {
358
+ for (const child of graph.children) {
359
+ if (child.slots)
360
+ allocation(child.identity ?? child, this.width(child.type), () => visit(child.value, child.type));
361
+ else
362
+ visit(child.value, child.type);
363
+ }
364
+ });
365
+ return;
366
+ }
367
+ }
368
+ };
369
+ if (root.slots)
370
+ allocation(root.identity ?? root, this.width(root.type), () => visit(root.value, root.type));
371
+ else
372
+ visit(root.value, root.type);
373
+ }
374
+ }
375
+ /** One incarnation owns backing edges. Retaining an existing backing does not
376
+ * revisit its payload; its children belong to that backing until its final
377
+ * local retention ends. Mutable/provider publication invalidates those edges
378
+ * and reconciles them through the same typed visitor before acquisition. */
379
+ export class ManagedBudget {
380
+ layouts;
381
+ limit;
382
+ acquired;
383
+ roots = new Set();
384
+ nodes = new Map();
385
+ edges = new Map();
386
+ retainedBytes = 0n;
387
+ dirty = false;
388
+ constructor(layouts, limit, acquired) {
389
+ this.layouts = layouts;
390
+ this.limit = limit;
391
+ this.acquired = acquired;
392
+ }
393
+ allocations(extra = [], substitution, stringGrowth) {
394
+ const allocations = new Map();
395
+ for (const root of this.roots)
396
+ this.layouts.collect(root, allocations, substitution, stringGrowth);
397
+ for (const root of extra)
398
+ this.layouts.collect(root, allocations);
399
+ return allocations;
400
+ }
401
+ get liveBytes() {
402
+ this.refresh();
403
+ return this.retainedBytes;
404
+ }
405
+ changed() {
406
+ this.dirty = true;
407
+ }
408
+ bytes(nodes) {
409
+ let bytes = 0n;
410
+ for (const node of nodes.values())
411
+ bytes = checked(bytes + node.bytes);
412
+ return bytes;
413
+ }
414
+ graph(roots, known = new Map()) {
415
+ const nodes = new Map();
416
+ const edges = new Map();
417
+ let fresh = false;
418
+ for (const root of roots) {
419
+ let parent = new Set();
420
+ edges.set(root, parent);
421
+ const storage = (identity, bytes, children) => {
422
+ parent.add(identity);
423
+ const prior = nodes.get(identity) ?? known.get(identity);
424
+ if (prior?.bytes === bytes)
425
+ return;
426
+ fresh = true;
427
+ if (prior && !known.has(identity))
428
+ throw new Error("managed storage has conflicting widths");
429
+ const node = { bytes, children: new Set() };
430
+ nodes.set(identity, node);
431
+ const outer = parent;
432
+ parent = node.children;
433
+ children();
434
+ parent = outer;
435
+ };
436
+ this.layouts.collect(root, new Map(), undefined, undefined, undefined, undefined, storage);
437
+ }
438
+ return { nodes: fresh ? nodes : known, edges, fresh };
439
+ }
440
+ refresh() {
441
+ if (!this.dirty)
442
+ return;
443
+ const graph = this.graph([...this.roots]);
444
+ this.nodes = graph.nodes;
445
+ this.edges = graph.edges;
446
+ this.retainedBytes = this.bytes(graph.nodes);
447
+ this.dirty = false;
448
+ }
449
+ prune() {
450
+ const live = new Set();
451
+ const pending = [...this.edges.values()].flatMap((edges) => [...edges]);
452
+ while (pending.length) {
453
+ const identity = pending.pop();
454
+ if (live.has(identity))
455
+ continue;
456
+ live.add(identity);
457
+ pending.push(...(this.nodes.get(identity)?.children ?? []));
458
+ }
459
+ for (const [identity, node] of this.nodes)
460
+ if (!live.has(identity)) {
461
+ this.nodes.delete(identity);
462
+ this.retainedBytes -= node.bytes;
463
+ }
464
+ }
465
+ check(extra) {
466
+ this.refresh();
467
+ let graph = this.graph(extra, this.nodes);
468
+ if (graph.fresh)
469
+ graph = this.graph([...this.roots, ...extra]);
470
+ if (graph.fresh && this.bytes(graph.nodes) > this.limit)
471
+ throw new HeapExceeded();
472
+ }
473
+ checkPayload(bytes, children) {
474
+ const allocations = this.allocations(children);
475
+ allocations.set({}, checked(bytes));
476
+ if (total(allocations) > this.limit)
477
+ throw new HeapExceeded();
478
+ this.changed();
479
+ }
480
+ checkMutation(before, after, detached = false) {
481
+ const allocations = this.allocations([], { before, after });
482
+ if (detached)
483
+ this.layouts.collect({ value: before, type: this.layouts.type(before) }, allocations);
484
+ if (total(allocations) > this.limit)
485
+ throw new HeapExceeded();
486
+ this.changed();
487
+ }
488
+ checkStringGrowth(value, capacity, detached = false) {
489
+ const allocations = this.allocations([], undefined, {
490
+ value,
491
+ identity: {},
492
+ capacity,
493
+ });
494
+ if (detached)
495
+ this.layouts.collect({ value, type: this.layouts.type(value) }, allocations);
496
+ if (total(allocations) > this.limit)
497
+ throw new HeapExceeded();
498
+ this.changed();
499
+ }
500
+ retain(root) {
501
+ return this.retainMany([root]);
502
+ }
503
+ retainMany(roots) {
504
+ roots = roots.map((root) => ({
505
+ ...root,
506
+ ...(root.slots ? { identity: root.identity ?? root } : {}),
507
+ }));
508
+ this.refresh();
509
+ let graph = this.graph(roots, this.nodes);
510
+ const acquired = graph.fresh;
511
+ // New storage can have been moved into an existing mutable owner since
512
+ // its previous snapshot. Reconcile that ownership before committing any
513
+ // new bytes; ordinary alias retains keep the existing child edges.
514
+ if (graph.fresh)
515
+ graph = this.graph([...this.roots, ...roots]);
516
+ if (graph.fresh && this.bytes(graph.nodes) > this.limit)
517
+ throw new HeapExceeded();
518
+ this.nodes = graph.nodes;
519
+ if (graph.fresh)
520
+ this.retainedBytes = this.bytes(graph.nodes);
521
+ for (const [root, edges] of graph.edges)
522
+ this.edges.set(root, edges);
523
+ roots.forEach((root) => this.roots.add(root));
524
+ if (acquired)
525
+ roots.forEach((root) => this.acquired?.(root));
526
+ let released = false;
527
+ return {
528
+ roots,
529
+ release: () => {
530
+ if (released)
531
+ return;
532
+ released = true;
533
+ this.refresh();
534
+ let retained = false;
535
+ roots.forEach((root) => {
536
+ this.roots.delete(root);
537
+ retained ||= !!this.edges.get(root)?.size;
538
+ this.edges.delete(root);
539
+ });
540
+ if (retained)
541
+ this.prune();
542
+ },
543
+ };
544
+ }
545
+ }
546
+ function checked(bytes) {
547
+ if (bytes < 0n || bytes > U64_MAX)
548
+ throw new HeapExceeded();
549
+ return bytes;
550
+ }
551
+ function total(allocations) {
552
+ let bytes = 0n;
553
+ for (const width of allocations.values())
554
+ bytes = checked(bytes + width);
555
+ return bytes;
556
+ }
@@ -1,4 +1,5 @@
1
1
  import { validCodecShape } from "./package-validator.generated.js";
2
+ import { staticOperations } from "./package.js";
2
3
  function require(condition, message) {
3
4
  if (!condition)
4
5
  throw new Error(message);
@@ -23,6 +24,67 @@ export function validatePackageGraph(pkg) {
23
24
  const supervisors = pkg.supervisors ?? [];
24
25
  const resources = pkg.resources ?? [];
25
26
  const recipes = pkg.structural_render ?? [];
27
+ const managed = pkg.managed_types ?? [];
28
+ indexed(managed, "managed type");
29
+ const managedType = (id) => id == null ? null : at(managed, id, "managed type");
30
+ const width = (value) => {
31
+ require(/^(0|[1-9][0-9]*)$/.test(value), "managed width is not decimal u64");
32
+ require(BigInt(value) <= 0xffffffffffffffffn, "managed width exceeds u64");
33
+ };
34
+ for (const row of managed) {
35
+ width(row.slot_bytes);
36
+ switch (row.kind) {
37
+ case "scalar":
38
+ case "handle":
39
+ case "string":
40
+ case "bytes":
41
+ case "dynamic":
42
+ break;
43
+ case "array":
44
+ width(row.length);
45
+ managedType(row.element);
46
+ break;
47
+ case "vector":
48
+ case "slice":
49
+ case "set":
50
+ managedType(row.element);
51
+ break;
52
+ case "map":
53
+ managedType(row.key);
54
+ managedType(row.value);
55
+ break;
56
+ case "aggregate":
57
+ row.fields.forEach(managedType);
58
+ break;
59
+ case "variant":
60
+ width(row.payload_bytes);
61
+ at(pkg.variants, row.shape, "managed variant shape");
62
+ row.cases.forEach((fields) => fields.forEach(managedType));
63
+ break;
64
+ case "callable":
65
+ row.captures?.forEach(managedType);
66
+ break;
67
+ case "provider":
68
+ if (row.invocation_slot_bytes != null)
69
+ width(row.invocation_slot_bytes);
70
+ row.children.forEach(managedType);
71
+ break;
72
+ case "actor_call":
73
+ managedType(row.completion);
74
+ managedType(row.reply);
75
+ break;
76
+ case "generator":
77
+ width(row.payload_bytes);
78
+ managedType(row.yielded);
79
+ managedType(row.returned);
80
+ break;
81
+ case "shared":
82
+ managedType(row.payload);
83
+ break;
84
+ default:
85
+ exhaustive(row);
86
+ }
87
+ }
26
88
  indexed(pkg.functions, "function");
27
89
  indexed(pkg.aggregates, "aggregate");
28
90
  indexed(pkg.variants, "variant");
@@ -149,6 +211,9 @@ export function validatePackageGraph(pkg) {
149
211
  }
150
212
  }
151
213
  for (const actor of actors) {
214
+ if (actor.max_heap_bytes != null)
215
+ width(actor.max_heap_bytes);
216
+ managedType(actor.state_type);
152
217
  for (const id of [
153
218
  actor.init,
154
219
  actor.start,
@@ -314,13 +379,17 @@ export function validatePackageGraph(pkg) {
314
379
  .length, `edge to block ${edge.to} has ${edge.args.length} arguments, expected ${target.params.length}`);
315
380
  edge.args.forEach(value);
316
381
  };
317
- const result = (def) => define(def.value);
382
+ const result = (def) => {
383
+ managedType(def.managed_type);
384
+ define(def.value);
385
+ };
318
386
  fn.params.forEach(result);
319
387
  // Results exist on authored successor edges. This pass checks identity,
320
388
  // not dominance or whether a particular path owns the referenced value.
321
389
  for (const block of fn.blocks) {
322
390
  block.params.forEach(result);
323
- for (const op of block.ops) {
391
+ for (const op of staticOperations(block)) {
392
+ op.definitions?.forEach((def) => managedType(def.managed_type));
324
393
  if ("dst" in op && op.dst != null)
325
394
  define(op.dst);
326
395
  if (op.op === "destructure" || op.op === "variant.destructure")
@@ -343,6 +412,7 @@ export function validatePackageGraph(pkg) {
343
412
  term.arms.forEach((arm) => arm.fields.forEach(result));
344
413
  }
345
414
  for (const declaration of fn.places) {
415
+ managedType(declaration.managed_type);
346
416
  switch (declaration.origin) {
347
417
  case "local":
348
418
  case "runtime":
@@ -537,6 +607,7 @@ export function validatePackageGraph(pkg) {
537
607
  break;
538
608
  case "task.race":
539
609
  scope(op.scope);
610
+ width(op.member_slot_bytes);
540
611
  op.members.forEach(value);
541
612
  break;
542
613
  case "task.spawn":
@@ -570,6 +641,10 @@ export function validatePackageGraph(pkg) {
570
641
  if ("request_shapes" in term)
571
642
  requestShapes(term.request_shapes);
572
643
  switch (term.op) {
644
+ case "materialize":
645
+ operation(term.operation);
646
+ edge(term.failure);
647
+ break;
573
648
  case "return":
574
649
  if (term.value)
575
650
  boundary(term.value);