@hew-lang/sandbox-vm 0.6.1 → 0.6.3
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/README.md +48 -4
- package/cli/test-runner.mjs +4 -1
- package/dist/interpreter/interpreter.d.ts +1 -1
- package/dist/interpreter/interpreter.js +1 -1
- package/dist/interpreter/parity-runner.js +1 -1
- package/dist/interpreter/run-program.d.ts +3 -2
- package/dist/interpreter/run-program.js +5 -3
- package/dist/interpreter/schema-validator.d.ts +9 -4
- package/dist/interpreter/schema-validator.js +15 -49
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +6 -0
- package/dist/interpreter/unicode-data.d.ts +4 -0
- package/dist/interpreter/unicode-data.js +1673 -0
- package/dist/interpreter/unicode.d.ts +3 -0
- package/dist/interpreter/unicode.js +35 -0
- package/dist/interpreter/v1/actor-identity.d.ts +3 -0
- package/dist/interpreter/v1/actor-identity.js +7 -0
- package/dist/interpreter/v1/actor-waits.d.ts +6 -0
- package/dist/interpreter/v1/actor-waits.js +33 -0
- package/dist/interpreter/v1/browser-directory.d.ts +107 -0
- package/dist/interpreter/v1/browser-directory.js +288 -0
- package/dist/interpreter/v1/calendar.d.ts +4 -0
- package/dist/interpreter/v1/calendar.js +64 -0
- package/dist/interpreter/v1/codecs.js +8 -11
- package/dist/interpreter/v1/exec.d.ts +2 -2
- package/dist/interpreter/v1/exec.js +1761 -489
- package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
- package/dist/interpreter/v1/instructions.generated.js +106 -0
- package/dist/interpreter/v1/package-graph.d.ts +2 -0
- package/dist/interpreter/v1/package-graph.js +729 -0
- package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
- package/dist/interpreter/v1/package-validator.generated.js +75902 -0
- package/dist/interpreter/v1/package.d.ts +5 -645
- package/dist/interpreter/v1/package.js +0 -12
- package/dist/interpreter/v1/pipes.d.ts +1 -0
- package/dist/interpreter/v1/pipes.js +9 -6
- package/dist/interpreter/v1/scheduler.d.ts +8 -1
- package/dist/interpreter/v1/scheduler.js +50 -11
- package/dist/interpreter/v1/shims.d.ts +260 -7
- package/dist/interpreter/v1/shims.js +647 -175
- package/dist/interpreter/v1/structural.d.ts +2 -28
- package/dist/interpreter/v1/timestamp.d.ts +7 -0
- package/dist/interpreter/v1/timestamp.js +54 -0
- package/dist/interpreter/v1/validate.d.ts +8 -2
- package/dist/interpreter/v1/validate.js +284 -200
- package/dist/interpreter/v1/value-operations.d.ts +11 -3
- package/dist/interpreter/v1/value-operations.js +70 -18
- package/dist/interpreter/values.d.ts +30 -8
- package/dist/interpreter/values.js +31 -5
- package/dist/interpreter/worker.js +4 -3
- package/package.json +4 -4
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { validCodecShape } from "./package-validator.generated.js";
|
|
2
|
+
function require(condition, message) {
|
|
3
|
+
if (!condition)
|
|
4
|
+
throw new Error(message);
|
|
5
|
+
}
|
|
6
|
+
function indexed(table, name) {
|
|
7
|
+
// The executor indexes these tables directly; accepting conflicting IDs
|
|
8
|
+
// would let the same reference denote different entries at different sites.
|
|
9
|
+
for (const [index, entry] of table.entries())
|
|
10
|
+
require(entry.id ===
|
|
11
|
+
index, `${name} ID ${entry.id} does not match index ${index}`);
|
|
12
|
+
}
|
|
13
|
+
function at(table, id, name) {
|
|
14
|
+
const value = table[id];
|
|
15
|
+
require(value !== undefined, `${name} reference ${id} is absent`);
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function exhaustive(value) {
|
|
19
|
+
throw new Error(`unhandled bytecode reference carrier ${String(value)}`);
|
|
20
|
+
}
|
|
21
|
+
export function validatePackageGraph(pkg) {
|
|
22
|
+
const actors = pkg.actors ?? [];
|
|
23
|
+
const supervisors = pkg.supervisors ?? [];
|
|
24
|
+
const resources = pkg.resources ?? [];
|
|
25
|
+
const recipes = pkg.structural_render ?? [];
|
|
26
|
+
indexed(pkg.functions, "function");
|
|
27
|
+
indexed(pkg.aggregates, "aggregate");
|
|
28
|
+
indexed(pkg.variants, "variant");
|
|
29
|
+
indexed(pkg.closures, "closure");
|
|
30
|
+
indexed(pkg.vtables, "vtable");
|
|
31
|
+
indexed(pkg.value_capabilities, "value capability");
|
|
32
|
+
indexed(pkg.runtime_families, "runtime family");
|
|
33
|
+
indexed(pkg.externs, "extern");
|
|
34
|
+
indexed(actors, "actor");
|
|
35
|
+
indexed(supervisors, "supervisor");
|
|
36
|
+
const callable = (id) => at(pkg.functions, id, "function");
|
|
37
|
+
const aggregate = (id) => id == null ? null : at(pkg.aggregates, id, "aggregate");
|
|
38
|
+
const variant = (id) => id == null ? null : at(pkg.variants, id, "variant");
|
|
39
|
+
const capability = (id) => at(pkg.value_capabilities, id, "value capability");
|
|
40
|
+
const recipe = (id) => at(recipes, id, "structural recipe");
|
|
41
|
+
const resource = (id) => at(resources, id, "resource");
|
|
42
|
+
const messages = actors.map((actor) => {
|
|
43
|
+
const handlers = new Map(actor.handlers.map((handler) => [handler.message_id, handler]));
|
|
44
|
+
require(handlers.size ===
|
|
45
|
+
actor.handlers.length, `actor ${actor.id} repeats a message`);
|
|
46
|
+
return handlers;
|
|
47
|
+
});
|
|
48
|
+
const message = (actor, id) => {
|
|
49
|
+
const handler = at(messages, actor, "actor").get(id);
|
|
50
|
+
require(handler, `actor ${actor} message ${id} is absent`);
|
|
51
|
+
return handler;
|
|
52
|
+
};
|
|
53
|
+
const requestShapes = (shapes) => {
|
|
54
|
+
if (!shapes)
|
|
55
|
+
return;
|
|
56
|
+
aggregate(shapes.failure);
|
|
57
|
+
variant(shapes.reason);
|
|
58
|
+
aggregate(shapes.message);
|
|
59
|
+
aggregate(shapes.request);
|
|
60
|
+
};
|
|
61
|
+
if (pkg.entry) {
|
|
62
|
+
const entry = callable(pkg.entry.function);
|
|
63
|
+
require(entry.params.length === 0, "entry function requires arguments");
|
|
64
|
+
}
|
|
65
|
+
for (const shape of pkg.variants)
|
|
66
|
+
for (const tag of Object.values(shape.runtime_tags ?? {}))
|
|
67
|
+
require(tag !== undefined &&
|
|
68
|
+
tag < shape.cases.length, `variant ${shape.id} runtime tag is absent`);
|
|
69
|
+
for (const closure of pkg.closures) {
|
|
70
|
+
const specializations = new Set();
|
|
71
|
+
for (const entry of closure.entries) {
|
|
72
|
+
callable(entry.body);
|
|
73
|
+
require(!specializations.has(entry.specialization), `closure ${closure.id} repeats a specialization`);
|
|
74
|
+
specializations.add(entry.specialization);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const table of pkg.vtables) {
|
|
78
|
+
const slots = new Set();
|
|
79
|
+
for (const slot of table.slots) {
|
|
80
|
+
require(!slots.has(slot.slot), `vtable ${table.id} repeats slot ${slot.slot}`);
|
|
81
|
+
slots.add(slot.slot);
|
|
82
|
+
callable(slot.callee);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const plan of pkg.value_capabilities) {
|
|
86
|
+
if (plan.callable != null)
|
|
87
|
+
callable(plan.callable);
|
|
88
|
+
for (const member of [
|
|
89
|
+
...(plan.components ?? []),
|
|
90
|
+
...(plan.variants ?? []).flat(),
|
|
91
|
+
]) {
|
|
92
|
+
capability(member.selected);
|
|
93
|
+
if (member.ordering)
|
|
94
|
+
for (const selected of Object.values(member.ordering))
|
|
95
|
+
capability(selected);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const value of recipes) {
|
|
99
|
+
switch (value.kind) {
|
|
100
|
+
case "scalar":
|
|
101
|
+
case "unit":
|
|
102
|
+
case "identity":
|
|
103
|
+
break;
|
|
104
|
+
case "display":
|
|
105
|
+
callable(value.callee);
|
|
106
|
+
break;
|
|
107
|
+
case "vector":
|
|
108
|
+
require(value.members.length ===
|
|
109
|
+
1, "vector rendering requires one member");
|
|
110
|
+
value.members.forEach(recipe);
|
|
111
|
+
break;
|
|
112
|
+
case "map":
|
|
113
|
+
require(value.members.length ===
|
|
114
|
+
2, "map rendering requires two members");
|
|
115
|
+
value.members.forEach(recipe);
|
|
116
|
+
break;
|
|
117
|
+
case "tuple":
|
|
118
|
+
value.members.forEach(recipe);
|
|
119
|
+
break;
|
|
120
|
+
case "record":
|
|
121
|
+
value.fields.forEach((field) => recipe(field.recipe));
|
|
122
|
+
break;
|
|
123
|
+
case "enum":
|
|
124
|
+
value.cases.forEach((item) => item.fields.forEach((field) => recipe(field.recipe)));
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
exhaustive(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const value of resources) {
|
|
131
|
+
switch (value.kind) {
|
|
132
|
+
case "record":
|
|
133
|
+
aggregate(value.shape);
|
|
134
|
+
callable(value.close);
|
|
135
|
+
break;
|
|
136
|
+
case "opaque":
|
|
137
|
+
callable(value.close);
|
|
138
|
+
break;
|
|
139
|
+
case "nominal":
|
|
140
|
+
case "task":
|
|
141
|
+
case "actor_call":
|
|
142
|
+
case "actor_request":
|
|
143
|
+
case "generator":
|
|
144
|
+
case "stream":
|
|
145
|
+
case "sink":
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
exhaustive(value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const actor of actors) {
|
|
152
|
+
for (const id of [
|
|
153
|
+
actor.init,
|
|
154
|
+
actor.start,
|
|
155
|
+
actor.crash,
|
|
156
|
+
actor.exit,
|
|
157
|
+
actor.down,
|
|
158
|
+
...actor.stop,
|
|
159
|
+
])
|
|
160
|
+
if (id != null)
|
|
161
|
+
callable(id);
|
|
162
|
+
aggregate(actor.crash_info);
|
|
163
|
+
variant(actor.crash_kind);
|
|
164
|
+
variant(actor.crash_action);
|
|
165
|
+
for (const handler of actor.handlers) {
|
|
166
|
+
callable(handler.callable);
|
|
167
|
+
variant(handler.result_shape);
|
|
168
|
+
}
|
|
169
|
+
for (const key of actor.coalesce?.keys ?? []) {
|
|
170
|
+
const handler = message(actor.id, key.message);
|
|
171
|
+
require(key.param <
|
|
172
|
+
handler.params, "coalescing key is outside the message parameters");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const supervisor of supervisors)
|
|
176
|
+
for (const child of supervisor.children) {
|
|
177
|
+
callable(child.spawn);
|
|
178
|
+
if ("actor" in child.role)
|
|
179
|
+
at(actors, child.role.actor, "actor");
|
|
180
|
+
else
|
|
181
|
+
at(supervisors, child.role.supervisor, "supervisor");
|
|
182
|
+
}
|
|
183
|
+
// The codec's inner JSON remains the v1 string wire format, but its shape
|
|
184
|
+
// vocabulary is generated from the same Rust carrier used by native codecs.
|
|
185
|
+
const codecs = (pkg.codecs ?? []).map((encoded, index) => {
|
|
186
|
+
const decoded = JSON.parse(encoded);
|
|
187
|
+
if (!validCodecShape(decoded)) {
|
|
188
|
+
const reasons = (validCodecShape.errors ?? []).map((issue) => `${issue.instancePath || "/"} ${issue.message ?? "invalid value"}`);
|
|
189
|
+
throw new Error(`codec ${index}: ${reasons.join("; ")}`);
|
|
190
|
+
}
|
|
191
|
+
const shape = decoded;
|
|
192
|
+
const node = (id) => at(shape.nodes, id, "codec node");
|
|
193
|
+
node(shape.root);
|
|
194
|
+
require([
|
|
195
|
+
shape.names,
|
|
196
|
+
shape.aggregates,
|
|
197
|
+
shape.variants,
|
|
198
|
+
shape.grouped_payloads,
|
|
199
|
+
].every((table) => table.length === shape.nodes.length), "codec descriptor tables have different lengths");
|
|
200
|
+
shape.aggregates.forEach(aggregate);
|
|
201
|
+
shape.variants.forEach(variant);
|
|
202
|
+
for (const value of shape.nodes)
|
|
203
|
+
validateNode(value);
|
|
204
|
+
return shape;
|
|
205
|
+
function validateNode(value) {
|
|
206
|
+
if (typeof value === "string")
|
|
207
|
+
return;
|
|
208
|
+
if ("Tuple" in value)
|
|
209
|
+
value.Tuple.forEach(node);
|
|
210
|
+
else if ("Array" in value)
|
|
211
|
+
node(value.Array.element);
|
|
212
|
+
else if ("Sequence" in value)
|
|
213
|
+
node(value.Sequence);
|
|
214
|
+
else if ("Set" in value)
|
|
215
|
+
node(value.Set);
|
|
216
|
+
else if ("Map" in value) {
|
|
217
|
+
node(value.Map.key);
|
|
218
|
+
node(value.Map.value);
|
|
219
|
+
}
|
|
220
|
+
else if ("Record" in value)
|
|
221
|
+
value.Record.forEach(([, id]) => node(id));
|
|
222
|
+
else if ("Variant" in value)
|
|
223
|
+
value.Variant.forEach(([, id]) => {
|
|
224
|
+
if (id != null)
|
|
225
|
+
node(id);
|
|
226
|
+
});
|
|
227
|
+
else if ("Option" in value)
|
|
228
|
+
node(value.Option);
|
|
229
|
+
else if ("Float" in value)
|
|
230
|
+
require(value.Float.bits === 32 ||
|
|
231
|
+
value.Float.bits === 64, "codec float width is invalid");
|
|
232
|
+
else if ("Signed" in value)
|
|
233
|
+
require(value.Signed.min <=
|
|
234
|
+
value.Signed.max, "codec signed range is invalid");
|
|
235
|
+
else if ("Unsigned" in value)
|
|
236
|
+
require(value.Unsigned.max >= 0, "codec unsigned range is invalid");
|
|
237
|
+
else
|
|
238
|
+
exhaustive(value);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
function actorOperation(operation) {
|
|
242
|
+
switch (operation.op) {
|
|
243
|
+
case "identity":
|
|
244
|
+
case "local_observation":
|
|
245
|
+
break;
|
|
246
|
+
case "spawn":
|
|
247
|
+
case "self_handle":
|
|
248
|
+
case "submit":
|
|
249
|
+
at(actors, operation.actor, "actor");
|
|
250
|
+
break;
|
|
251
|
+
case "stop":
|
|
252
|
+
case "terminate":
|
|
253
|
+
case "await_stopped":
|
|
254
|
+
case "await_restarted":
|
|
255
|
+
if ("actor" in operation)
|
|
256
|
+
at(actors, operation.actor, "actor");
|
|
257
|
+
else
|
|
258
|
+
at(supervisors, operation.supervisor, "supervisor");
|
|
259
|
+
break;
|
|
260
|
+
case "call_start":
|
|
261
|
+
case "call_take":
|
|
262
|
+
message(operation.protocol.actor, operation.protocol.message);
|
|
263
|
+
break;
|
|
264
|
+
case "stream_start":
|
|
265
|
+
message(operation.actor, operation.message);
|
|
266
|
+
break;
|
|
267
|
+
case "timer_schedule":
|
|
268
|
+
message(operation.actor, operation.message);
|
|
269
|
+
resource(operation.resource);
|
|
270
|
+
break;
|
|
271
|
+
case "supervisor_spawn":
|
|
272
|
+
at(supervisors, operation.supervisor, "supervisor");
|
|
273
|
+
break;
|
|
274
|
+
case "supervisor_child":
|
|
275
|
+
case "supervisor_pool_view":
|
|
276
|
+
at(at(supervisors, operation.supervisor, "supervisor").children, operation.child, "supervisor child");
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
exhaustive(operation);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const suspensions = new Set(pkg.suspend_kinds);
|
|
283
|
+
require(suspensions.size ===
|
|
284
|
+
pkg.suspend_kinds.length, "suspension manifest repeats a kind");
|
|
285
|
+
for (const fn of pkg.functions)
|
|
286
|
+
validateFunction(fn);
|
|
287
|
+
function validateFunction(fn) {
|
|
288
|
+
indexed(fn.blocks, `function ${fn.id} block`);
|
|
289
|
+
indexed(fn.places, `function ${fn.id} place`);
|
|
290
|
+
const entry = at(fn.blocks, fn.entry, "entry block");
|
|
291
|
+
require(entry.params.length ===
|
|
292
|
+
0, "entry block has unbound block arguments");
|
|
293
|
+
const values = new Set();
|
|
294
|
+
const scopes = new Set();
|
|
295
|
+
const defers = new Set();
|
|
296
|
+
const define = (id) => {
|
|
297
|
+
require(!values.has(id), `function ${fn.id} repeats value ${id}`);
|
|
298
|
+
values.add(id);
|
|
299
|
+
};
|
|
300
|
+
const value = (id) => require(values.has(id), `function ${fn.id} value ${id} is absent`);
|
|
301
|
+
const boundary = (operand) => value(operand.value);
|
|
302
|
+
const place = (id) => at(fn.places, id, "place");
|
|
303
|
+
const scope = (id) => {
|
|
304
|
+
if (id != null)
|
|
305
|
+
require(scopes.has(id), `task scope ${id} is absent`);
|
|
306
|
+
};
|
|
307
|
+
const defer = (id) => require(defers.has(id), `defer ${id} is absent`);
|
|
308
|
+
const edge = (edge) => {
|
|
309
|
+
if (!edge)
|
|
310
|
+
return;
|
|
311
|
+
const target = at(fn.blocks, edge.to, "block");
|
|
312
|
+
require(target.params.length ===
|
|
313
|
+
edge.args
|
|
314
|
+
.length, `edge to block ${edge.to} has ${edge.args.length} arguments, expected ${target.params.length}`);
|
|
315
|
+
edge.args.forEach(value);
|
|
316
|
+
};
|
|
317
|
+
const result = (def) => define(def.value);
|
|
318
|
+
fn.params.forEach(result);
|
|
319
|
+
// Results exist on authored successor edges. This pass checks identity,
|
|
320
|
+
// not dominance or whether a particular path owns the referenced value.
|
|
321
|
+
for (const block of fn.blocks) {
|
|
322
|
+
block.params.forEach(result);
|
|
323
|
+
for (const op of block.ops) {
|
|
324
|
+
if ("dst" in op && op.dst != null)
|
|
325
|
+
define(op.dst);
|
|
326
|
+
if (op.op === "destructure" || op.op === "variant.destructure")
|
|
327
|
+
op.results.forEach(define);
|
|
328
|
+
if (op.op === "stream.pipe")
|
|
329
|
+
op.results.forEach(result);
|
|
330
|
+
if (op.op === "task_scope.enter") {
|
|
331
|
+
scopes.add(op.scope);
|
|
332
|
+
}
|
|
333
|
+
if (op.op === "register_defer") {
|
|
334
|
+
defers.add(op.defer);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const term = block.term;
|
|
338
|
+
if ("result" in term && term.result && term.result !== "never")
|
|
339
|
+
result(term.result);
|
|
340
|
+
if (term.op === "call" && term.handback)
|
|
341
|
+
result(term.handback);
|
|
342
|
+
if (term.op === "switch.variant")
|
|
343
|
+
term.arms.forEach((arm) => arm.fields.forEach(result));
|
|
344
|
+
}
|
|
345
|
+
for (const declaration of fn.places) {
|
|
346
|
+
switch (declaration.origin) {
|
|
347
|
+
case "local":
|
|
348
|
+
case "runtime":
|
|
349
|
+
break;
|
|
350
|
+
case "aggregate": {
|
|
351
|
+
if ("place" in declaration.base)
|
|
352
|
+
place(declaration.base.place);
|
|
353
|
+
else
|
|
354
|
+
value(declaration.base.value);
|
|
355
|
+
const shape = aggregate(declaration.shape);
|
|
356
|
+
if (shape)
|
|
357
|
+
require(declaration.field <
|
|
358
|
+
shape.fields.length, "place field is outside its aggregate");
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case "capture":
|
|
362
|
+
case "actor_state":
|
|
363
|
+
value(declaration.environment);
|
|
364
|
+
break;
|
|
365
|
+
default:
|
|
366
|
+
exhaustive(declaration);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Every projected place has at most one parent. Colour each chain once;
|
|
370
|
+
// cycles otherwise recurse forever in the executor's place resolver.
|
|
371
|
+
const complete = new Set();
|
|
372
|
+
for (const declaration of fn.places) {
|
|
373
|
+
const chain = new Set();
|
|
374
|
+
let current = declaration;
|
|
375
|
+
while (!complete.has(current.id)) {
|
|
376
|
+
require(!chain.has(current.id), "place projection contains a cycle");
|
|
377
|
+
chain.add(current.id);
|
|
378
|
+
if (current.origin !== "aggregate" || !("place" in current.base))
|
|
379
|
+
break;
|
|
380
|
+
current = place(current.base.place);
|
|
381
|
+
}
|
|
382
|
+
chain.forEach((id) => complete.add(id));
|
|
383
|
+
}
|
|
384
|
+
for (const block of fn.blocks) {
|
|
385
|
+
block.ops.forEach(operation);
|
|
386
|
+
terminator(block.term);
|
|
387
|
+
}
|
|
388
|
+
function operation(op) {
|
|
389
|
+
switch (op.op) {
|
|
390
|
+
case "coverage.hit":
|
|
391
|
+
case "const.int":
|
|
392
|
+
case "const.bool":
|
|
393
|
+
case "const.float":
|
|
394
|
+
case "const.char":
|
|
395
|
+
case "const.unit":
|
|
396
|
+
case "const.duration":
|
|
397
|
+
case "finish_linear_receiver":
|
|
398
|
+
break;
|
|
399
|
+
case "const.str":
|
|
400
|
+
at(pkg.strings, op.str, "string");
|
|
401
|
+
break;
|
|
402
|
+
case "const.bytes":
|
|
403
|
+
at(pkg.bytes, op.bytes, "bytes");
|
|
404
|
+
break;
|
|
405
|
+
case "copy_value":
|
|
406
|
+
case "move":
|
|
407
|
+
case "fork":
|
|
408
|
+
case "callable.coerce":
|
|
409
|
+
value(op.source);
|
|
410
|
+
break;
|
|
411
|
+
case "destroy_value":
|
|
412
|
+
case "unary":
|
|
413
|
+
case "cast":
|
|
414
|
+
value(op.value);
|
|
415
|
+
break;
|
|
416
|
+
case "begin_borrow":
|
|
417
|
+
value(op.owner);
|
|
418
|
+
break;
|
|
419
|
+
case "end_borrow":
|
|
420
|
+
value(op.borrow);
|
|
421
|
+
break;
|
|
422
|
+
case "alloc_place":
|
|
423
|
+
case "load.copy":
|
|
424
|
+
case "load.take":
|
|
425
|
+
case "load.borrow":
|
|
426
|
+
case "end_lifetime":
|
|
427
|
+
place(op.place);
|
|
428
|
+
break;
|
|
429
|
+
case "store.init":
|
|
430
|
+
case "store.assign":
|
|
431
|
+
place(op.place);
|
|
432
|
+
value(op.value);
|
|
433
|
+
break;
|
|
434
|
+
case "tuple.make":
|
|
435
|
+
case "array.make":
|
|
436
|
+
op.elements.forEach(value);
|
|
437
|
+
break;
|
|
438
|
+
case "tuple.get":
|
|
439
|
+
value(op.tuple);
|
|
440
|
+
break;
|
|
441
|
+
case "aggregate.make": {
|
|
442
|
+
const shape = aggregate(op.shape);
|
|
443
|
+
if (shape)
|
|
444
|
+
require(shape.fields.length ===
|
|
445
|
+
op.fields
|
|
446
|
+
.length, "aggregate constructor has the wrong field count");
|
|
447
|
+
op.fields.forEach(value);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case "aggregate.project_copy":
|
|
451
|
+
case "aggregate.project_borrow": {
|
|
452
|
+
const shape = aggregate(op.shape);
|
|
453
|
+
if (shape)
|
|
454
|
+
require(op.field <
|
|
455
|
+
shape.fields.length, "aggregate projection field is absent");
|
|
456
|
+
value(op.aggregate);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case "destructure": {
|
|
460
|
+
const shape = aggregate(op.shape);
|
|
461
|
+
if (shape)
|
|
462
|
+
require(shape.fields.length ===
|
|
463
|
+
op.results
|
|
464
|
+
.length, "aggregate destructuring has the wrong field count");
|
|
465
|
+
value(op.aggregate);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
case "array.repeat":
|
|
469
|
+
value(op.value);
|
|
470
|
+
break;
|
|
471
|
+
case "variant.make":
|
|
472
|
+
case "variant.is":
|
|
473
|
+
case "variant.project_copy":
|
|
474
|
+
case "variant.project_borrow":
|
|
475
|
+
case "variant.destructure": {
|
|
476
|
+
const selected = at(variant(op.shape).cases, op.variant, "variant case");
|
|
477
|
+
if (op.op === "variant.make") {
|
|
478
|
+
require(selected.fields.length ===
|
|
479
|
+
op.fields
|
|
480
|
+
.length, "variant constructor has the wrong field count");
|
|
481
|
+
op.fields.forEach(value);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
value(op.source);
|
|
485
|
+
if ("field" in op)
|
|
486
|
+
require(op.field <
|
|
487
|
+
selected.fields.length, "variant projection field is absent");
|
|
488
|
+
if ("results" in op)
|
|
489
|
+
require(op.results.length ===
|
|
490
|
+
selected.fields
|
|
491
|
+
.length, "variant destructuring has the wrong field count");
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case "binary":
|
|
496
|
+
case "str.eq":
|
|
497
|
+
case "bytes.eq":
|
|
498
|
+
value(op.lhs);
|
|
499
|
+
value(op.rhs);
|
|
500
|
+
break;
|
|
501
|
+
case "function.make":
|
|
502
|
+
callable(op.callable);
|
|
503
|
+
break;
|
|
504
|
+
case "closure.make": {
|
|
505
|
+
const closure = at(pkg.closures, op.closure, "closure");
|
|
506
|
+
require(closure.fields ===
|
|
507
|
+
op.fields
|
|
508
|
+
.length, "closure constructor has the wrong capture count");
|
|
509
|
+
op.fields.forEach(value);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
case "dyn.make":
|
|
513
|
+
at(pkg.vtables, op.vtable, "vtable");
|
|
514
|
+
value(op.value);
|
|
515
|
+
break;
|
|
516
|
+
case "register_defer":
|
|
517
|
+
op.dependencies.forEach(place);
|
|
518
|
+
break;
|
|
519
|
+
case "generator.make":
|
|
520
|
+
at(pkg.closures, op.closure, "closure");
|
|
521
|
+
value(op.callable);
|
|
522
|
+
break;
|
|
523
|
+
case "actor.ingress_adapter":
|
|
524
|
+
message(op.actor, op.message);
|
|
525
|
+
break;
|
|
526
|
+
case "stream.pipe":
|
|
527
|
+
require(op.results.length ===
|
|
528
|
+
2, "stream pipe requires sink and stream results");
|
|
529
|
+
break;
|
|
530
|
+
case "task_scope.enter":
|
|
531
|
+
scope(op.parent);
|
|
532
|
+
if (op.duration != null)
|
|
533
|
+
value(op.duration);
|
|
534
|
+
break;
|
|
535
|
+
case "task_scope.close":
|
|
536
|
+
scope(op.scope);
|
|
537
|
+
break;
|
|
538
|
+
case "task.race":
|
|
539
|
+
scope(op.scope);
|
|
540
|
+
op.members.forEach(value);
|
|
541
|
+
break;
|
|
542
|
+
case "task.spawn":
|
|
543
|
+
scope(op.scope);
|
|
544
|
+
value(op.callable);
|
|
545
|
+
break;
|
|
546
|
+
case "task.invoke": {
|
|
547
|
+
scope(op.scope);
|
|
548
|
+
op.args.forEach(boundary);
|
|
549
|
+
if ("direct" in op.callee)
|
|
550
|
+
callable(op.callee.direct);
|
|
551
|
+
else {
|
|
552
|
+
boundary(op.callee.receiver);
|
|
553
|
+
if ("mutable" in op.callee)
|
|
554
|
+
callable(op.callee.mutable);
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
default:
|
|
559
|
+
exhaustive(op);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function terminator(term) {
|
|
563
|
+
// These are common wire fields, not inferred source semantics.
|
|
564
|
+
if ("args" in term)
|
|
565
|
+
term.args.forEach(boundary);
|
|
566
|
+
if ("normal" in term)
|
|
567
|
+
edge(term.normal);
|
|
568
|
+
if ("unwind" in term)
|
|
569
|
+
edge(term.unwind);
|
|
570
|
+
if ("request_shapes" in term)
|
|
571
|
+
requestShapes(term.request_shapes);
|
|
572
|
+
switch (term.op) {
|
|
573
|
+
case "return":
|
|
574
|
+
if (term.value)
|
|
575
|
+
boundary(term.value);
|
|
576
|
+
break;
|
|
577
|
+
case "goto":
|
|
578
|
+
edge(term.edge);
|
|
579
|
+
break;
|
|
580
|
+
case "branch":
|
|
581
|
+
value(term.condition);
|
|
582
|
+
edge(term.then);
|
|
583
|
+
edge(term.else);
|
|
584
|
+
break;
|
|
585
|
+
case "switch.variant": {
|
|
586
|
+
const shape = variant(term.shape);
|
|
587
|
+
value(term.scrutinee);
|
|
588
|
+
const tags = new Set();
|
|
589
|
+
for (const arm of term.arms) {
|
|
590
|
+
const selected = at(shape.cases, arm.variant, "variant case");
|
|
591
|
+
require(!tags.has(arm.variant), "variant switch repeats a case");
|
|
592
|
+
tags.add(arm.variant);
|
|
593
|
+
require(selected.fields.length ===
|
|
594
|
+
arm.fields.length, "variant switch has the wrong payload count");
|
|
595
|
+
edge(arm.edge);
|
|
596
|
+
}
|
|
597
|
+
require(tags.size ===
|
|
598
|
+
shape.cases.length, "variant switch omits a case");
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case "checked.binary":
|
|
602
|
+
case "timestamp.binary":
|
|
603
|
+
value(term.lhs);
|
|
604
|
+
value(term.rhs);
|
|
605
|
+
term.failures.forEach((failure) => edge(failure.edge));
|
|
606
|
+
if (term.op === "timestamp.binary")
|
|
607
|
+
aggregate(term.shape);
|
|
608
|
+
break;
|
|
609
|
+
case "call":
|
|
610
|
+
require(callable(term.callee).params.length ===
|
|
611
|
+
term.args.length, "direct call has the wrong argument count");
|
|
612
|
+
break;
|
|
613
|
+
case "indirect.call":
|
|
614
|
+
boundary(term.callee);
|
|
615
|
+
break;
|
|
616
|
+
case "dyn.call":
|
|
617
|
+
boundary(term.receiver);
|
|
618
|
+
break;
|
|
619
|
+
case "value.call":
|
|
620
|
+
capability(term.plan);
|
|
621
|
+
break;
|
|
622
|
+
case "wire.codec": {
|
|
623
|
+
const codec = at(codecs, term.codec, "codec");
|
|
624
|
+
for (const [node, callbacks] of Object.entries(term.callbacks)) {
|
|
625
|
+
require(/^(0|[1-9][0-9]*)$/.test(node), "codec callback node is invalid");
|
|
626
|
+
at(codec.nodes, Number(node), "codec callback node");
|
|
627
|
+
callbacks.forEach(capability);
|
|
628
|
+
}
|
|
629
|
+
if (term.decoded) {
|
|
630
|
+
const shape = variant(term.decoded.shape);
|
|
631
|
+
at(shape.cases, term.decoded.ok, "decoded success case");
|
|
632
|
+
at(shape.cases, term.decoded.error, "decoded failure case");
|
|
633
|
+
at(codec.nodes, term.decoded.error_root, "codec error node");
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
case "runtime.call":
|
|
638
|
+
at(pkg.runtime_families, term.family, "runtime family");
|
|
639
|
+
variant(term.result_shape);
|
|
640
|
+
aggregate(term.aggregate_result_shape);
|
|
641
|
+
if (term.structural != null)
|
|
642
|
+
recipe(term.structural);
|
|
643
|
+
term.callbacks?.forEach(capability);
|
|
644
|
+
term.result_member_shapes?.forEach(variant);
|
|
645
|
+
if (term.variant_result_shapes) {
|
|
646
|
+
aggregate(term.variant_result_shapes.error);
|
|
647
|
+
variant(term.variant_result_shapes.error_len);
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
case "extern.call":
|
|
651
|
+
if (term.result_resource != null)
|
|
652
|
+
at(pkg.resources ?? [], term.result_resource, "resource");
|
|
653
|
+
at(pkg.externs, term.extern, "extern");
|
|
654
|
+
variant(term.result_shape);
|
|
655
|
+
aggregate(term.aggregate_result_shape);
|
|
656
|
+
break;
|
|
657
|
+
case "actor.call":
|
|
658
|
+
actorOperation(term.operation);
|
|
659
|
+
variant(term.result_shape);
|
|
660
|
+
variant(term.error_shape);
|
|
661
|
+
if (term.submission_shapes) {
|
|
662
|
+
variant(term.submission_shapes.success);
|
|
663
|
+
aggregate(term.submission_shapes.failure);
|
|
664
|
+
variant(term.submission_shapes.reason);
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
case "panic":
|
|
668
|
+
boundary(term.message);
|
|
669
|
+
term.assertion?.forEach(boundary);
|
|
670
|
+
edge(term.cleanup);
|
|
671
|
+
break;
|
|
672
|
+
case "checked_raise":
|
|
673
|
+
edge(term.cleanup);
|
|
674
|
+
break;
|
|
675
|
+
case "cleanup.dispatch":
|
|
676
|
+
edge(term.fault);
|
|
677
|
+
break;
|
|
678
|
+
case "resume_unwind":
|
|
679
|
+
if (term.handback)
|
|
680
|
+
boundary(term.handback);
|
|
681
|
+
break;
|
|
682
|
+
case "enter_defer":
|
|
683
|
+
defer(term.defer);
|
|
684
|
+
edge(term.body);
|
|
685
|
+
break;
|
|
686
|
+
case "finish_defer":
|
|
687
|
+
defer(term.defer);
|
|
688
|
+
edge(term.next);
|
|
689
|
+
break;
|
|
690
|
+
case "recover_fault": {
|
|
691
|
+
const shape = variant(term.result_shape);
|
|
692
|
+
require(shape, "fault recovery lacks its result shape");
|
|
693
|
+
at(shape.cases, term.deadline_variant, "deadline variant");
|
|
694
|
+
at(shape.cases, term.fault_variant, "fault variant");
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "suspend":
|
|
698
|
+
require(suspensions.has(term.kind), `suspension ${term.kind} is missing from its manifest`);
|
|
699
|
+
variant(term.result_shape);
|
|
700
|
+
variant(term.error_shape);
|
|
701
|
+
term.inputs.forEach(boundary);
|
|
702
|
+
term.resumes.forEach(edge);
|
|
703
|
+
edge(term.cancel);
|
|
704
|
+
switch (term.kind) {
|
|
705
|
+
case "Offload":
|
|
706
|
+
callable(term.detail.function);
|
|
707
|
+
break;
|
|
708
|
+
case "ServiceAcquire":
|
|
709
|
+
at(actors, term.detail.actor, "actor");
|
|
710
|
+
break;
|
|
711
|
+
case "Ask":
|
|
712
|
+
message(term.detail.actor, term.detail.message);
|
|
713
|
+
break;
|
|
714
|
+
case "Join":
|
|
715
|
+
scope(term.detail.scope);
|
|
716
|
+
break;
|
|
717
|
+
default:
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
case "trap":
|
|
722
|
+
case "unreachable":
|
|
723
|
+
break;
|
|
724
|
+
default:
|
|
725
|
+
exhaustive(term);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|