@mica-js/compiler 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +275 -2
- package/dist/index.d.ts +402 -0
- package/dist/index.js +1886 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1886 @@
|
|
|
1
|
+
//#region src/values.ts
|
|
2
|
+
function createEnum(_name, member) {
|
|
3
|
+
return member;
|
|
4
|
+
}
|
|
5
|
+
function createRecord(fields) {
|
|
6
|
+
return Object.freeze(Object.fromEntries(fields));
|
|
7
|
+
}
|
|
8
|
+
function createBytes(elements) {
|
|
9
|
+
const bytes = new Uint8Array(elements.length);
|
|
10
|
+
for (let index = 0; index < elements.length; index++) {
|
|
11
|
+
const value = elements[index];
|
|
12
|
+
if (typeof value !== "number" || !Number.isInteger(value)) throw new TypeError("Byte value must be an integer number");
|
|
13
|
+
if (value < 0 || value > 255) throw new RangeError("Byte value must be in [0, 255]");
|
|
14
|
+
bytes[index] = value;
|
|
15
|
+
}
|
|
16
|
+
return bytes;
|
|
17
|
+
}
|
|
18
|
+
function combineIntents(values) {
|
|
19
|
+
const branches = [];
|
|
20
|
+
for (const value of values) if (Array.isArray(value)) for (const branch of value) branches.push(branch);
|
|
21
|
+
else branches.push(value);
|
|
22
|
+
return Object.freeze(branches);
|
|
23
|
+
}
|
|
24
|
+
/** Structural comparison deliberately does not shortcut identical objects containing NaN. */
|
|
25
|
+
function equalValues(left, right) {
|
|
26
|
+
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return left === right;
|
|
27
|
+
if (isByteSequence(left) || isByteSequence(right)) {
|
|
28
|
+
if (!isByteSequence(left) || !isByteSequence(right) || left.length !== right.length) return false;
|
|
29
|
+
for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const a = left;
|
|
33
|
+
const b = right;
|
|
34
|
+
const keys = Object.keys(a);
|
|
35
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
36
|
+
for (const key of keys) if (!Object.hasOwn(b, key) || !equalValues(a[key], b[key])) return false;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function isByteSequence(value) {
|
|
40
|
+
return Array.isArray(value) || ArrayBuffer.isView(value) || "length" in value && Symbol.iterator in value;
|
|
41
|
+
}
|
|
42
|
+
function readIndex(value, index) {
|
|
43
|
+
if (typeof index !== "number" || !Number.isSafeInteger(index)) throw new TypeError("Index must be a safe integer number");
|
|
44
|
+
if (index < 0 || index >= value.length) throw new RangeError("Index is outside the value");
|
|
45
|
+
return value[index];
|
|
46
|
+
}
|
|
47
|
+
function nonNull(value) {
|
|
48
|
+
if (value === null) throw new TypeError("Expected a non-null value");
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function checkBoolean(value) {
|
|
52
|
+
if (typeof value !== "boolean") throw new TypeError("Condition must be a boolean");
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function checkDuration(value) {
|
|
56
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) throw new TypeError("Duration must be a finite integer number");
|
|
57
|
+
if (value < 0 || value > 72e14) throw new RangeError("Duration must be in [0, 7200000000000000] picoseconds");
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function checkPc(value) {
|
|
61
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError("Redirect target must be a finite number");
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function reactionValue(frame, binding) {
|
|
65
|
+
if (!frame) throw new TypeError("Reaction value requires a reaction frame");
|
|
66
|
+
return frame[binding];
|
|
67
|
+
}
|
|
68
|
+
function reactionCapture(frame, index) {
|
|
69
|
+
if (!frame || index >= frame.captures.length) throw new RangeError("Reaction capture is not available");
|
|
70
|
+
return frame.captures[index];
|
|
71
|
+
}
|
|
72
|
+
var terminated = Symbol("terminated call");
|
|
73
|
+
/** Propagate terminal control across yield* even if a driver mistakenly resumes it. */
|
|
74
|
+
function* delegate(generator) {
|
|
75
|
+
let done = false;
|
|
76
|
+
try {
|
|
77
|
+
let step = generator.next();
|
|
78
|
+
while (!step.done) {
|
|
79
|
+
const signal = step.value;
|
|
80
|
+
const input = yield signal;
|
|
81
|
+
if (signal.kind === "stop" || signal.kind === "redirect") return terminated;
|
|
82
|
+
step = generator.next(input);
|
|
83
|
+
}
|
|
84
|
+
done = true;
|
|
85
|
+
return step.value;
|
|
86
|
+
} finally {
|
|
87
|
+
if (!done) generator.return(void 0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
var runtimeHelpers = Object.freeze({
|
|
91
|
+
createEnum,
|
|
92
|
+
createRecord,
|
|
93
|
+
createBytes,
|
|
94
|
+
combineIntents,
|
|
95
|
+
equalValues,
|
|
96
|
+
readIndex,
|
|
97
|
+
nonNull,
|
|
98
|
+
checkBoolean,
|
|
99
|
+
checkDuration,
|
|
100
|
+
checkPc,
|
|
101
|
+
reactionValue,
|
|
102
|
+
reactionCapture,
|
|
103
|
+
delegate,
|
|
104
|
+
terminated
|
|
105
|
+
});
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/analysis.ts
|
|
108
|
+
function sourceSnapshot(source) {
|
|
109
|
+
return source.uri === void 0 ? Object.freeze({ ...source }) : Object.freeze({
|
|
110
|
+
...source,
|
|
111
|
+
range: Object.freeze({
|
|
112
|
+
start: Object.freeze({ ...source.range.start }),
|
|
113
|
+
end: Object.freeze({ ...source.range.end })
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function typeSnapshot(type) {
|
|
118
|
+
return type.kind === "optional" ? Object.freeze({
|
|
119
|
+
...type,
|
|
120
|
+
inner: Object.freeze({ ...type.inner })
|
|
121
|
+
}) : Object.freeze({ ...type });
|
|
122
|
+
}
|
|
123
|
+
function inputs(op) {
|
|
124
|
+
switch (op.kind) {
|
|
125
|
+
case "unary":
|
|
126
|
+
case "field":
|
|
127
|
+
case "length":
|
|
128
|
+
case "optional":
|
|
129
|
+
case "isNone":
|
|
130
|
+
case "nonNull":
|
|
131
|
+
case "localWrite":
|
|
132
|
+
case "write":
|
|
133
|
+
case "writeIntent":
|
|
134
|
+
case "send":
|
|
135
|
+
case "match":
|
|
136
|
+
case "returnValue":
|
|
137
|
+
case "yield": return [op.value];
|
|
138
|
+
case "binary": return [op.left, op.right];
|
|
139
|
+
case "numeric": return [...op.args];
|
|
140
|
+
case "record": return op.fields.map((field) => field.value);
|
|
141
|
+
case "bytes": return [...op.elements];
|
|
142
|
+
case "index": return [op.value, op.index];
|
|
143
|
+
case "local": return [op.initial];
|
|
144
|
+
case "load8":
|
|
145
|
+
case "load16le":
|
|
146
|
+
case "load16be":
|
|
147
|
+
case "load32le":
|
|
148
|
+
case "load32be": return [op.offset];
|
|
149
|
+
case "store8":
|
|
150
|
+
case "store16le":
|
|
151
|
+
case "store16be":
|
|
152
|
+
case "store32le":
|
|
153
|
+
case "store32be": return [op.offset, op.value];
|
|
154
|
+
case "copyBytes": return [op.offset, op.length];
|
|
155
|
+
case "resistor": return [op.resistance];
|
|
156
|
+
case "combineIntents": return [...op.values];
|
|
157
|
+
case "call":
|
|
158
|
+
case "callValue": return op.args.flatMap((arg) => arg.kind === "value" ? [arg.value] : []);
|
|
159
|
+
case "if":
|
|
160
|
+
case "select":
|
|
161
|
+
case "assert": return [op.condition];
|
|
162
|
+
case "and":
|
|
163
|
+
case "or": return [op.left];
|
|
164
|
+
case "wait":
|
|
165
|
+
case "elapse": return [op.duration];
|
|
166
|
+
case "redirect": return [op.target, op.delay];
|
|
167
|
+
default: return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function argumentUses(op) {
|
|
171
|
+
const uses = [];
|
|
172
|
+
const ref = (input) => {
|
|
173
|
+
if (input.kind === "parameter") uses.push(`argument:${input.index}`);
|
|
174
|
+
};
|
|
175
|
+
if ("memory" in op) ref(op.memory);
|
|
176
|
+
if ("pin" in op) ref(op.pin);
|
|
177
|
+
if (op.kind === "resistor") ref(op.to);
|
|
178
|
+
if (op.kind === "refEqual") {
|
|
179
|
+
ref(op.left);
|
|
180
|
+
ref(op.right);
|
|
181
|
+
}
|
|
182
|
+
if (op.kind === "call" || op.kind === "callValue") op.args.forEach((arg) => {
|
|
183
|
+
if (arg.kind !== "value") ref(arg);
|
|
184
|
+
});
|
|
185
|
+
if (op.kind === "parameter") uses.push(`argument:${op.index}`);
|
|
186
|
+
if (op.kind === "reactionValue" || op.kind === "reactionCapture") uses.push("reaction");
|
|
187
|
+
return uses;
|
|
188
|
+
}
|
|
189
|
+
function resourceName(ref) {
|
|
190
|
+
return ref.kind === "parameter" ? `$${ref.index}` : ref.name;
|
|
191
|
+
}
|
|
192
|
+
function effects(op) {
|
|
193
|
+
const reads = [], writes = [], calls = [];
|
|
194
|
+
const call = op.kind === "call" || op.kind === "callValue";
|
|
195
|
+
const maySuspend = call || [
|
|
196
|
+
"wait",
|
|
197
|
+
"elapse",
|
|
198
|
+
"receive",
|
|
199
|
+
"waitUntil",
|
|
200
|
+
"redirect"
|
|
201
|
+
].includes(op.kind);
|
|
202
|
+
const mayThrow = call || [
|
|
203
|
+
"read",
|
|
204
|
+
"write",
|
|
205
|
+
"readSample",
|
|
206
|
+
"writeIntent",
|
|
207
|
+
"resistor",
|
|
208
|
+
"bytes",
|
|
209
|
+
"index",
|
|
210
|
+
"nonNull",
|
|
211
|
+
"field",
|
|
212
|
+
"length",
|
|
213
|
+
"memoryLength",
|
|
214
|
+
"copyBytes",
|
|
215
|
+
"send",
|
|
216
|
+
"receive",
|
|
217
|
+
"notify",
|
|
218
|
+
"now",
|
|
219
|
+
"random",
|
|
220
|
+
"reactionValue",
|
|
221
|
+
"reactionCapture",
|
|
222
|
+
"if",
|
|
223
|
+
"select",
|
|
224
|
+
"and",
|
|
225
|
+
"or",
|
|
226
|
+
"assert",
|
|
227
|
+
"wait",
|
|
228
|
+
"elapse",
|
|
229
|
+
"redirect"
|
|
230
|
+
].includes(op.kind) || op.kind.startsWith("load") || op.kind.startsWith("store");
|
|
231
|
+
if (op.kind === "read") reads.push(`${op.resource}:${op.name}`);
|
|
232
|
+
if (op.kind === "write") writes.push(`state:${op.name}`);
|
|
233
|
+
if ("memory" in op) (op.kind.startsWith("store") ? writes : reads).push(`memory:${resourceName(op.memory)}`);
|
|
234
|
+
if (op.kind === "readSample") reads.push(`pin:${resourceName(op.pin)}`);
|
|
235
|
+
if (op.kind === "writeIntent") writes.push(`pin:${resourceName(op.pin)}`);
|
|
236
|
+
if (op.kind === "send") writes.push(`channel:${op.channel}`);
|
|
237
|
+
if (op.kind === "receive") {
|
|
238
|
+
reads.push(`channel:${op.channel}`);
|
|
239
|
+
writes.push(`channel:${op.channel}`);
|
|
240
|
+
}
|
|
241
|
+
if (op.kind === "notify") writes.push(`notification:${op.notification}`);
|
|
242
|
+
if (op.kind === "waitUntil") {
|
|
243
|
+
reads.push("*");
|
|
244
|
+
calls.push(op.predicate);
|
|
245
|
+
}
|
|
246
|
+
if (call) {
|
|
247
|
+
calls.push(op.name);
|
|
248
|
+
reads.push("*");
|
|
249
|
+
writes.push("*");
|
|
250
|
+
}
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
reads: Object.freeze(reads),
|
|
253
|
+
writes: Object.freeze(writes),
|
|
254
|
+
calls: Object.freeze(calls),
|
|
255
|
+
operations: Object.freeze([op.kind]),
|
|
256
|
+
maySuspend,
|
|
257
|
+
mayThrow,
|
|
258
|
+
mayRedirect: call || op.kind === "redirect",
|
|
259
|
+
mayStop: call || op.kind === "stop",
|
|
260
|
+
timeDependent: call || op.kind === "now",
|
|
261
|
+
random: call || op.kind === "random"
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function fold(op, known) {
|
|
265
|
+
const values = inputs(op);
|
|
266
|
+
if (values.some((id) => !known.has(id))) return void 0;
|
|
267
|
+
const a = known.get(values[0]), b = known.get(values[1]);
|
|
268
|
+
switch (op.kind) {
|
|
269
|
+
case "constant": {
|
|
270
|
+
const c = op.constant;
|
|
271
|
+
if (c.kind === "none") return { value: null };
|
|
272
|
+
if (c.kind === "enum") return { value: c.member };
|
|
273
|
+
if (c.kind === "number" || c.kind === "bool" || c.kind === "string") return { value: c.value };
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
case "optional": return { value: a };
|
|
277
|
+
case "isNone": return { value: a === null };
|
|
278
|
+
case "nonNull": return a === null ? void 0 : { value: a };
|
|
279
|
+
case "unary":
|
|
280
|
+
switch (op.operator) {
|
|
281
|
+
case "positive": return { value: +a };
|
|
282
|
+
case "negate": return { value: -a };
|
|
283
|
+
case "not": return { value: !a };
|
|
284
|
+
case "bitNot": return { value: ~a };
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case "binary": {
|
|
288
|
+
const x = a, y = b;
|
|
289
|
+
switch (op.operator) {
|
|
290
|
+
case "add": return { value: x + y };
|
|
291
|
+
case "subtract": return { value: x - y };
|
|
292
|
+
case "multiply": return { value: x * y };
|
|
293
|
+
case "divide": return { value: x / y };
|
|
294
|
+
case "remainder": return { value: x % y };
|
|
295
|
+
case "power": return { value: x ** y };
|
|
296
|
+
case "lessThan": return { value: x < y };
|
|
297
|
+
case "lessThanOrEqual": return { value: x <= y };
|
|
298
|
+
case "greaterThan": return { value: x > y };
|
|
299
|
+
case "greaterThanOrEqual": return { value: x >= y };
|
|
300
|
+
case "equal": return { value: a === b };
|
|
301
|
+
case "notEqual": return { value: a !== b };
|
|
302
|
+
case "concat": return { value: a + b };
|
|
303
|
+
case "bitAnd": return { value: x & y };
|
|
304
|
+
case "bitOr": return { value: x | y };
|
|
305
|
+
case "bitXor": return { value: x ^ y };
|
|
306
|
+
case "shiftLeft": return { value: x << y };
|
|
307
|
+
case "shiftRight": return { value: x >> y };
|
|
308
|
+
case "shiftRightUnsigned": return { value: x >>> y };
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case "numeric": {
|
|
313
|
+
const args = op.args.map((id) => known.get(id));
|
|
314
|
+
if (op.builtin === "isNaN") return { value: Number.isNaN(args[0]) };
|
|
315
|
+
if (op.builtin === "isFinite") return { value: Number.isFinite(args[0]) };
|
|
316
|
+
if (op.builtin === "isInteger") return { value: Number.isInteger(args[0]) };
|
|
317
|
+
if (op.builtin === "isSafeInteger") return { value: Number.isSafeInteger(args[0]) };
|
|
318
|
+
const fn = Math[op.builtin];
|
|
319
|
+
return { value: fn(...args) };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function removable(op, types, folded) {
|
|
324
|
+
switch (op.kind) {
|
|
325
|
+
case "constant": return op.constant.kind !== "bytes" && op.constant.kind !== "record";
|
|
326
|
+
case "parameter":
|
|
327
|
+
case "localRead":
|
|
328
|
+
case "optional":
|
|
329
|
+
case "isNone":
|
|
330
|
+
case "unary":
|
|
331
|
+
case "numeric": return true;
|
|
332
|
+
case "nonNull": return folded.has(op.result);
|
|
333
|
+
case "binary": {
|
|
334
|
+
if (op.operator !== "equal" && op.operator !== "notEqual") return true;
|
|
335
|
+
let type = types.get(op.left);
|
|
336
|
+
if (type?.kind === "optional") type = type.inner;
|
|
337
|
+
return !!type && [
|
|
338
|
+
"number",
|
|
339
|
+
"string",
|
|
340
|
+
"bool",
|
|
341
|
+
"enum"
|
|
342
|
+
].includes(type.kind);
|
|
343
|
+
}
|
|
344
|
+
default: return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function attributes(op) {
|
|
348
|
+
const copy = (value) => {
|
|
349
|
+
if (value === null || typeof value !== "object") return value;
|
|
350
|
+
if (Array.isArray(value)) return Object.freeze(value.map(copy));
|
|
351
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).filter(([, value]) => value !== void 0).map(([key, value]) => [key, copy(value)])));
|
|
352
|
+
};
|
|
353
|
+
return Object.freeze(Object.fromEntries(Object.entries(op).flatMap(([key, value]) => {
|
|
354
|
+
if ([
|
|
355
|
+
"kind",
|
|
356
|
+
"type",
|
|
357
|
+
"source",
|
|
358
|
+
"body",
|
|
359
|
+
"test",
|
|
360
|
+
"message",
|
|
361
|
+
"yes",
|
|
362
|
+
"no"
|
|
363
|
+
].includes(key) || value === void 0 || key === "right" && typeof value === "object") return [];
|
|
364
|
+
return [[key, copy(key === "arms" ? op.arms.map((arm) => arm.pattern) : value)]];
|
|
365
|
+
})));
|
|
366
|
+
}
|
|
367
|
+
function analyzeBody(body, options, metadata = true) {
|
|
368
|
+
return new Analyzer(body, options.optimize !== false, metadata).analyze();
|
|
369
|
+
}
|
|
370
|
+
var Analyzer = class {
|
|
371
|
+
optimize;
|
|
372
|
+
metadata;
|
|
373
|
+
nodes = [];
|
|
374
|
+
regions = [];
|
|
375
|
+
blocks = [];
|
|
376
|
+
blockSpecs = [];
|
|
377
|
+
buildingGraph = false;
|
|
378
|
+
edges = [];
|
|
379
|
+
byOperation = /* @__PURE__ */ new Map();
|
|
380
|
+
folded = /* @__PURE__ */ new Map();
|
|
381
|
+
eliminated = /* @__PURE__ */ new Set();
|
|
382
|
+
root;
|
|
383
|
+
throwExit;
|
|
384
|
+
stopExit;
|
|
385
|
+
redirectExit;
|
|
386
|
+
constructor(body, optimize, metadata) {
|
|
387
|
+
this.optimize = optimize;
|
|
388
|
+
this.metadata = metadata;
|
|
389
|
+
this.root = this.index(body, [], null, body.source);
|
|
390
|
+
this.throwExit = this.block(0, "throw");
|
|
391
|
+
this.stopExit = this.block(0, "stop");
|
|
392
|
+
this.redirectExit = this.block(0, "redirect");
|
|
393
|
+
}
|
|
394
|
+
block(region, role, position = null) {
|
|
395
|
+
const id = this.blockSpecs.length;
|
|
396
|
+
const spec = {
|
|
397
|
+
id,
|
|
398
|
+
region,
|
|
399
|
+
role,
|
|
400
|
+
position
|
|
401
|
+
};
|
|
402
|
+
this.blockSpecs.push(spec);
|
|
403
|
+
if (this.buildingGraph) this.blocks.push(this.allocateBlock(spec));
|
|
404
|
+
return id;
|
|
405
|
+
}
|
|
406
|
+
allocateBlock(spec) {
|
|
407
|
+
return {
|
|
408
|
+
...spec,
|
|
409
|
+
uses: /* @__PURE__ */ new Set(),
|
|
410
|
+
definitions: /* @__PURE__ */ new Set(),
|
|
411
|
+
liveIn: /* @__PURE__ */ new Set(),
|
|
412
|
+
liveOut: /* @__PURE__ */ new Set()
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
edge(from, to, kind, extra = {}) {
|
|
416
|
+
this.edges.push(Object.freeze({
|
|
417
|
+
from,
|
|
418
|
+
to,
|
|
419
|
+
kind,
|
|
420
|
+
...extra
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
index(input, path, parent, inherited) {
|
|
424
|
+
const id = this.regions.length;
|
|
425
|
+
const region = {
|
|
426
|
+
id,
|
|
427
|
+
parent,
|
|
428
|
+
path: Object.freeze([...path]),
|
|
429
|
+
entry: this.block(id, "entry"),
|
|
430
|
+
exit: this.block(id, "exit"),
|
|
431
|
+
nodes: []
|
|
432
|
+
};
|
|
433
|
+
this.regions.push(region);
|
|
434
|
+
input.operations.forEach((operation, index) => {
|
|
435
|
+
const position = this.nodes.length;
|
|
436
|
+
const block = this.block(id, "operation", position);
|
|
437
|
+
const source = this.metadata ? operation.source ?? input.source ?? inherited : void 0;
|
|
438
|
+
let effectSummary;
|
|
439
|
+
const node = {
|
|
440
|
+
operation,
|
|
441
|
+
position,
|
|
442
|
+
block,
|
|
443
|
+
definitionBlock: block,
|
|
444
|
+
region: id,
|
|
445
|
+
path: Object.freeze([
|
|
446
|
+
...path,
|
|
447
|
+
"operations",
|
|
448
|
+
index
|
|
449
|
+
]),
|
|
450
|
+
inputs: Object.freeze(inputs(operation)),
|
|
451
|
+
get effects() {
|
|
452
|
+
return effectSummary ??= effects(operation);
|
|
453
|
+
},
|
|
454
|
+
children: /* @__PURE__ */ new Map(),
|
|
455
|
+
...source ? { source: sourceSnapshot(source) } : {}
|
|
456
|
+
};
|
|
457
|
+
region.nodes.push(node);
|
|
458
|
+
this.nodes.push(node);
|
|
459
|
+
this.byOperation.set(operation, node);
|
|
460
|
+
const child = (key, body, suffix = [key], inherited = source) => {
|
|
461
|
+
node.children.set(key, this.index(body, [...node.path, ...suffix], id, inherited));
|
|
462
|
+
};
|
|
463
|
+
switch (operation.kind) {
|
|
464
|
+
case "if":
|
|
465
|
+
child("yes", operation.yes);
|
|
466
|
+
if (operation.no) child("no", operation.no);
|
|
467
|
+
break;
|
|
468
|
+
case "match":
|
|
469
|
+
operation.arms.forEach((arm, i) => child(`arm${i}`, arm.body, [
|
|
470
|
+
"arms",
|
|
471
|
+
i,
|
|
472
|
+
"body"
|
|
473
|
+
], arm.source ?? source));
|
|
474
|
+
if (operation.otherwise) child("otherwise", operation.otherwise);
|
|
475
|
+
break;
|
|
476
|
+
case "while":
|
|
477
|
+
child("test", operation.test.body, ["test", "body"]);
|
|
478
|
+
child("body", operation.body);
|
|
479
|
+
break;
|
|
480
|
+
case "loop":
|
|
481
|
+
child("body", operation.body);
|
|
482
|
+
break;
|
|
483
|
+
case "select":
|
|
484
|
+
child("yes", operation.yes.body, ["yes", "body"]);
|
|
485
|
+
child("no", operation.no.body, ["no", "body"]);
|
|
486
|
+
break;
|
|
487
|
+
case "and":
|
|
488
|
+
case "or":
|
|
489
|
+
child("right", operation.right.body, ["right", "body"]);
|
|
490
|
+
break;
|
|
491
|
+
case "assert": if (operation.message) child("message", operation.message.body, ["message", "body"]);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
return region;
|
|
495
|
+
}
|
|
496
|
+
link(region, target, context, exitKind = "next") {
|
|
497
|
+
this.edge(region.entry, region.nodes[0]?.block ?? region.exit, "next");
|
|
498
|
+
if (target !== null) this.edge(region.exit, target, exitKind);
|
|
499
|
+
region.nodes.forEach((node, index) => {
|
|
500
|
+
const op = node.operation, head = node.block;
|
|
501
|
+
const next = region.nodes[index + 1]?.block ?? region.exit;
|
|
502
|
+
const child = (key, destination = next, ctx = context, kind = "next") => {
|
|
503
|
+
const nested = node.children.get(key);
|
|
504
|
+
this.link(nested, destination, ctx, kind);
|
|
505
|
+
return nested;
|
|
506
|
+
};
|
|
507
|
+
const valueChild = (key, destination) => {
|
|
508
|
+
const nested = node.children.get(key);
|
|
509
|
+
this.link(nested, destination, { yieldTo: nested.exit });
|
|
510
|
+
return nested;
|
|
511
|
+
};
|
|
512
|
+
if (node.effects.mayThrow && op.kind !== "assert") this.edge(head, this.throwExit, "throw");
|
|
513
|
+
switch (op.kind) {
|
|
514
|
+
case "if":
|
|
515
|
+
this.edge(head, child("yes").entry, "true");
|
|
516
|
+
this.edge(head, node.children.has("no") ? child("no").entry : next, "false");
|
|
517
|
+
break;
|
|
518
|
+
case "match":
|
|
519
|
+
op.arms.forEach((_, i) => this.edge(head, child(`arm${i}`).entry, "arm", { arm: i }));
|
|
520
|
+
if (op.otherwise) this.edge(head, child("otherwise").entry, "default");
|
|
521
|
+
break;
|
|
522
|
+
case "while": {
|
|
523
|
+
const test = valueChild("test", null);
|
|
524
|
+
const loopContext = {
|
|
525
|
+
...context,
|
|
526
|
+
breakTo: next,
|
|
527
|
+
continueTo: test.entry
|
|
528
|
+
};
|
|
529
|
+
const body = child("body", test.entry, loopContext, "backedge");
|
|
530
|
+
this.edge(head, test.entry, "next");
|
|
531
|
+
this.edge(test.exit, body.entry, "true");
|
|
532
|
+
this.edge(test.exit, next, "false");
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case "loop": {
|
|
536
|
+
const body = node.children.get("body");
|
|
537
|
+
child("body", body.entry, {
|
|
538
|
+
...context,
|
|
539
|
+
breakTo: next,
|
|
540
|
+
continueTo: body.entry
|
|
541
|
+
}, "backedge");
|
|
542
|
+
this.edge(head, body.entry, "next");
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
case "select":
|
|
546
|
+
case "and":
|
|
547
|
+
case "or": {
|
|
548
|
+
const join = this.block(region.id, "join", node.position);
|
|
549
|
+
node.definitionBlock = join;
|
|
550
|
+
this.edge(join, next, "next");
|
|
551
|
+
if (op.kind === "select") {
|
|
552
|
+
this.edge(head, valueChild("yes", join).entry, "true");
|
|
553
|
+
this.edge(head, valueChild("no", join).entry, "false");
|
|
554
|
+
} else {
|
|
555
|
+
const right = valueChild("right", join);
|
|
556
|
+
this.edge(head, right.entry, op.kind === "and" ? "true" : "false");
|
|
557
|
+
this.edge(head, join, op.kind === "and" ? "false" : "true", { value: op.left });
|
|
558
|
+
}
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
case "assert": {
|
|
562
|
+
const failure = this.block(region.id, "throw", node.position);
|
|
563
|
+
node.failureBlock = failure;
|
|
564
|
+
this.edge(head, next, "true");
|
|
565
|
+
this.edge(failure, this.throwExit, "throw");
|
|
566
|
+
this.edge(head, op.message ? valueChild("message", failure).entry : failure, "false");
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "break":
|
|
570
|
+
if (context.breakTo === void 0) throw new TypeError("Break has no loop target");
|
|
571
|
+
this.edge(head, context.breakTo, "break");
|
|
572
|
+
break;
|
|
573
|
+
case "continue":
|
|
574
|
+
if (context.continueTo === void 0) throw new TypeError("Continue has no loop target");
|
|
575
|
+
this.edge(head, context.continueTo, "continue");
|
|
576
|
+
break;
|
|
577
|
+
case "yield":
|
|
578
|
+
if (context.yieldTo === void 0) throw new TypeError("Yield has no value-region target");
|
|
579
|
+
this.edge(head, context.yieldTo, "yield", { value: op.value });
|
|
580
|
+
break;
|
|
581
|
+
case "return":
|
|
582
|
+
case "returnValue":
|
|
583
|
+
this.edge(head, this.root.exit, "return");
|
|
584
|
+
break;
|
|
585
|
+
case "redirect":
|
|
586
|
+
this.edge(head, this.redirectExit, "redirect");
|
|
587
|
+
break;
|
|
588
|
+
case "stop":
|
|
589
|
+
this.edge(head, this.stopExit, "stop");
|
|
590
|
+
break;
|
|
591
|
+
case "wait":
|
|
592
|
+
case "elapse":
|
|
593
|
+
case "receive":
|
|
594
|
+
case "waitUntil":
|
|
595
|
+
case "call":
|
|
596
|
+
case "callValue": {
|
|
597
|
+
const resume = this.block(region.id, "resume", node.position);
|
|
598
|
+
node.resumeBlock = resume;
|
|
599
|
+
const call = op.kind === "call" || op.kind === "callValue";
|
|
600
|
+
this.edge(head, resume, "suspend");
|
|
601
|
+
if (call) {
|
|
602
|
+
const completion = this.block(region.id, "join", node.position);
|
|
603
|
+
node.completionBlock = completion;
|
|
604
|
+
node.definitionBlock = completion;
|
|
605
|
+
this.edge(head, completion, "next");
|
|
606
|
+
this.edge(resume, resume, "suspend");
|
|
607
|
+
this.edge(resume, completion, "resume");
|
|
608
|
+
this.edge(completion, next, "next");
|
|
609
|
+
for (const from of [head, resume]) {
|
|
610
|
+
this.edge(from, this.redirectExit, "redirect");
|
|
611
|
+
this.edge(from, this.stopExit, "stop");
|
|
612
|
+
}
|
|
613
|
+
this.edge(resume, this.throwExit, "throw");
|
|
614
|
+
} else {
|
|
615
|
+
if (op.kind === "receive") node.definitionBlock = resume;
|
|
616
|
+
this.edge(resume, op.kind === "waitUntil" ? this.root.exit : next, "resume");
|
|
617
|
+
if (op.kind === "elapse") this.edge(head, next, "next");
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
default: this.edge(head, next, "next");
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
analyze() {
|
|
626
|
+
const types = /* @__PURE__ */ new Map();
|
|
627
|
+
for (const node of this.nodes) if ("result" in node.operation) types.set(node.operation.result, node.operation.type);
|
|
628
|
+
const known = /* @__PURE__ */ new Map();
|
|
629
|
+
if (this.optimize) {
|
|
630
|
+
for (const node of this.nodes) {
|
|
631
|
+
if (!("result" in node.operation)) continue;
|
|
632
|
+
const constant = fold(node.operation, known);
|
|
633
|
+
if (constant) {
|
|
634
|
+
known.set(node.operation.result, constant.value);
|
|
635
|
+
if (node.operation.kind !== "constant") this.folded.set(node.position, constant.value);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const foldedByResult = /* @__PURE__ */ new Map();
|
|
639
|
+
for (const node of this.nodes) if ("result" in node.operation && this.folded.has(node.position)) foldedByResult.set(node.operation.result, this.folded.get(node.position));
|
|
640
|
+
const definitions = new Map(this.nodes.flatMap((node) => "result" in node.operation ? [[node.operation.result, node]] : []));
|
|
641
|
+
const required = /* @__PURE__ */ new Set();
|
|
642
|
+
const queue = [];
|
|
643
|
+
const require = (id) => {
|
|
644
|
+
if (!required.has(id)) {
|
|
645
|
+
required.add(id);
|
|
646
|
+
queue.push(id);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
for (const node of this.nodes) if (!this.folded.has(node.position)) node.inputs.forEach(require);
|
|
650
|
+
for (let i = 0; i < queue.length; i++) {
|
|
651
|
+
const node = definitions.get(queue[i]);
|
|
652
|
+
if (node && !this.folded.has(node.position)) node.inputs.forEach(require);
|
|
653
|
+
}
|
|
654
|
+
for (const node of this.nodes) if ("result" in node.operation && !required.has(node.operation.result) && removable(node.operation, types, foldedByResult)) this.eliminated.add(node.position);
|
|
655
|
+
}
|
|
656
|
+
let completed;
|
|
657
|
+
const complete = () => completed ??= this.complete(known);
|
|
658
|
+
return {
|
|
659
|
+
nodes: this.nodes,
|
|
660
|
+
byOperation: this.byOperation,
|
|
661
|
+
folded: this.folded,
|
|
662
|
+
constants: known,
|
|
663
|
+
eliminated: this.eliminated,
|
|
664
|
+
optimizations: Object.freeze({
|
|
665
|
+
enabled: this.optimize,
|
|
666
|
+
foldedPositions: Object.freeze([...this.folded.keys()].filter((id) => !this.eliminated.has(id))),
|
|
667
|
+
eliminatedPositions: Object.freeze([...this.eliminated])
|
|
668
|
+
}),
|
|
669
|
+
get graph() {
|
|
670
|
+
return complete().graph;
|
|
671
|
+
},
|
|
672
|
+
get effects() {
|
|
673
|
+
return complete().effects;
|
|
674
|
+
},
|
|
675
|
+
get resources() {
|
|
676
|
+
return complete().resources;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
complete(known) {
|
|
681
|
+
this.buildingGraph = true;
|
|
682
|
+
this.blocks = this.blockSpecs.map((spec) => this.allocateBlock(spec));
|
|
683
|
+
this.link(this.root, null, {});
|
|
684
|
+
for (const node of this.nodes) {
|
|
685
|
+
if (this.eliminated.has(node.position)) continue;
|
|
686
|
+
const block = this.blocks[node.block], op = node.operation;
|
|
687
|
+
if (!this.folded.has(node.position)) {
|
|
688
|
+
node.inputs.forEach((id) => block.uses.add(`value:${id}`));
|
|
689
|
+
argumentUses(op).forEach((key) => block.uses.add(key));
|
|
690
|
+
if (op.kind === "localRead") block.uses.add(`local:${op.local}`);
|
|
691
|
+
}
|
|
692
|
+
if ("result" in op) this.blocks[node.definitionBlock].definitions.add(`value:${op.result}`);
|
|
693
|
+
if (op.kind === "local" || op.kind === "localWrite") block.definitions.add(`local:${op.local}`);
|
|
694
|
+
}
|
|
695
|
+
const regionOutputs = /* @__PURE__ */ new Map();
|
|
696
|
+
for (const node of this.nodes) {
|
|
697
|
+
const child = node.operation.kind === "while" ? node.children.get("test") : node.operation.kind === "assert" ? node.children.get("message") : void 0;
|
|
698
|
+
if (!child) continue;
|
|
699
|
+
const key = `region:${child.id}`;
|
|
700
|
+
regionOutputs.set(child.exit, key);
|
|
701
|
+
this.blocks[node.failureBlock ?? child.exit].uses.add(key);
|
|
702
|
+
}
|
|
703
|
+
for (const edge of this.edges) if (edge.kind === "yield" && regionOutputs.has(edge.to)) this.blocks[edge.from].definitions.add(regionOutputs.get(edge.to));
|
|
704
|
+
const outgoing = this.blocks.map(() => []), incoming = this.blocks.map(() => []);
|
|
705
|
+
this.edges.forEach((edge) => {
|
|
706
|
+
outgoing[edge.from].push(edge.to);
|
|
707
|
+
incoming[edge.to].push(edge.from);
|
|
708
|
+
});
|
|
709
|
+
const baselineUses = this.blocks.map((block) => new Set([...block.uses].filter((key) => key.startsWith("region:"))));
|
|
710
|
+
const baselineDefinitions = this.blocks.map((block) => new Set([...block.definitions].filter((key) => key.startsWith("region:"))));
|
|
711
|
+
for (const node of this.nodes) {
|
|
712
|
+
const op = node.operation;
|
|
713
|
+
node.inputs.forEach((id) => baselineUses[node.block].add(`value:${id}`));
|
|
714
|
+
argumentUses(op).forEach((key) => baselineUses[node.block].add(key));
|
|
715
|
+
if (op.kind === "localRead") baselineUses[node.block].add(`local:${op.local}`);
|
|
716
|
+
if ("result" in op) baselineDefinitions[node.definitionBlock].add(`value:${op.result}`);
|
|
717
|
+
if (op.kind === "local" || op.kind === "localWrite") baselineDefinitions[node.block].add(`local:${op.local}`);
|
|
718
|
+
}
|
|
719
|
+
const liveness = (uses, definitions) => {
|
|
720
|
+
const input = this.blocks.map(() => /* @__PURE__ */ new Set());
|
|
721
|
+
const output = this.blocks.map(() => /* @__PURE__ */ new Set());
|
|
722
|
+
let changed = true;
|
|
723
|
+
while (changed) {
|
|
724
|
+
changed = false;
|
|
725
|
+
for (let i = this.blocks.length - 1; i >= 0; i--) {
|
|
726
|
+
const out = new Set(outgoing[i].flatMap((id) => [...input[id]]));
|
|
727
|
+
const incoming = /* @__PURE__ */ new Set([...uses[i], ...[...out].filter((key) => !definitions[i].has(key))]);
|
|
728
|
+
if (out.size !== output[i].size || [...out].some((key) => !output[i].has(key)) || incoming.size !== input[i].size || [...incoming].some((key) => !input[i].has(key))) {
|
|
729
|
+
changed = true;
|
|
730
|
+
output[i] = out;
|
|
731
|
+
input[i] = incoming;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
input,
|
|
737
|
+
output
|
|
738
|
+
};
|
|
739
|
+
};
|
|
740
|
+
const optimizedLive = liveness(this.blocks.map((block) => block.uses), this.blocks.map((block) => block.definitions));
|
|
741
|
+
const baselineLive = liveness(baselineUses, baselineDefinitions);
|
|
742
|
+
this.blocks.forEach((block, index) => {
|
|
743
|
+
block.liveIn = optimizedLive.input[index];
|
|
744
|
+
block.liveOut = optimizedLive.output[index];
|
|
745
|
+
});
|
|
746
|
+
const yieldInputs = /* @__PURE__ */ new Map();
|
|
747
|
+
for (const edge of this.edges) if (edge.kind === "yield" && edge.value !== void 0) {
|
|
748
|
+
const values = yieldInputs.get(edge.to) ?? [];
|
|
749
|
+
values.push(edge.value);
|
|
750
|
+
yieldInputs.set(edge.to, values);
|
|
751
|
+
}
|
|
752
|
+
const users = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const node of this.nodes) for (const input of node.inputs) {
|
|
754
|
+
const positions = users.get(input) ?? [];
|
|
755
|
+
positions.push(node.position);
|
|
756
|
+
users.set(input, positions);
|
|
757
|
+
}
|
|
758
|
+
const values = this.nodes.flatMap((node) => {
|
|
759
|
+
const op = node.operation;
|
|
760
|
+
if (!("result" in op)) return [];
|
|
761
|
+
const phi = op.kind === "select" || op.kind === "and" || op.kind === "or";
|
|
762
|
+
const phiInputs = phi ? [...node.children.values()].flatMap((region) => yieldInputs.get(region.exit) ?? []) : [];
|
|
763
|
+
if (op.kind === "and" || op.kind === "or") phiInputs.unshift(op.left);
|
|
764
|
+
return [Object.freeze({
|
|
765
|
+
result: op.result,
|
|
766
|
+
type: typeSnapshot(op.type),
|
|
767
|
+
position: node.position,
|
|
768
|
+
region: node.region,
|
|
769
|
+
block: node.definitionBlock,
|
|
770
|
+
form: phi ? "phi" : "operation",
|
|
771
|
+
inputs: Object.freeze(phi ? phiInputs : [...node.inputs]),
|
|
772
|
+
users: Object.freeze(users.get(op.result) ?? []),
|
|
773
|
+
eliminated: this.eliminated.has(node.position),
|
|
774
|
+
folded: this.folded.has(node.position)
|
|
775
|
+
})];
|
|
776
|
+
});
|
|
777
|
+
const graph = Object.freeze({
|
|
778
|
+
entry: this.root.entry,
|
|
779
|
+
normalExit: this.root.exit,
|
|
780
|
+
regions: Object.freeze(this.regions.map(({ id, parent, path, entry, exit }) => Object.freeze({
|
|
781
|
+
id,
|
|
782
|
+
parent,
|
|
783
|
+
path,
|
|
784
|
+
entry,
|
|
785
|
+
exit
|
|
786
|
+
}))),
|
|
787
|
+
blocks: Object.freeze(this.blocks.map((block) => Object.freeze({
|
|
788
|
+
...block,
|
|
789
|
+
predecessors: Object.freeze([...new Set(incoming[block.id])]),
|
|
790
|
+
successors: Object.freeze([...new Set(outgoing[block.id])]),
|
|
791
|
+
uses: Object.freeze([...block.uses]),
|
|
792
|
+
definitions: Object.freeze([...block.definitions]),
|
|
793
|
+
liveIn: Object.freeze([...block.liveIn]),
|
|
794
|
+
liveOut: Object.freeze([...block.liveOut]),
|
|
795
|
+
baselineLiveIn: Object.freeze([...baselineLive.input[block.id]]),
|
|
796
|
+
baselineLiveOut: Object.freeze([...baselineLive.output[block.id]])
|
|
797
|
+
}))),
|
|
798
|
+
edges: Object.freeze([...this.edges]),
|
|
799
|
+
values: Object.freeze(values),
|
|
800
|
+
operations: Object.freeze(this.nodes.map((node) => Object.freeze({
|
|
801
|
+
position: node.position,
|
|
802
|
+
block: node.block,
|
|
803
|
+
region: node.region,
|
|
804
|
+
kind: node.operation.kind,
|
|
805
|
+
path: node.path,
|
|
806
|
+
...node.source ? { source: node.source } : {},
|
|
807
|
+
inputs: node.inputs,
|
|
808
|
+
attributes: attributes(node.operation),
|
|
809
|
+
regions: Object.freeze([...node.children].map(([role, region]) => Object.freeze({
|
|
810
|
+
role,
|
|
811
|
+
region: region.id
|
|
812
|
+
}))),
|
|
813
|
+
effects: node.effects,
|
|
814
|
+
eliminated: this.eliminated.has(node.position),
|
|
815
|
+
folded: this.folded.has(node.position)
|
|
816
|
+
})))
|
|
817
|
+
});
|
|
818
|
+
const summaries = this.nodes.map((node) => node.effects);
|
|
819
|
+
const union = (key) => Object.freeze([...new Set(summaries.flatMap((summary) => summary[key]))]);
|
|
820
|
+
const effectMetadata = Object.freeze({
|
|
821
|
+
reads: union("reads"),
|
|
822
|
+
writes: union("writes"),
|
|
823
|
+
calls: union("calls"),
|
|
824
|
+
operations: union("operations"),
|
|
825
|
+
maySuspend: summaries.some((e) => e.maySuspend),
|
|
826
|
+
mayRedirect: summaries.some((e) => e.mayRedirect),
|
|
827
|
+
mayStop: summaries.some((e) => e.mayStop),
|
|
828
|
+
timeDependent: summaries.some((e) => e.timeDependent),
|
|
829
|
+
random: summaries.some((e) => e.random)
|
|
830
|
+
});
|
|
831
|
+
const resources = new Set([...effectMetadata.reads, ...effectMetadata.writes].filter((key) => key !== "*"));
|
|
832
|
+
for (const node of this.nodes) {
|
|
833
|
+
const op = node.operation;
|
|
834
|
+
if (op.kind === "resistor") resources.add(`pin:${resourceName(op.to)}`);
|
|
835
|
+
if (op.kind === "refEqual") for (const ref of [op.left, op.right]) resources.add(`${ref.kind === "parameter" ? ref.resource : ref.kind}:${resourceName(ref)}`);
|
|
836
|
+
if (op.kind === "call" || op.kind === "callValue") {
|
|
837
|
+
for (const arg of op.args) if (arg.kind !== "value") resources.add(`${arg.kind === "parameter" ? arg.resource : arg.kind}:${resourceName(arg)}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
nodes: this.nodes,
|
|
842
|
+
byOperation: this.byOperation,
|
|
843
|
+
graph,
|
|
844
|
+
folded: this.folded,
|
|
845
|
+
constants: known,
|
|
846
|
+
eliminated: this.eliminated,
|
|
847
|
+
effects: effectMetadata,
|
|
848
|
+
resources: Object.freeze([...resources]),
|
|
849
|
+
optimizations: Object.freeze({
|
|
850
|
+
enabled: this.optimize,
|
|
851
|
+
foldedPositions: Object.freeze([...this.folded.keys()].filter((id) => !this.eliminated.has(id))),
|
|
852
|
+
eliminatedPositions: Object.freeze([...this.eliminated])
|
|
853
|
+
})
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/metadata.ts
|
|
859
|
+
function layoutMetadata(analysis, ranges, resume, source, options, temporaries) {
|
|
860
|
+
const slots = [];
|
|
861
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
862
|
+
const slotTypes = /* @__PURE__ */ new Map();
|
|
863
|
+
for (const node of analysis.nodes) {
|
|
864
|
+
const op = node.operation;
|
|
865
|
+
if ("result" in op) slotTypes.set(`value:${op.result}`, typeSnapshot(op.type));
|
|
866
|
+
if (op.kind === "local") slotTypes.set(`local:${op.local}`, typeSnapshot(op.type));
|
|
867
|
+
if (op.kind === "parameter") slotTypes.set(`argument:${op.index}`, typeSnapshot(op.type));
|
|
868
|
+
const resource = (ref) => {
|
|
869
|
+
if (ref.kind === "parameter" && ref.resource) slotTypes.set(`argument:${ref.index}`, Object.freeze({ kind: ref.resource }));
|
|
870
|
+
};
|
|
871
|
+
if ("memory" in op) resource(op.memory);
|
|
872
|
+
if ("pin" in op) resource(op.pin);
|
|
873
|
+
if (op.kind === "resistor") resource(op.to);
|
|
874
|
+
if (op.kind === "refEqual") {
|
|
875
|
+
resource(op.left);
|
|
876
|
+
resource(op.right);
|
|
877
|
+
}
|
|
878
|
+
if (op.kind === "call" || op.kind === "callValue") op.args.forEach((arg) => {
|
|
879
|
+
if (arg.kind !== "value") resource(arg);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const slot = (key, kind, name, region, definition, materialized = true) => {
|
|
883
|
+
if (byKey.has(key)) return;
|
|
884
|
+
byKey.set(key, slots.length);
|
|
885
|
+
slots.push(Object.freeze({
|
|
886
|
+
id: slots.length,
|
|
887
|
+
key,
|
|
888
|
+
kind,
|
|
889
|
+
name,
|
|
890
|
+
region,
|
|
891
|
+
definition,
|
|
892
|
+
materialized,
|
|
893
|
+
...slotTypes.has(key) ? { type: slotTypes.get(key) } : {}
|
|
894
|
+
}));
|
|
895
|
+
};
|
|
896
|
+
for (const node of analysis.nodes) {
|
|
897
|
+
const op = node.operation;
|
|
898
|
+
if ("result" in op) slot(`value:${op.result}`, "value", `v${op.result}`, node.region, node.position, !analysis.eliminated.has(node.position));
|
|
899
|
+
if (op.kind === "local") slot(`local:${op.local}`, "local", `l${op.local}`, node.region, node.position);
|
|
900
|
+
}
|
|
901
|
+
for (const temporary of temporaries) {
|
|
902
|
+
slotTypes.set(temporary.key, typeSnapshot(temporary.type));
|
|
903
|
+
slot(temporary.key, "temporary", temporary.name, temporary.region, temporary.definition);
|
|
904
|
+
}
|
|
905
|
+
for (const block of analysis.graph.blocks) for (const key of [
|
|
906
|
+
...block.uses,
|
|
907
|
+
...block.liveIn,
|
|
908
|
+
...block.baselineLiveIn
|
|
909
|
+
]) {
|
|
910
|
+
if (key.startsWith("argument:")) slot(key, "argument", `args[${key.slice(9)}]`, 0, null);
|
|
911
|
+
if (key === "reaction") slot(key, "reaction", "reaction", 0, null);
|
|
912
|
+
}
|
|
913
|
+
const materializations = Object.freeze([...analysis.constants].flatMap(([result, value]) => {
|
|
914
|
+
const id = byKey.get(`value:${result}`);
|
|
915
|
+
return id === void 0 ? [] : [Object.freeze({
|
|
916
|
+
slot: id,
|
|
917
|
+
kind: "constant",
|
|
918
|
+
value
|
|
919
|
+
})];
|
|
920
|
+
}));
|
|
921
|
+
const materializedConstants = new Set(materializations.map((item) => item.slot));
|
|
922
|
+
const requiredSlots = (keys) => Object.freeze(keys.flatMap((key) => {
|
|
923
|
+
const id = byKey.get(key);
|
|
924
|
+
return id === void 0 ? [] : [id];
|
|
925
|
+
}).sort((a, b) => a - b));
|
|
926
|
+
const captureSlots = (keys) => Object.freeze(requiredSlots(keys).filter((id) => slots[id].materialized && !materializedConstants.has(id)));
|
|
927
|
+
const liveSlots = (keys) => Object.freeze(keys.flatMap((key) => {
|
|
928
|
+
const id = byKey.get(key);
|
|
929
|
+
return id === void 0 || !slots[id].materialized ? [] : [id];
|
|
930
|
+
}).sort((a, b) => a - b));
|
|
931
|
+
const baseline = (node, phase, block = node.block) => Object.freeze({
|
|
932
|
+
position: node.position,
|
|
933
|
+
block,
|
|
934
|
+
phase
|
|
935
|
+
});
|
|
936
|
+
const nodeByPath = new Map(analysis.nodes.map((node) => [JSON.stringify(node.path), node]));
|
|
937
|
+
const lines = source.split("\n");
|
|
938
|
+
const lineRange = (line) => Object.freeze({
|
|
939
|
+
start: Object.freeze({
|
|
940
|
+
line,
|
|
941
|
+
column: (lines[line - 1]?.search(/\S/) ?? 0) + 1
|
|
942
|
+
}),
|
|
943
|
+
end: Object.freeze({
|
|
944
|
+
line,
|
|
945
|
+
column: (lines[line - 1]?.length ?? 0) + 1
|
|
946
|
+
})
|
|
947
|
+
});
|
|
948
|
+
const resumeMap = Object.freeze({ entries: Object.freeze(resume.map((point) => {
|
|
949
|
+
const node = nodeByPath.get(JSON.stringify(point.path));
|
|
950
|
+
const terminal = node.operation.kind === "redirect" || node.operation.kind === "stop" || node.operation.kind === "waitUntil";
|
|
951
|
+
const keys = terminal ? [] : analysis.graph.blocks[node.resumeBlock].liveIn;
|
|
952
|
+
return Object.freeze({
|
|
953
|
+
id: point.id,
|
|
954
|
+
kind: point.kind,
|
|
955
|
+
suspended: baseline(node, "before"),
|
|
956
|
+
baseline: baseline(node, point.delegated ? "delegate" : "after", node.resumeBlock ?? node.block),
|
|
957
|
+
generated: lineRange(point.line),
|
|
958
|
+
continuation: Object.freeze({
|
|
959
|
+
line: point.delegated ? point.line : Math.min(point.line + 1, lines.length),
|
|
960
|
+
column: point.delegated ? point.column : (lines[point.line]?.search(/\S/) ?? 0) + 1
|
|
961
|
+
}),
|
|
962
|
+
liveSlots: liveSlots(keys),
|
|
963
|
+
delegated: point.delegated,
|
|
964
|
+
terminal
|
|
965
|
+
});
|
|
966
|
+
})) });
|
|
967
|
+
const exitMap = [];
|
|
968
|
+
const exit = (node, reason, range, block = node.block) => {
|
|
969
|
+
exitMap.push(Object.freeze({
|
|
970
|
+
baseline: baseline(node, "before", block),
|
|
971
|
+
reason,
|
|
972
|
+
generated: range,
|
|
973
|
+
requiredSlots: requiredSlots(analysis.graph.blocks[block].baselineLiveIn),
|
|
974
|
+
captureSlots: captureSlots(analysis.graph.blocks[block].baselineLiveIn)
|
|
975
|
+
}));
|
|
976
|
+
};
|
|
977
|
+
for (const node of analysis.nodes) {
|
|
978
|
+
const range = ranges.get(node.position);
|
|
979
|
+
if (!range) continue;
|
|
980
|
+
exit(node, "deopt", lineRange(range.start.line));
|
|
981
|
+
if (node.effects.mayThrow && !analysis.folded.has(node.position)) {
|
|
982
|
+
if (node.operation.kind === "assert") {
|
|
983
|
+
let failureLine = range.start.line;
|
|
984
|
+
for (let line = range.start.line; line <= range.end.line; line++) if (lines[line - 1]?.includes("host.assert(")) failureLine = line;
|
|
985
|
+
exit(node, "throw", lineRange(failureLine), node.failureBlock);
|
|
986
|
+
} else exit(node, "throw", lineRange(range.start.line));
|
|
987
|
+
}
|
|
988
|
+
if (node.operation.kind === "return" || node.operation.kind === "returnValue") exit(node, "return", lineRange(range.start.line));
|
|
989
|
+
}
|
|
990
|
+
for (const point of resumeMap.entries) {
|
|
991
|
+
const keys = point.terminal ? [] : analysis.graph.blocks[point.baseline.block].baselineLiveIn;
|
|
992
|
+
exitMap.push(Object.freeze({
|
|
993
|
+
baseline: point.baseline,
|
|
994
|
+
reason: point.kind === "redirect" ? "redirect" : point.kind === "stop" ? "stop" : "suspend",
|
|
995
|
+
generated: point.generated,
|
|
996
|
+
requiredSlots: requiredSlots(keys),
|
|
997
|
+
captureSlots: captureSlots(keys)
|
|
998
|
+
}));
|
|
999
|
+
}
|
|
1000
|
+
for (const point of exitMap) for (const id of point.requiredSlots) if (!slots[id].materialized && !materializedConstants.has(id)) throw new Error(`Missing baseline value for frame slot ${id} at position ${point.baseline.position}`);
|
|
1001
|
+
const binding = options.binding ? Object.freeze({
|
|
1002
|
+
...options.binding,
|
|
1003
|
+
...options.binding.instruction ? { instruction: Object.freeze({ ...options.binding.instruction }) } : {}
|
|
1004
|
+
}) : null;
|
|
1005
|
+
return {
|
|
1006
|
+
dependencies: Object.freeze({
|
|
1007
|
+
binding,
|
|
1008
|
+
reads: analysis.effects.reads,
|
|
1009
|
+
writes: analysis.effects.writes,
|
|
1010
|
+
calls: analysis.effects.calls,
|
|
1011
|
+
resources: analysis.resources,
|
|
1012
|
+
unresolvedCalls: analysis.effects.calls.length !== 0
|
|
1013
|
+
}),
|
|
1014
|
+
sourceMap: Object.freeze({ entries: Object.freeze(analysis.nodes.map((node) => Object.freeze({
|
|
1015
|
+
position: node.position,
|
|
1016
|
+
block: node.block,
|
|
1017
|
+
path: node.path,
|
|
1018
|
+
kind: node.operation.kind,
|
|
1019
|
+
...node.source ? { source: node.source } : {},
|
|
1020
|
+
generated: ranges.get(node.position) ?? null,
|
|
1021
|
+
eliminated: analysis.eliminated.has(node.position),
|
|
1022
|
+
folded: analysis.folded.has(node.position)
|
|
1023
|
+
}))) }),
|
|
1024
|
+
frameLayout: Object.freeze({
|
|
1025
|
+
storage: "native-generator",
|
|
1026
|
+
serializable: false,
|
|
1027
|
+
slots: Object.freeze(slots),
|
|
1028
|
+
materializations
|
|
1029
|
+
}),
|
|
1030
|
+
resumeMap,
|
|
1031
|
+
exitMap: Object.freeze(exitMap)
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region src/compiler.ts
|
|
1036
|
+
var binaryOperators = {
|
|
1037
|
+
add: "+",
|
|
1038
|
+
subtract: "-",
|
|
1039
|
+
multiply: "*",
|
|
1040
|
+
divide: "/",
|
|
1041
|
+
remainder: "%",
|
|
1042
|
+
power: "**",
|
|
1043
|
+
lessThan: "<",
|
|
1044
|
+
lessThanOrEqual: "<=",
|
|
1045
|
+
greaterThan: ">",
|
|
1046
|
+
greaterThanOrEqual: ">=",
|
|
1047
|
+
concat: "+",
|
|
1048
|
+
bitAnd: "&",
|
|
1049
|
+
bitOr: "|",
|
|
1050
|
+
bitXor: "^",
|
|
1051
|
+
shiftLeft: "<<",
|
|
1052
|
+
shiftRight: ">>",
|
|
1053
|
+
shiftRightUnsigned: ">>>"
|
|
1054
|
+
};
|
|
1055
|
+
var numericBuiltins = /* @__PURE__ */ new Set([
|
|
1056
|
+
"isNaN",
|
|
1057
|
+
"isFinite",
|
|
1058
|
+
"isInteger",
|
|
1059
|
+
"isSafeInteger",
|
|
1060
|
+
"abs",
|
|
1061
|
+
"floor",
|
|
1062
|
+
"ceil",
|
|
1063
|
+
"trunc",
|
|
1064
|
+
"round",
|
|
1065
|
+
"sqrt",
|
|
1066
|
+
"min",
|
|
1067
|
+
"max",
|
|
1068
|
+
"pow"
|
|
1069
|
+
]);
|
|
1070
|
+
var numberBuiltins = /* @__PURE__ */ new Set([
|
|
1071
|
+
"isNaN",
|
|
1072
|
+
"isFinite",
|
|
1073
|
+
"isInteger",
|
|
1074
|
+
"isSafeInteger"
|
|
1075
|
+
]);
|
|
1076
|
+
/** Inputs must already have passed device-model validation and contextual binding. */
|
|
1077
|
+
function compileBody(region, options = {}) {
|
|
1078
|
+
return new Emitter(analyzeBody(region, options, false), options, false).compileExecutable(region);
|
|
1079
|
+
}
|
|
1080
|
+
function compileBodyArtifact(region, options = {}) {
|
|
1081
|
+
return new Emitter(analyzeBody(region, options), options).compile(region);
|
|
1082
|
+
}
|
|
1083
|
+
function compileComputation(computation, options = {}) {
|
|
1084
|
+
return completeComputation(compileBody(computation.body, options));
|
|
1085
|
+
}
|
|
1086
|
+
function compileComputationArtifact(computation, options = {}) {
|
|
1087
|
+
const body = compileBodyArtifact(computation.source && !computation.body.source ? {
|
|
1088
|
+
...computation.body,
|
|
1089
|
+
source: computation.source
|
|
1090
|
+
} : computation.body, options);
|
|
1091
|
+
return Object.freeze(Object.defineProperties({}, {
|
|
1092
|
+
...Object.getOwnPropertyDescriptors(body),
|
|
1093
|
+
execute: {
|
|
1094
|
+
value: completeComputation(body.execute),
|
|
1095
|
+
enumerable: true
|
|
1096
|
+
}
|
|
1097
|
+
}));
|
|
1098
|
+
}
|
|
1099
|
+
function completeComputation(body) {
|
|
1100
|
+
return (host) => {
|
|
1101
|
+
const generator = body(host);
|
|
1102
|
+
const result = generator.next();
|
|
1103
|
+
if (!result.done) {
|
|
1104
|
+
generator.return(void 0);
|
|
1105
|
+
throw new TypeError(`Computation unexpectedly suspended with ${result.value.kind}`);
|
|
1106
|
+
}
|
|
1107
|
+
return result.value;
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function name(prefix, id) {
|
|
1111
|
+
if (!Number.isSafeInteger(id) || id < 0) throw new TypeError("IR identifiers must be nonnegative safe integers");
|
|
1112
|
+
return `${prefix}${id}`;
|
|
1113
|
+
}
|
|
1114
|
+
var value = (id) => name("v", id);
|
|
1115
|
+
var local = (id) => name("l", id);
|
|
1116
|
+
var quote = (text) => JSON.stringify(text);
|
|
1117
|
+
function snapshotSource(source) {
|
|
1118
|
+
if (source.uri !== void 0) return Object.freeze({
|
|
1119
|
+
...source,
|
|
1120
|
+
range: Object.freeze({
|
|
1121
|
+
start: Object.freeze({ ...source.range.start }),
|
|
1122
|
+
end: Object.freeze({ ...source.range.end })
|
|
1123
|
+
})
|
|
1124
|
+
});
|
|
1125
|
+
return Object.freeze({ ...source });
|
|
1126
|
+
}
|
|
1127
|
+
var Emitter = class {
|
|
1128
|
+
analysis;
|
|
1129
|
+
options;
|
|
1130
|
+
captureMetadata;
|
|
1131
|
+
constructor(analysis, options, captureMetadata = true) {
|
|
1132
|
+
this.analysis = analysis;
|
|
1133
|
+
this.options = options;
|
|
1134
|
+
this.captureMetadata = captureMetadata;
|
|
1135
|
+
}
|
|
1136
|
+
ranges = /* @__PURE__ */ new Map();
|
|
1137
|
+
temporaries = [];
|
|
1138
|
+
lines = [];
|
|
1139
|
+
constants = [];
|
|
1140
|
+
primitiveConstants = /* @__PURE__ */ new Map();
|
|
1141
|
+
negativeZero = Symbol("negative zero");
|
|
1142
|
+
positions = [];
|
|
1143
|
+
resume = [];
|
|
1144
|
+
types = /* @__PURE__ */ new Map();
|
|
1145
|
+
indent = 0;
|
|
1146
|
+
serial = 0;
|
|
1147
|
+
compileExecutable(region) {
|
|
1148
|
+
return this.generate(region).execute;
|
|
1149
|
+
}
|
|
1150
|
+
compile(region) {
|
|
1151
|
+
const generated = this.generate(region);
|
|
1152
|
+
const analysis = this.analysis;
|
|
1153
|
+
let layout;
|
|
1154
|
+
const metadata = () => layout ??= layoutMetadata(analysis, this.ranges, this.resume, generated.source, this.options, this.temporaries);
|
|
1155
|
+
return Object.freeze({
|
|
1156
|
+
...generated,
|
|
1157
|
+
positions: Object.freeze(this.positions.slice()),
|
|
1158
|
+
resume: Object.freeze(this.resume.slice()),
|
|
1159
|
+
optimizations: analysis.optimizations,
|
|
1160
|
+
get effects() {
|
|
1161
|
+
return analysis.effects;
|
|
1162
|
+
},
|
|
1163
|
+
get mir() {
|
|
1164
|
+
return analysis.graph;
|
|
1165
|
+
},
|
|
1166
|
+
get dependencies() {
|
|
1167
|
+
return metadata().dependencies;
|
|
1168
|
+
},
|
|
1169
|
+
get sourceMap() {
|
|
1170
|
+
return metadata().sourceMap;
|
|
1171
|
+
},
|
|
1172
|
+
get frameLayout() {
|
|
1173
|
+
return metadata().frameLayout;
|
|
1174
|
+
},
|
|
1175
|
+
get resumeMap() {
|
|
1176
|
+
return metadata().resumeMap;
|
|
1177
|
+
},
|
|
1178
|
+
get exitMap() {
|
|
1179
|
+
return metadata().exitMap;
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
generate(region) {
|
|
1184
|
+
this.line("function* compiledBody(host, args = [], reaction) {");
|
|
1185
|
+
this.indent++;
|
|
1186
|
+
this.region(region, []);
|
|
1187
|
+
this.indent--;
|
|
1188
|
+
this.line("}");
|
|
1189
|
+
const source = this.lines.join("\n");
|
|
1190
|
+
const constants = Object.freeze(this.constants.slice());
|
|
1191
|
+
return {
|
|
1192
|
+
execute: new Function("C", "H", `"use strict"; return (${source});`)(constants, runtimeHelpers),
|
|
1193
|
+
source,
|
|
1194
|
+
constants
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
line(text) {
|
|
1198
|
+
this.lines.push(`${" ".repeat(this.indent)}${text}`);
|
|
1199
|
+
}
|
|
1200
|
+
block(header, body) {
|
|
1201
|
+
this.line(`${header} {`);
|
|
1202
|
+
this.indent++;
|
|
1203
|
+
body();
|
|
1204
|
+
this.indent--;
|
|
1205
|
+
this.line("}");
|
|
1206
|
+
}
|
|
1207
|
+
capture(input) {
|
|
1208
|
+
const primitive = input === null || typeof input !== "object";
|
|
1209
|
+
const key = Object.is(input, -0) ? this.negativeZero : input;
|
|
1210
|
+
const existing = primitive ? this.primitiveConstants.get(key) : void 0;
|
|
1211
|
+
if (existing !== void 0) return `C[${existing}]`;
|
|
1212
|
+
const index = this.constants.push(input) - 1;
|
|
1213
|
+
if (primitive) this.primitiveConstants.set(key, index);
|
|
1214
|
+
return `C[${index}]`;
|
|
1215
|
+
}
|
|
1216
|
+
ref(ref) {
|
|
1217
|
+
return ref.kind === "parameter" ? `args[${ref.index}]` : this.capture(ref.name);
|
|
1218
|
+
}
|
|
1219
|
+
argument(argument) {
|
|
1220
|
+
return argument.kind === "value" ? value(argument.value) : this.ref(argument);
|
|
1221
|
+
}
|
|
1222
|
+
constant(constant) {
|
|
1223
|
+
switch (constant.kind) {
|
|
1224
|
+
case "number":
|
|
1225
|
+
case "bool":
|
|
1226
|
+
case "string": return constant.value;
|
|
1227
|
+
case "none": return null;
|
|
1228
|
+
case "enum": return createEnum(constant.name, constant.member);
|
|
1229
|
+
case "bytes": return Object.freeze(Array.from(createBytes(constant.value)));
|
|
1230
|
+
case "record": return createRecord(constant.fields.map((field) => [field.name, field.value]));
|
|
1231
|
+
default: return exhaustive(constant);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
position(operation, path) {
|
|
1235
|
+
const position = Object.freeze({
|
|
1236
|
+
path: Object.freeze([...path]),
|
|
1237
|
+
kind: operation.kind,
|
|
1238
|
+
line: this.lines.length + 1,
|
|
1239
|
+
column: this.indent * 2 + 1,
|
|
1240
|
+
...this.analysis.byOperation.get(operation)?.source ? { source: snapshotSource(this.analysis.byOperation.get(operation).source) } : {}
|
|
1241
|
+
});
|
|
1242
|
+
this.positions.push(position);
|
|
1243
|
+
return position;
|
|
1244
|
+
}
|
|
1245
|
+
suspension(position, delegated = false) {
|
|
1246
|
+
if (!position) return;
|
|
1247
|
+
this.resume.push(Object.freeze({
|
|
1248
|
+
...position,
|
|
1249
|
+
line: this.lines.length + 1,
|
|
1250
|
+
column: this.indent * 2 + 1,
|
|
1251
|
+
id: this.resume.length,
|
|
1252
|
+
delegated
|
|
1253
|
+
}));
|
|
1254
|
+
}
|
|
1255
|
+
region(region, path, output) {
|
|
1256
|
+
region.operations.forEach((operation, index) => {
|
|
1257
|
+
this.operation(operation, [
|
|
1258
|
+
...path,
|
|
1259
|
+
"operations",
|
|
1260
|
+
index
|
|
1261
|
+
], output);
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
valueRegion(region, slot, path) {
|
|
1265
|
+
if (this.captureMetadata && !slot.startsWith("v")) {
|
|
1266
|
+
const key = JSON.stringify([...path, "body"]);
|
|
1267
|
+
const owner = this.analysis.nodes.find((node) => [...node.children.values()].some((child) => JSON.stringify(child.path) === key));
|
|
1268
|
+
const nested = [...owner.children.values()].find((child) => JSON.stringify(child.path) === key);
|
|
1269
|
+
this.temporaries.push({
|
|
1270
|
+
key: `region:${nested.id}`,
|
|
1271
|
+
name: slot,
|
|
1272
|
+
region: owner.region,
|
|
1273
|
+
definition: owner.position,
|
|
1274
|
+
type: region.type
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
const label = `valueRegion${this.serial++}`;
|
|
1278
|
+
this.block(`${label}:`, () => this.region(region.body, [...path, "body"], {
|
|
1279
|
+
slot,
|
|
1280
|
+
label
|
|
1281
|
+
}));
|
|
1282
|
+
}
|
|
1283
|
+
equality(left, right) {
|
|
1284
|
+
let type = this.types.get(left);
|
|
1285
|
+
if (type?.kind === "optional") type = type.inner;
|
|
1286
|
+
if (type && (type.kind === "number" || type.kind === "string" || type.kind === "bool")) return `(${value(left)} === ${value(right)})`;
|
|
1287
|
+
return `H.equalValues(${value(left)}, ${value(right)})`;
|
|
1288
|
+
}
|
|
1289
|
+
operation(operation, path, output) {
|
|
1290
|
+
if ("result" in operation) this.types.set(operation.result, operation.type);
|
|
1291
|
+
const node = this.analysis.byOperation.get(operation);
|
|
1292
|
+
if (this.analysis.eliminated.has(node.position)) return;
|
|
1293
|
+
const position = this.captureMetadata ? this.position(operation, path) : void 0;
|
|
1294
|
+
try {
|
|
1295
|
+
if ("result" in operation && this.analysis.folded.has(node.position)) this.line(`const ${value(operation.result)} = ${this.capture(this.analysis.folded.get(node.position))};`);
|
|
1296
|
+
else this.emitOperation(operation, path, position, output);
|
|
1297
|
+
} finally {
|
|
1298
|
+
if (position) this.ranges.set(node.position, Object.freeze({
|
|
1299
|
+
start: Object.freeze({
|
|
1300
|
+
line: position.line,
|
|
1301
|
+
column: position.column
|
|
1302
|
+
}),
|
|
1303
|
+
end: Object.freeze({
|
|
1304
|
+
line: this.lines.length,
|
|
1305
|
+
column: this.lines[this.lines.length - 1].length + 1
|
|
1306
|
+
})
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
emitOperation(operation, path, position, output) {
|
|
1311
|
+
const assign = (expression) => {
|
|
1312
|
+
if (!("result" in operation)) throw new TypeError("Expected a result-producing operation");
|
|
1313
|
+
this.line(`const ${value(operation.result)} = ${expression};`);
|
|
1314
|
+
};
|
|
1315
|
+
const child = (region, key) => this.region(region, [...path, key], output);
|
|
1316
|
+
switch (operation.kind) {
|
|
1317
|
+
case "constant": {
|
|
1318
|
+
const captured = this.capture(this.constant(operation.constant));
|
|
1319
|
+
assign(operation.constant.kind === "bytes" ? `H.createBytes(${captured})` : captured);
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
case "read":
|
|
1323
|
+
assign(`host.read(${this.capture(operation.resource)}, ${this.capture(operation.name)})`);
|
|
1324
|
+
break;
|
|
1325
|
+
case "parameter":
|
|
1326
|
+
assign(`args[${operation.index}]`);
|
|
1327
|
+
break;
|
|
1328
|
+
case "reactionValue":
|
|
1329
|
+
assign(`H.reactionValue(reaction, ${quote(operation.binding)})`);
|
|
1330
|
+
break;
|
|
1331
|
+
case "reactionCapture":
|
|
1332
|
+
assign(`H.reactionCapture(reaction, ${operation.index})`);
|
|
1333
|
+
break;
|
|
1334
|
+
case "unary": {
|
|
1335
|
+
const operator = {
|
|
1336
|
+
positive: "+",
|
|
1337
|
+
negate: "-",
|
|
1338
|
+
not: "!",
|
|
1339
|
+
bitNot: "~"
|
|
1340
|
+
}[operation.operator];
|
|
1341
|
+
if (!operator) throw new TypeError("Unknown unary operator");
|
|
1342
|
+
assign(`(${operator}${value(operation.value)})`);
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case "binary":
|
|
1346
|
+
if (operation.operator === "equal" || operation.operator === "notEqual") assign(`${operation.operator === "notEqual" ? "!" : ""}${this.equality(operation.left, operation.right)}`);
|
|
1347
|
+
else {
|
|
1348
|
+
const operator = binaryOperators[operation.operator];
|
|
1349
|
+
if (!operator) throw new TypeError("Unknown binary operator");
|
|
1350
|
+
assign(`(${value(operation.left)} ${operator} ${value(operation.right)})`);
|
|
1351
|
+
}
|
|
1352
|
+
break;
|
|
1353
|
+
case "numeric":
|
|
1354
|
+
if (!numericBuiltins.has(operation.builtin)) throw new TypeError("Unknown numeric builtin");
|
|
1355
|
+
assign(`${numberBuiltins.has(operation.builtin) ? "Number" : "Math"}.${operation.builtin}(${operation.args.map(value).join(", ")})`);
|
|
1356
|
+
break;
|
|
1357
|
+
case "record":
|
|
1358
|
+
assign(`H.createRecord([${operation.fields.map((field) => `[${this.capture(field.name)}, ${value(field.value)}]`).join(", ")}])`);
|
|
1359
|
+
break;
|
|
1360
|
+
case "field":
|
|
1361
|
+
assign(`${value(operation.value)}[${this.capture(operation.field)}]`);
|
|
1362
|
+
break;
|
|
1363
|
+
case "bytes":
|
|
1364
|
+
assign(`H.createBytes([${operation.elements.map(value).join(", ")}])`);
|
|
1365
|
+
break;
|
|
1366
|
+
case "length":
|
|
1367
|
+
assign(`${value(operation.value)}.length`);
|
|
1368
|
+
break;
|
|
1369
|
+
case "index":
|
|
1370
|
+
assign(`H.readIndex(${value(operation.value)}, ${value(operation.index)})`);
|
|
1371
|
+
break;
|
|
1372
|
+
case "optional":
|
|
1373
|
+
assign(value(operation.value));
|
|
1374
|
+
break;
|
|
1375
|
+
case "isNone":
|
|
1376
|
+
assign(`(${value(operation.value)} === null)`);
|
|
1377
|
+
break;
|
|
1378
|
+
case "nonNull":
|
|
1379
|
+
assign(`H.nonNull(${value(operation.value)})`);
|
|
1380
|
+
break;
|
|
1381
|
+
case "localRead":
|
|
1382
|
+
assign(local(operation.local));
|
|
1383
|
+
break;
|
|
1384
|
+
case "local":
|
|
1385
|
+
this.line(`let ${local(operation.local)} = ${value(operation.initial)};`);
|
|
1386
|
+
break;
|
|
1387
|
+
case "localWrite":
|
|
1388
|
+
this.line(`${local(operation.local)} = ${value(operation.value)};`);
|
|
1389
|
+
break;
|
|
1390
|
+
case "write":
|
|
1391
|
+
this.line(`host.write(${this.capture(operation.name)}, ${value(operation.value)});`);
|
|
1392
|
+
break;
|
|
1393
|
+
case "memoryLength":
|
|
1394
|
+
assign(`host.memoryLength(${this.ref(operation.memory)})`);
|
|
1395
|
+
break;
|
|
1396
|
+
case "load8":
|
|
1397
|
+
case "load16le":
|
|
1398
|
+
case "load16be":
|
|
1399
|
+
case "load32le":
|
|
1400
|
+
case "load32be":
|
|
1401
|
+
assign(`host.load(${quote(operation.kind)}, ${this.ref(operation.memory)}, ${value(operation.offset)})`);
|
|
1402
|
+
break;
|
|
1403
|
+
case "store8":
|
|
1404
|
+
case "store16le":
|
|
1405
|
+
case "store16be":
|
|
1406
|
+
case "store32le":
|
|
1407
|
+
case "store32be":
|
|
1408
|
+
this.line(`host.store(${quote(operation.kind)}, ${this.ref(operation.memory)}, ${value(operation.offset)}, ${value(operation.value)});`);
|
|
1409
|
+
break;
|
|
1410
|
+
case "copyBytes":
|
|
1411
|
+
assign(`host.copyBytes(${this.ref(operation.memory)}, ${value(operation.offset)}, ${value(operation.length)})`);
|
|
1412
|
+
break;
|
|
1413
|
+
case "readSample":
|
|
1414
|
+
assign(`host.sample(${this.ref(operation.pin)})`);
|
|
1415
|
+
break;
|
|
1416
|
+
case "resistor":
|
|
1417
|
+
assign(`host.resistor(${this.ref(operation.to)}, ${value(operation.resistance)})`);
|
|
1418
|
+
break;
|
|
1419
|
+
case "combineIntents":
|
|
1420
|
+
assign(`H.combineIntents([${operation.values.map(value).join(", ")}])`);
|
|
1421
|
+
break;
|
|
1422
|
+
case "refEqual":
|
|
1423
|
+
assign(`(${this.ref(operation.left)} === ${this.ref(operation.right)})`);
|
|
1424
|
+
break;
|
|
1425
|
+
case "writeIntent":
|
|
1426
|
+
this.line(`host.intent(${this.ref(operation.pin)}, ${value(operation.value)});`);
|
|
1427
|
+
break;
|
|
1428
|
+
case "call":
|
|
1429
|
+
case "callValue": {
|
|
1430
|
+
this.suspension(position, true);
|
|
1431
|
+
const slot = `call${this.serial++}`;
|
|
1432
|
+
this.line(`const ${slot} = yield* H.delegate(host.call(${this.capture(operation.name)}, [${operation.args.map((argument) => this.argument(argument)).join(", ")}]));`);
|
|
1433
|
+
this.line(`if (${slot} === H.terminated) return;`);
|
|
1434
|
+
if (operation.kind === "callValue") assign(slot);
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
case "send":
|
|
1438
|
+
this.line(`host.send(${this.capture(operation.channel)}, ${value(operation.value)});`);
|
|
1439
|
+
break;
|
|
1440
|
+
case "notify":
|
|
1441
|
+
this.line(`host.notify(${this.capture(operation.notification)});`);
|
|
1442
|
+
break;
|
|
1443
|
+
case "receive":
|
|
1444
|
+
this.suspension(position);
|
|
1445
|
+
assign(`yield { kind: "receive", channel: ${this.capture(operation.channel)} }`);
|
|
1446
|
+
break;
|
|
1447
|
+
case "now":
|
|
1448
|
+
assign("host.now()");
|
|
1449
|
+
break;
|
|
1450
|
+
case "random":
|
|
1451
|
+
assign("host.random()");
|
|
1452
|
+
break;
|
|
1453
|
+
case "if":
|
|
1454
|
+
this.block(`if (H.checkBoolean(${value(operation.condition)}))`, () => child(operation.yes, "yes"));
|
|
1455
|
+
if (operation.no) this.block("else", () => child(operation.no, "no"));
|
|
1456
|
+
break;
|
|
1457
|
+
case "match":
|
|
1458
|
+
operation.arms.forEach((arm, index) => {
|
|
1459
|
+
const pattern = arm.pattern;
|
|
1460
|
+
const constant = pattern.kind === "none" ? null : pattern.kind === "enum" ? createEnum(pattern.name, pattern.member) : pattern.value;
|
|
1461
|
+
this.block(`${index ? "else if" : "if"} (H.equalValues(${value(operation.value)}, ${this.capture(constant)}))`, () => {
|
|
1462
|
+
this.region(arm.body, [
|
|
1463
|
+
...path,
|
|
1464
|
+
"arms",
|
|
1465
|
+
index,
|
|
1466
|
+
"body"
|
|
1467
|
+
], output);
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
if (operation.otherwise) this.block(operation.arms.length ? "else" : "", () => child(operation.otherwise, "otherwise"));
|
|
1471
|
+
break;
|
|
1472
|
+
case "while":
|
|
1473
|
+
this.block("while (true)", () => {
|
|
1474
|
+
const slot = `condition${this.serial++}`;
|
|
1475
|
+
this.line(`let ${slot};`);
|
|
1476
|
+
this.valueRegion(operation.test, slot, [...path, "test"]);
|
|
1477
|
+
this.line(`if (!H.checkBoolean(${slot})) break;`);
|
|
1478
|
+
this.block("", () => child(operation.body, "body"));
|
|
1479
|
+
});
|
|
1480
|
+
break;
|
|
1481
|
+
case "loop":
|
|
1482
|
+
this.block("while (true)", () => child(operation.body, "body"));
|
|
1483
|
+
break;
|
|
1484
|
+
case "and":
|
|
1485
|
+
case "or": {
|
|
1486
|
+
const slot = value(operation.result);
|
|
1487
|
+
this.line(`let ${slot} = H.checkBoolean(${value(operation.left)});`);
|
|
1488
|
+
this.block(`if (${operation.kind === "or" ? "!" : ""}${slot})`, () => {
|
|
1489
|
+
this.valueRegion(operation.right, slot, [...path, "right"]);
|
|
1490
|
+
this.line(`${slot} = H.checkBoolean(${slot});`);
|
|
1491
|
+
});
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
case "select": {
|
|
1495
|
+
const slot = value(operation.result);
|
|
1496
|
+
this.line(`let ${slot};`);
|
|
1497
|
+
this.block(`if (H.checkBoolean(${value(operation.condition)}))`, () => this.valueRegion(operation.yes, slot, [...path, "yes"]));
|
|
1498
|
+
this.block("else", () => this.valueRegion(operation.no, slot, [...path, "no"]));
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
case "assert":
|
|
1502
|
+
this.block(`if (!H.checkBoolean(${value(operation.condition)}))`, () => {
|
|
1503
|
+
if (operation.message) {
|
|
1504
|
+
const slot = `message${this.serial++}`;
|
|
1505
|
+
this.line(`let ${slot};`);
|
|
1506
|
+
this.valueRegion(operation.message, slot, [...path, "message"]);
|
|
1507
|
+
this.line(`host.assert(false, ${slot});`);
|
|
1508
|
+
} else this.line("host.assert(false, undefined);");
|
|
1509
|
+
});
|
|
1510
|
+
break;
|
|
1511
|
+
case "wait":
|
|
1512
|
+
case "elapse": {
|
|
1513
|
+
const duration = `duration${this.serial++}`;
|
|
1514
|
+
this.line(`const ${duration} = H.checkDuration(${value(operation.duration)});`);
|
|
1515
|
+
this.suspension(position);
|
|
1516
|
+
const signal = `yield { kind: ${quote(operation.kind === "wait" ? "ticks" : "elapse")}, duration: ${duration} };`;
|
|
1517
|
+
if (operation.kind === "elapse") this.line(`if (${duration} !== 0) ${signal}`);
|
|
1518
|
+
else this.line(signal);
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
case "waitUntil":
|
|
1522
|
+
this.suspension(position);
|
|
1523
|
+
this.line(`yield { kind: "waitUntil", predicate: ${this.capture(operation.predicate)} };`);
|
|
1524
|
+
this.line("return;");
|
|
1525
|
+
break;
|
|
1526
|
+
case "redirect":
|
|
1527
|
+
this.suspension(position);
|
|
1528
|
+
this.line(`yield { kind: "redirect", pc: H.checkPc(${value(operation.target)}), delay: H.checkDuration(${value(operation.delay)}) };`);
|
|
1529
|
+
this.line("return;");
|
|
1530
|
+
break;
|
|
1531
|
+
case "stop":
|
|
1532
|
+
this.suspension(position);
|
|
1533
|
+
this.line("yield { kind: \"stop\" };");
|
|
1534
|
+
this.line("return;");
|
|
1535
|
+
break;
|
|
1536
|
+
case "return":
|
|
1537
|
+
this.line("return;");
|
|
1538
|
+
break;
|
|
1539
|
+
case "returnValue":
|
|
1540
|
+
this.line(`return ${value(operation.value)};`);
|
|
1541
|
+
break;
|
|
1542
|
+
case "break":
|
|
1543
|
+
this.line("break;");
|
|
1544
|
+
break;
|
|
1545
|
+
case "continue":
|
|
1546
|
+
this.line("continue;");
|
|
1547
|
+
break;
|
|
1548
|
+
case "yield":
|
|
1549
|
+
if (!output) throw new TypeError("Value-region yield is outside a value region");
|
|
1550
|
+
this.line(`${output.slot} = ${value(operation.value)};`);
|
|
1551
|
+
this.line(`break ${output.label};`);
|
|
1552
|
+
break;
|
|
1553
|
+
default: exhaustive(operation);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
function exhaustive(value) {
|
|
1558
|
+
throw new TypeError(`Unsupported IR operation: ${String(value.kind)}`);
|
|
1559
|
+
}
|
|
1560
|
+
//#endregion
|
|
1561
|
+
//#region src/direct-jit.ts
|
|
1562
|
+
var binary = {
|
|
1563
|
+
add: "+",
|
|
1564
|
+
subtract: "-",
|
|
1565
|
+
multiply: "*",
|
|
1566
|
+
divide: "/",
|
|
1567
|
+
remainder: "%",
|
|
1568
|
+
power: "**",
|
|
1569
|
+
lessThan: "<",
|
|
1570
|
+
lessThanOrEqual: "<=",
|
|
1571
|
+
greaterThan: ">",
|
|
1572
|
+
greaterThanOrEqual: ">=",
|
|
1573
|
+
equal: "===",
|
|
1574
|
+
notEqual: "!==",
|
|
1575
|
+
bitAnd: "&",
|
|
1576
|
+
bitOr: "|",
|
|
1577
|
+
bitXor: "^",
|
|
1578
|
+
shiftLeft: "<<",
|
|
1579
|
+
shiftRight: ">>",
|
|
1580
|
+
shiftRightUnsigned: ">>>"
|
|
1581
|
+
};
|
|
1582
|
+
var numeric = /* @__PURE__ */ new Set([
|
|
1583
|
+
"isNaN",
|
|
1584
|
+
"isFinite",
|
|
1585
|
+
"isInteger",
|
|
1586
|
+
"isSafeInteger",
|
|
1587
|
+
"abs",
|
|
1588
|
+
"floor",
|
|
1589
|
+
"ceil",
|
|
1590
|
+
"trunc",
|
|
1591
|
+
"round",
|
|
1592
|
+
"sqrt",
|
|
1593
|
+
"min",
|
|
1594
|
+
"max",
|
|
1595
|
+
"pow"
|
|
1596
|
+
]);
|
|
1597
|
+
var predicates = /* @__PURE__ */ new Set([
|
|
1598
|
+
"isNaN",
|
|
1599
|
+
"isFinite",
|
|
1600
|
+
"isInteger",
|
|
1601
|
+
"isSafeInteger"
|
|
1602
|
+
]);
|
|
1603
|
+
function offset(memory, value) {
|
|
1604
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new TypeError("offset must be a safe integer");
|
|
1605
|
+
if (value < 0) throw new RangeError("offset must be nonnegative");
|
|
1606
|
+
if (value >= memory.length) throw new RangeError("Memory range is out of bounds");
|
|
1607
|
+
return value;
|
|
1608
|
+
}
|
|
1609
|
+
function byte(value) {
|
|
1610
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new TypeError("stored value must be a safe integer");
|
|
1611
|
+
return value;
|
|
1612
|
+
}
|
|
1613
|
+
var helpers = Object.freeze({
|
|
1614
|
+
checkBoolean,
|
|
1615
|
+
checkPc,
|
|
1616
|
+
offset,
|
|
1617
|
+
byte
|
|
1618
|
+
});
|
|
1619
|
+
var unsupported = Symbol("unsupported direct instruction");
|
|
1620
|
+
/** Inputs have already passed model validation and contextual binding. */
|
|
1621
|
+
function compileDirectJit(instructions, binding) {
|
|
1622
|
+
if (!validIndex(binding.pc.index) || binding.states.get(binding.pc.name) !== binding.pc.index) return void 0;
|
|
1623
|
+
const constants = [];
|
|
1624
|
+
const capture = (value) => `C[${constants.push(value) - 1}]`;
|
|
1625
|
+
const entries = [];
|
|
1626
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1627
|
+
for (const instruction of instructions) {
|
|
1628
|
+
if (!Number.isFinite(instruction.pc) || seen.has(instruction.pc)) continue;
|
|
1629
|
+
seen.add(instruction.pc);
|
|
1630
|
+
if (!Number.isFinite(instruction.nextPc)) continue;
|
|
1631
|
+
try {
|
|
1632
|
+
entries.push(new Instruction(binding, capture).compile(instruction));
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
if (error !== unsupported) throw error;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (!entries.length) return void 0;
|
|
1638
|
+
const localCount = Math.max(...entries.map((entry) => entry.slots.length));
|
|
1639
|
+
const registers = Array.from({ length: localCount }, (_, index) => `v${index}`);
|
|
1640
|
+
const lines = [
|
|
1641
|
+
"function run(F, deadline) {",
|
|
1642
|
+
"const S = F.states, M = F.memories, L = F.locals, SD = F.stateDirty, MD = F.memoryDirty;",
|
|
1643
|
+
...registers.length ? [`let ${registers.join(",")};`] : [],
|
|
1644
|
+
"while (true) {",
|
|
1645
|
+
"if (F.time >= deadline) return \"deadline\";",
|
|
1646
|
+
"if (F.resume !== 0) {",
|
|
1647
|
+
"if (F.due >= deadline) { F.time = deadline; return \"deadline\"; }",
|
|
1648
|
+
"F.time = Math.max(F.time, F.due);",
|
|
1649
|
+
"switch (F.resume) {"
|
|
1650
|
+
];
|
|
1651
|
+
entries.forEach((entry, index) => {
|
|
1652
|
+
lines.push(`case ${index + 1}: {`);
|
|
1653
|
+
entry.slots.forEach((slot, slotIndex) => lines.push(`${slot} = L[${slotIndex}];`));
|
|
1654
|
+
lines.push("F.resume = 0;", ...entry.suffix, finish(entry.stop), "}");
|
|
1655
|
+
});
|
|
1656
|
+
lines.push("default: throw new TypeError(\"Unknown direct JIT continuation\");", "}", "}", "if (F.stopAfterInstruction) return \"invalidated\";", `switch (H.checkPc(S[${binding.pc.index}])) {`);
|
|
1657
|
+
entries.forEach((entry, index) => {
|
|
1658
|
+
lines.push(`case ${entry.pc}: {`, `S[${binding.pc.index}] = ${entry.nextPc};`, dirty("SD", binding.pc.index), ...entry.prefix);
|
|
1659
|
+
if (entry.duration !== void 0) {
|
|
1660
|
+
lines.push(`F.due = F.time + ${entry.duration};`, "if (F.due > 7200000000000000) throw new RangeError(\"Time out of range\");", "if (F.due >= deadline) {");
|
|
1661
|
+
entry.slots.forEach((slot, slotIndex) => lines.push(`L[${slotIndex}] = ${slot};`));
|
|
1662
|
+
lines.push(`F.resume = ${index + 1};`, "F.time = deadline;", "return \"deadline\";", "}");
|
|
1663
|
+
lines.push("F.time = F.due;");
|
|
1664
|
+
}
|
|
1665
|
+
lines.push(...entry.suffix, finish(entry.stop), "}");
|
|
1666
|
+
});
|
|
1667
|
+
lines.push("default: return \"fallback\";", "}", "}", "}");
|
|
1668
|
+
const factory = new Function("C", "H", `"use strict"; return (${lines.join("\n")});`);
|
|
1669
|
+
const names = (field) => Object.freeze([...new Set(entries.flatMap((entry) => [...entry[field]]))]);
|
|
1670
|
+
return Object.freeze({
|
|
1671
|
+
run: factory(Object.freeze(constants), helpers),
|
|
1672
|
+
localCount,
|
|
1673
|
+
readStates: names("readStates"),
|
|
1674
|
+
writeStates: names("writeStates"),
|
|
1675
|
+
readMemories: names("readMemories"),
|
|
1676
|
+
writeMemories: names("writeMemories")
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
function finish(stop) {
|
|
1680
|
+
return stop ? "return \"stopped\";" : "if (F.stopAfterInstruction) return \"invalidated\"; continue;";
|
|
1681
|
+
}
|
|
1682
|
+
function dirty(bank, index) {
|
|
1683
|
+
return `${bank}[${index >>> 5}] |= ${1 << (index & 31)};`;
|
|
1684
|
+
}
|
|
1685
|
+
function validIndex(value) {
|
|
1686
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1687
|
+
}
|
|
1688
|
+
var Instruction = class {
|
|
1689
|
+
binding;
|
|
1690
|
+
capture;
|
|
1691
|
+
readStates = /* @__PURE__ */ new Set();
|
|
1692
|
+
writeStates = /* @__PURE__ */ new Set();
|
|
1693
|
+
readMemories = /* @__PURE__ */ new Set();
|
|
1694
|
+
writeMemories = /* @__PURE__ */ new Set();
|
|
1695
|
+
slots = [];
|
|
1696
|
+
prefix = [];
|
|
1697
|
+
suffix = [];
|
|
1698
|
+
pc = "";
|
|
1699
|
+
nextPc = "";
|
|
1700
|
+
duration;
|
|
1701
|
+
stop = false;
|
|
1702
|
+
values = /* @__PURE__ */ new Map();
|
|
1703
|
+
locals = /* @__PURE__ */ new Map();
|
|
1704
|
+
literals = /* @__PURE__ */ new Map();
|
|
1705
|
+
labels = 0;
|
|
1706
|
+
constructor(binding, capture) {
|
|
1707
|
+
this.binding = binding;
|
|
1708
|
+
this.capture = capture;
|
|
1709
|
+
}
|
|
1710
|
+
compile(instruction) {
|
|
1711
|
+
this.pc = this.capture(instruction.pc);
|
|
1712
|
+
this.nextPc = this.capture(instruction.nextPc);
|
|
1713
|
+
this.readStates.add(this.binding.pc.name);
|
|
1714
|
+
this.state(this.binding.pc.name, true);
|
|
1715
|
+
let output = this.prefix;
|
|
1716
|
+
let terminal = false;
|
|
1717
|
+
for (const operation of instruction.body.operations) {
|
|
1718
|
+
if (terminal) throw unsupported;
|
|
1719
|
+
if (operation.kind === "elapse") {
|
|
1720
|
+
if (this.duration !== void 0) throw unsupported;
|
|
1721
|
+
this.duration = this.delay(operation.duration);
|
|
1722
|
+
output = this.suffix;
|
|
1723
|
+
} else if (operation.kind === "redirect") {
|
|
1724
|
+
if (this.duration !== void 0) throw unsupported;
|
|
1725
|
+
const duration = this.delay(operation.delay);
|
|
1726
|
+
output.push(`S[${this.binding.pc.index}] = H.checkPc(${this.value(operation.target)});`, dirty("SD", this.binding.pc.index));
|
|
1727
|
+
this.duration = duration;
|
|
1728
|
+
output = this.suffix;
|
|
1729
|
+
terminal = true;
|
|
1730
|
+
} else if (operation.kind === "stop") {
|
|
1731
|
+
this.stop = true;
|
|
1732
|
+
terminal = true;
|
|
1733
|
+
} else this.operation(operation, output);
|
|
1734
|
+
}
|
|
1735
|
+
if (this.duration === void 0 && !this.stop) throw unsupported;
|
|
1736
|
+
return this;
|
|
1737
|
+
}
|
|
1738
|
+
delay(id) {
|
|
1739
|
+
const duration = this.literals.get(id);
|
|
1740
|
+
if (typeof duration !== "number") throw unsupported;
|
|
1741
|
+
try {
|
|
1742
|
+
checkDuration(duration);
|
|
1743
|
+
} catch {
|
|
1744
|
+
throw unsupported;
|
|
1745
|
+
}
|
|
1746
|
+
if (duration <= 0) throw unsupported;
|
|
1747
|
+
return this.capture(duration);
|
|
1748
|
+
}
|
|
1749
|
+
value(id) {
|
|
1750
|
+
const slot = this.values.get(id);
|
|
1751
|
+
if (!slot) throw unsupported;
|
|
1752
|
+
return slot;
|
|
1753
|
+
}
|
|
1754
|
+
slot() {
|
|
1755
|
+
const slot = `v${this.slots.length}`;
|
|
1756
|
+
this.slots.push(slot);
|
|
1757
|
+
return slot;
|
|
1758
|
+
}
|
|
1759
|
+
state(name, write) {
|
|
1760
|
+
const index = this.binding.states.get(name);
|
|
1761
|
+
if (!validIndex(index) || write && this.binding.blockedStates?.has(name)) throw unsupported;
|
|
1762
|
+
(write ? this.writeStates : this.readStates).add(name);
|
|
1763
|
+
return index;
|
|
1764
|
+
}
|
|
1765
|
+
memory(operation) {
|
|
1766
|
+
if (operation.memory.kind !== "memory") throw unsupported;
|
|
1767
|
+
const name = operation.memory.name;
|
|
1768
|
+
const index = this.binding.memories.get(name);
|
|
1769
|
+
if (!validIndex(index) || operation.kind !== "load8" && this.binding.blockedMemories?.has(name)) throw unsupported;
|
|
1770
|
+
(operation.kind === "load8" ? this.readMemories : this.writeMemories).add(name);
|
|
1771
|
+
return index;
|
|
1772
|
+
}
|
|
1773
|
+
region(region, lines, output) {
|
|
1774
|
+
for (const operation of region.operations) if (operation.kind === "yield") {
|
|
1775
|
+
if (!output) throw unsupported;
|
|
1776
|
+
lines.push(`${output.slot} = ${this.value(operation.value)}; break ${output.label};`);
|
|
1777
|
+
} else this.operation(operation, lines, output);
|
|
1778
|
+
}
|
|
1779
|
+
operation(operation, lines, output) {
|
|
1780
|
+
let result;
|
|
1781
|
+
if ("result" in operation) {
|
|
1782
|
+
if (operation.type.kind !== "number" && operation.type.kind !== "bool") throw unsupported;
|
|
1783
|
+
result = this.slot();
|
|
1784
|
+
this.values.set(operation.result, result);
|
|
1785
|
+
}
|
|
1786
|
+
const assign = (expression) => {
|
|
1787
|
+
lines.push(`${result} = ${expression};`);
|
|
1788
|
+
};
|
|
1789
|
+
switch (operation.kind) {
|
|
1790
|
+
case "constant":
|
|
1791
|
+
if (operation.constant.kind !== "number" && operation.constant.kind !== "bool") throw unsupported;
|
|
1792
|
+
this.literals.set(operation.result, operation.constant.value);
|
|
1793
|
+
assign(this.capture(operation.constant.value));
|
|
1794
|
+
break;
|
|
1795
|
+
case "read":
|
|
1796
|
+
if (operation.resource !== "state") throw unsupported;
|
|
1797
|
+
assign(`S[${this.state(operation.name, false)}]`);
|
|
1798
|
+
break;
|
|
1799
|
+
case "write": {
|
|
1800
|
+
const index = this.state(operation.name, true);
|
|
1801
|
+
lines.push(`S[${index}] = ${this.value(operation.value)};`, dirty("SD", index));
|
|
1802
|
+
break;
|
|
1803
|
+
}
|
|
1804
|
+
case "unary": {
|
|
1805
|
+
const operator = {
|
|
1806
|
+
positive: "+",
|
|
1807
|
+
negate: "-",
|
|
1808
|
+
not: "!",
|
|
1809
|
+
bitNot: "~"
|
|
1810
|
+
}[operation.operator];
|
|
1811
|
+
if (!operator) throw unsupported;
|
|
1812
|
+
assign(`(${operator}${this.value(operation.value)})`);
|
|
1813
|
+
break;
|
|
1814
|
+
}
|
|
1815
|
+
case "binary": {
|
|
1816
|
+
const operator = binary[operation.operator];
|
|
1817
|
+
if (!operator) throw unsupported;
|
|
1818
|
+
assign(`(${this.value(operation.left)} ${operator} ${this.value(operation.right)})`);
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
case "numeric":
|
|
1822
|
+
if (!numeric.has(operation.builtin)) throw unsupported;
|
|
1823
|
+
assign(`${predicates.has(operation.builtin) ? "Number" : "Math"}.${operation.builtin}(${operation.args.map((id) => this.value(id)).join(",")})`);
|
|
1824
|
+
break;
|
|
1825
|
+
case "local": {
|
|
1826
|
+
if (operation.type.kind !== "number" && operation.type.kind !== "bool") throw unsupported;
|
|
1827
|
+
const slot = this.slot();
|
|
1828
|
+
this.locals.set(operation.local, slot);
|
|
1829
|
+
lines.push(`${slot} = ${this.value(operation.initial)};`);
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
case "localRead": {
|
|
1833
|
+
const slot = this.locals.get(operation.local);
|
|
1834
|
+
if (!slot) throw unsupported;
|
|
1835
|
+
assign(slot);
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
case "localWrite": {
|
|
1839
|
+
const slot = this.locals.get(operation.local);
|
|
1840
|
+
if (!slot) throw unsupported;
|
|
1841
|
+
lines.push(`${slot} = ${this.value(operation.value)};`);
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
case "load8": {
|
|
1845
|
+
const memory = `M[${this.memory(operation)}]`;
|
|
1846
|
+
assign(`${memory}[H.offset(${memory},${this.value(operation.offset)})]`);
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "store8": {
|
|
1850
|
+
const index = this.memory(operation);
|
|
1851
|
+
const memory = `M[${index}]`;
|
|
1852
|
+
lines.push(`${memory}[H.offset(${memory},${this.value(operation.offset)})] = H.byte(${this.value(operation.value)});`, dirty("MD", index));
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
case "if":
|
|
1856
|
+
lines.push(`if (H.checkBoolean(${this.value(operation.condition)})) {`);
|
|
1857
|
+
this.region(operation.yes, lines, output);
|
|
1858
|
+
if (operation.no) {
|
|
1859
|
+
lines.push("} else {");
|
|
1860
|
+
this.region(operation.no, lines, output);
|
|
1861
|
+
}
|
|
1862
|
+
lines.push("}");
|
|
1863
|
+
break;
|
|
1864
|
+
case "select": {
|
|
1865
|
+
const label = `select${this.labels++}`;
|
|
1866
|
+
lines.push(`${label}: {`, `if (H.checkBoolean(${this.value(operation.condition)})) {`);
|
|
1867
|
+
this.region(operation.yes.body, lines, {
|
|
1868
|
+
slot: result,
|
|
1869
|
+
label
|
|
1870
|
+
});
|
|
1871
|
+
lines.push("} else {");
|
|
1872
|
+
this.region(operation.no.body, lines, {
|
|
1873
|
+
slot: result,
|
|
1874
|
+
label
|
|
1875
|
+
});
|
|
1876
|
+
lines.push("}", "}");
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1879
|
+
default: throw unsupported;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
//#endregion
|
|
1884
|
+
export { checkBoolean, checkDuration, checkPc, combineIntents, compileBody, compileBodyArtifact, compileComputation, compileComputationArtifact, compileDirectJit, createBytes, createEnum, createRecord, equalValues, nonNull, reactionCapture, reactionValue, readIndex };
|
|
1885
|
+
|
|
1886
|
+
//# sourceMappingURL=index.js.map
|