main
mjs 1,193 lines 40.1 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 /**
9 * Debug HIR printer for the Rust port testing infrastructure.
10 *
11 * Custom printer that walks the HIRFunction structure and prints every field
12 * of every identifier, instruction, terminal, and block. Also includes
13 * outlined functions (from FunctionExpression instruction values).
14 *
15 * This does NOT delegate to printFunctionWithOutlined() — it is a standalone
16 * walker that produces a detailed, deterministic representation suitable for
17 * cross-compiler comparison between the TS and Rust implementations.
18 *
19 * @param {Function} _printFunctionWithOutlined - Unused (kept for API compat)
20 * @param {object} hirFunction - The HIRFunction to print
21 * @returns {string} The debug representation
22 */
23 export function debugPrintHIR(_printFunctionWithOutlined, hirFunction) {
24 const outlined = [];
25 const result = printHIRFunction(hirFunction, 0, outlined);
26 const parts = [result];
27 for (let i = 0; i < outlined.length; i++) {
28 parts.push(printHIRFunction(outlined[i], i + 1, outlined));
29 }
30 return parts.join("\n\n");
31 }
32
33 // ---------------------------------------------------------------------------
34 // Helpers
35 // ---------------------------------------------------------------------------
36
37 function indent(depth) {
38 return " ".repeat(depth);
39 }
40
41 function formatLoc(loc) {
42 if (loc == null || typeof loc === "symbol") {
43 return "generated";
44 }
45 return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`;
46 }
47
48 function formatEffect(effect) {
49 // Effect enum values in the TS compiler are lowercase strings like
50 // "read", "mutate", "<unknown>", etc.
51 return String(effect);
52 }
53
54 function formatType(type) {
55 if (type == null) return "Type";
56 switch (type.kind) {
57 case "Type":
58 return "Type";
59 case "Primitive":
60 return "Primitive";
61 case "Object":
62 return type.shapeId != null ? `Object<${type.shapeId}>` : "Object";
63 case "Function": {
64 const ret = formatType(type.return);
65 const base =
66 type.shapeId != null ? `Function<${type.shapeId}>` : "Function";
67 return ret !== "Type" ? `${base}():${ret}` : base;
68 }
69 case "Poly":
70 return "Poly";
71 case "Phi": {
72 const ops = type.operands.map(formatType).join(", ");
73 return `Phi(${ops})`;
74 }
75 case "Property": {
76 const objType = formatType(type.objectType);
77 return `Property(${objType}.${type.objectName})`;
78 }
79 case "ObjectMethod":
80 return "ObjectMethod";
81 default:
82 return "Type";
83 }
84 }
85
86 function formatIdentifierName(name) {
87 if (name == null) return "null";
88 if (typeof name === "object" && name.value != null) {
89 return JSON.stringify(name.value);
90 }
91 return JSON.stringify(String(name));
92 }
93
94 function formatMutableRange(range) {
95 if (range == null) return "[0:0]";
96 return `[${range.start}:${range.end}]`;
97 }
98
99 function formatScopeId(scope) {
100 if (scope == null) return "null";
101 return `@${scope.id}`;
102 }
103
104 function formatDeclarationId(id) {
105 if (id == null) return "null";
106 return String(id);
107 }
108
109 // ---------------------------------------------------------------------------
110 // Place printing
111 // ---------------------------------------------------------------------------
112
113 function printPlaceInline(place, depth) {
114 const id = place.identifier;
115 return [
116 `${indent(depth)}Place {`,
117 `${indent(depth + 1)}identifier: $${id.id}`,
118 `${indent(depth + 1)}effect: ${formatEffect(place.effect)}`,
119 `${indent(depth + 1)}reactive: ${place.reactive}`,
120 `${indent(depth + 1)}loc: ${formatLoc(place.loc)}`,
121 `${indent(depth)}}`,
122 ].join("\n");
123 }
124
125 // ---------------------------------------------------------------------------
126 // Identifier printing
127 // ---------------------------------------------------------------------------
128
129 function printIdentifierEntry(identifier, depth) {
130 return [
131 `${indent(depth)}$${identifier.id}: Identifier {`,
132 `${indent(depth + 1)}id: ${identifier.id}`,
133 `${indent(depth + 1)}declarationId: ${formatDeclarationId(identifier.declarationId)}`,
134 `${indent(depth + 1)}name: ${formatIdentifierName(identifier.name)}`,
135 `${indent(depth + 1)}mutableRange: ${formatMutableRange(identifier.mutableRange)}`,
136 `${indent(depth + 1)}scope: ${formatScopeId(identifier.scope)}`,
137 `${indent(depth + 1)}type: ${formatType(identifier.type)}`,
138 `${indent(depth + 1)}loc: ${formatLoc(identifier.loc)}`,
139 `${indent(depth)}}`,
140 ].join("\n");
141 }
142
143 // ---------------------------------------------------------------------------
144 // InstructionValue printing
145 // ---------------------------------------------------------------------------
146
147 function printObjectPropertyKey(key) {
148 switch (key.kind) {
149 case "identifier":
150 return key.name;
151 case "string":
152 return `"${key.name}"`;
153 case "computed":
154 return `[$${key.name.identifier.id}]`;
155 case "number":
156 return String(key.name);
157 default:
158 return String(key.name ?? key.kind);
159 }
160 }
161
162 function printPattern(pattern) {
163 switch (pattern.kind) {
164 case "ArrayPattern":
165 return `[${pattern.items.map((item) => (item.kind === "Hole" ? "<hole>" : item.kind === "Spread" ? `...$${item.place.identifier.id}` : `$${item.identifier.id}`)).join(", ")}]`;
166 case "ObjectPattern":
167 return `{${pattern.properties.map((p) => p.kind === "Spread" ? `...$${p.place.identifier.id}` : `${printObjectPropertyKey(p.key)}: $${p.place.identifier.id}`).join(", ")}}`;
168 default:
169 return String(pattern);
170 }
171 }
172
173 function printPlaceOrSpread(ps) {
174 if (ps.kind === "Identifier") return `$${ps.identifier.id}`;
175 if (ps.kind === "Spread") return `...$${ps.place.identifier.id}`;
176 return "<hole>";
177 }
178
179 function printInstructionValueFields(value, depth) {
180 const d = depth;
181 const lines = [];
182 const kind = value.kind;
183
184 switch (kind) {
185 case "LoadLocal":
186 lines.push(`${indent(d)}LoadLocal {`);
187 lines.push(`${indent(d + 1)}place: $${value.place.identifier.id}`);
188 lines.push(`${indent(d)}}`);
189 break;
190 case "LoadContext":
191 lines.push(`${indent(d)}LoadContext {`);
192 lines.push(`${indent(d + 1)}place: $${value.place.identifier.id}`);
193 lines.push(`${indent(d)}}`);
194 break;
195 case "DeclareLocal":
196 lines.push(`${indent(d)}DeclareLocal {`);
197 lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
198 lines.push(
199 `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
200 );
201 lines.push(`${indent(d)}}`);
202 break;
203 case "DeclareContext":
204 lines.push(`${indent(d)}DeclareContext {`);
205 lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
206 lines.push(
207 `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
208 );
209 lines.push(`${indent(d)}}`);
210 break;
211 case "StoreLocal":
212 lines.push(`${indent(d)}StoreLocal {`);
213 lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
214 lines.push(
215 `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
216 );
217 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
218 lines.push(`${indent(d)}}`);
219 break;
220 case "StoreContext":
221 lines.push(`${indent(d)}StoreContext {`);
222 lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
223 lines.push(
224 `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
225 );
226 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
227 lines.push(`${indent(d)}}`);
228 break;
229 case "Destructure":
230 lines.push(`${indent(d)}Destructure {`);
231 lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
232 lines.push(
233 `${indent(d + 1)}lvalue.pattern: ${printPattern(value.lvalue.pattern)}`
234 );
235 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
236 lines.push(`${indent(d)}}`);
237 break;
238 case "Primitive":
239 lines.push(`${indent(d)}Primitive {`);
240 lines.push(
241 `${indent(d + 1)}value: ${value.value === undefined ? "undefined" : JSON.stringify(value.value)}`
242 );
243 lines.push(`${indent(d)}}`);
244 break;
245 case "JSXText":
246 lines.push(`${indent(d)}JSXText {`);
247 lines.push(`${indent(d + 1)}value: ${JSON.stringify(value.value)}`);
248 lines.push(`${indent(d)}}`);
249 break;
250 case "BinaryExpression":
251 lines.push(`${indent(d)}BinaryExpression {`);
252 lines.push(`${indent(d + 1)}operator: ${value.operator}`);
253 lines.push(`${indent(d + 1)}left: $${value.left.identifier.id}`);
254 lines.push(`${indent(d + 1)}right: $${value.right.identifier.id}`);
255 lines.push(`${indent(d)}}`);
256 break;
257 case "UnaryExpression":
258 lines.push(`${indent(d)}UnaryExpression {`);
259 lines.push(`${indent(d + 1)}operator: ${value.operator}`);
260 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
261 lines.push(`${indent(d)}}`);
262 break;
263 case "CallExpression":
264 lines.push(`${indent(d)}CallExpression {`);
265 lines.push(`${indent(d + 1)}callee: $${value.callee.identifier.id}`);
266 lines.push(
267 `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
268 );
269 lines.push(`${indent(d)}}`);
270 break;
271 case "MethodCall":
272 lines.push(`${indent(d)}MethodCall {`);
273 lines.push(
274 `${indent(d + 1)}receiver: $${value.receiver.identifier.id}`
275 );
276 lines.push(
277 `${indent(d + 1)}property: $${value.property.identifier.id}`
278 );
279 lines.push(
280 `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
281 );
282 lines.push(`${indent(d)}}`);
283 break;
284 case "NewExpression":
285 lines.push(`${indent(d)}NewExpression {`);
286 lines.push(`${indent(d + 1)}callee: $${value.callee.identifier.id}`);
287 lines.push(
288 `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
289 );
290 lines.push(`${indent(d)}}`);
291 break;
292 case "ObjectExpression":
293 lines.push(`${indent(d)}ObjectExpression {`);
294 if (value.properties != null) {
295 lines.push(`${indent(d + 1)}properties:`);
296 for (const prop of value.properties) {
297 if (prop.kind === "ObjectProperty") {
298 lines.push(
299 `${indent(d + 2)}${printObjectPropertyKey(prop.key)}: $${prop.place.identifier.id}`
300 );
301 } else {
302 lines.push(`${indent(d + 2)}...$${prop.place.identifier.id}`);
303 }
304 }
305 } else {
306 lines.push(`${indent(d + 1)}properties: null`);
307 }
308 lines.push(`${indent(d)}}`);
309 break;
310 case "ArrayExpression":
311 lines.push(`${indent(d)}ArrayExpression {`);
312 lines.push(
313 `${indent(d + 1)}elements: [${value.elements.map((e) => (e.kind === "Hole" ? "<hole>" : e.kind === "Spread" ? `...$${e.place.identifier.id}` : `$${e.identifier.id}`)).join(", ")}]`
314 );
315 lines.push(`${indent(d)}}`);
316 break;
317 case "PropertyLoad":
318 lines.push(`${indent(d)}PropertyLoad {`);
319 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
320 lines.push(`${indent(d + 1)}property: ${value.property}`);
321 lines.push(`${indent(d)}}`);
322 break;
323 case "PropertyStore":
324 lines.push(`${indent(d)}PropertyStore {`);
325 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
326 lines.push(`${indent(d + 1)}property: ${value.property}`);
327 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
328 lines.push(`${indent(d)}}`);
329 break;
330 case "PropertyDelete":
331 lines.push(`${indent(d)}PropertyDelete {`);
332 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
333 lines.push(`${indent(d + 1)}property: ${value.property}`);
334 lines.push(`${indent(d)}}`);
335 break;
336 case "ComputedLoad":
337 lines.push(`${indent(d)}ComputedLoad {`);
338 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
339 lines.push(
340 `${indent(d + 1)}property: $${value.property.identifier.id}`
341 );
342 lines.push(`${indent(d)}}`);
343 break;
344 case "ComputedStore":
345 lines.push(`${indent(d)}ComputedStore {`);
346 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
347 lines.push(
348 `${indent(d + 1)}property: $${value.property.identifier.id}`
349 );
350 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
351 lines.push(`${indent(d)}}`);
352 break;
353 case "ComputedDelete":
354 lines.push(`${indent(d)}ComputedDelete {`);
355 lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`);
356 lines.push(
357 `${indent(d + 1)}property: $${value.property.identifier.id}`
358 );
359 lines.push(`${indent(d)}}`);
360 break;
361 case "LoadGlobal": {
362 lines.push(`${indent(d)}LoadGlobal {`);
363 const b = value.binding;
364 lines.push(`${indent(d + 1)}binding.kind: ${b.kind}`);
365 lines.push(`${indent(d + 1)}binding.name: ${b.name}`);
366 if (b.module != null) {
367 lines.push(`${indent(d + 1)}binding.module: ${b.module}`);
368 }
369 if (b.imported != null) {
370 lines.push(`${indent(d + 1)}binding.imported: ${b.imported}`);
371 }
372 lines.push(`${indent(d)}}`);
373 break;
374 }
375 case "StoreGlobal":
376 lines.push(`${indent(d)}StoreGlobal {`);
377 lines.push(`${indent(d + 1)}name: ${value.name}`);
378 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
379 lines.push(`${indent(d)}}`);
380 break;
381 case "TypeCastExpression":
382 lines.push(`${indent(d)}TypeCastExpression {`);
383 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
384 lines.push(`${indent(d + 1)}type: ${formatType(value.type)}`);
385 lines.push(`${indent(d)}}`);
386 break;
387 case "JsxExpression": {
388 lines.push(`${indent(d)}JsxExpression {`);
389 if (value.tag.kind === "Identifier") {
390 lines.push(`${indent(d + 1)}tag: $${value.tag.identifier.id}`);
391 } else {
392 lines.push(`${indent(d + 1)}tag: "${value.tag.name}"`);
393 }
394 lines.push(`${indent(d + 1)}props:`);
395 for (const attr of value.props) {
396 if (attr.kind === "JsxAttribute") {
397 lines.push(
398 `${indent(d + 2)}${attr.name}: $${attr.place.identifier.id}`
399 );
400 } else {
401 lines.push(
402 `${indent(d + 2)}...$${attr.argument.identifier.id}`
403 );
404 }
405 }
406 if (value.children != null) {
407 lines.push(
408 `${indent(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]`
409 );
410 } else {
411 lines.push(`${indent(d + 1)}children: null`);
412 }
413 lines.push(`${indent(d)}}`);
414 break;
415 }
416 case "JsxFragment":
417 lines.push(`${indent(d)}JsxFragment {`);
418 lines.push(
419 `${indent(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]`
420 );
421 lines.push(`${indent(d)}}`);
422 break;
423 case "FunctionExpression":
424 case "ObjectMethod": {
425 const label =
426 kind === "FunctionExpression" ? "FunctionExpression" : "ObjectMethod";
427 lines.push(`${indent(d)}${label} {`);
428 if (kind === "FunctionExpression") {
429 lines.push(
430 `${indent(d + 1)}name: ${value.name != null ? JSON.stringify(value.name) : "null"}`
431 );
432 }
433 lines.push(
434 `${indent(d + 1)}loweredFunc.id: ${value.loweredFunc.func.id ?? "null"}`
435 );
436 // context
437 const ctx = value.loweredFunc.func.context;
438 lines.push(
439 `${indent(d + 1)}context: [${ctx.map((c) => `$${c.identifier.id}`).join(", ")}]`
440 );
441 // aliasing effects
442 const ae = value.loweredFunc.func.aliasingEffects;
443 lines.push(
444 `${indent(d + 1)}aliasingEffects: ${ae != null ? `[${ae.length} effects]` : "null"}`
445 );
446 lines.push(`${indent(d)}}`);
447 break;
448 }
449 case "TaggedTemplateExpression":
450 lines.push(`${indent(d)}TaggedTemplateExpression {`);
451 lines.push(`${indent(d + 1)}tag: $${value.tag.identifier.id}`);
452 lines.push(`${indent(d + 1)}value.raw: ${JSON.stringify(value.value.raw)}`);
453 lines.push(`${indent(d)}}`);
454 break;
455 case "TemplateLiteral":
456 lines.push(`${indent(d)}TemplateLiteral {`);
457 lines.push(
458 `${indent(d + 1)}quasis: [${value.quasis.map((q) => JSON.stringify(q.raw)).join(", ")}]`
459 );
460 lines.push(
461 `${indent(d + 1)}subexprs: [${value.subexprs.map((s) => `$${s.identifier.id}`).join(", ")}]`
462 );
463 lines.push(`${indent(d)}}`);
464 break;
465 case "RegExpLiteral":
466 lines.push(`${indent(d)}RegExpLiteral {`);
467 lines.push(`${indent(d + 1)}pattern: ${value.pattern}`);
468 lines.push(`${indent(d + 1)}flags: ${value.flags}`);
469 lines.push(`${indent(d)}}`);
470 break;
471 case "MetaProperty":
472 lines.push(`${indent(d)}MetaProperty {`);
473 lines.push(`${indent(d + 1)}meta: ${value.meta}`);
474 lines.push(`${indent(d + 1)}property: ${value.property}`);
475 lines.push(`${indent(d)}}`);
476 break;
477 case "Await":
478 lines.push(`${indent(d)}Await {`);
479 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
480 lines.push(`${indent(d)}}`);
481 break;
482 case "GetIterator":
483 lines.push(`${indent(d)}GetIterator {`);
484 lines.push(
485 `${indent(d + 1)}collection: $${value.collection.identifier.id}`
486 );
487 lines.push(`${indent(d)}}`);
488 break;
489 case "IteratorNext":
490 lines.push(`${indent(d)}IteratorNext {`);
491 lines.push(
492 `${indent(d + 1)}iterator: $${value.iterator.identifier.id}`
493 );
494 lines.push(
495 `${indent(d + 1)}collection: $${value.collection.identifier.id}`
496 );
497 lines.push(`${indent(d)}}`);
498 break;
499 case "NextPropertyOf":
500 lines.push(`${indent(d)}NextPropertyOf {`);
501 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
502 lines.push(`${indent(d)}}`);
503 break;
504 case "PostfixUpdate":
505 lines.push(`${indent(d)}PostfixUpdate {`);
506 lines.push(`${indent(d + 1)}lvalue: $${value.lvalue.identifier.id}`);
507 lines.push(`${indent(d + 1)}operation: ${value.operation}`);
508 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
509 lines.push(`${indent(d)}}`);
510 break;
511 case "PrefixUpdate":
512 lines.push(`${indent(d)}PrefixUpdate {`);
513 lines.push(`${indent(d + 1)}lvalue: $${value.lvalue.identifier.id}`);
514 lines.push(`${indent(d + 1)}operation: ${value.operation}`);
515 lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`);
516 lines.push(`${indent(d)}}`);
517 break;
518 case "Debugger":
519 lines.push(`${indent(d)}Debugger {}`);
520 break;
521 case "StartMemoize":
522 lines.push(`${indent(d)}StartMemoize {`);
523 lines.push(`${indent(d + 1)}manualMemoId: ${value.manualMemoId}`);
524 lines.push(`${indent(d + 1)}deps: ${value.deps != null ? `[${value.deps.length} deps]` : "null"}`);
525 lines.push(`${indent(d)}}`);
526 break;
527 case "FinishMemoize":
528 lines.push(`${indent(d)}FinishMemoize {`);
529 lines.push(`${indent(d + 1)}manualMemoId: ${value.manualMemoId}`);
530 lines.push(`${indent(d + 1)}decl: $${value.decl.identifier.id}`);
531 lines.push(`${indent(d + 1)}pruned: ${value.pruned === true}`);
532 lines.push(`${indent(d)}}`);
533 break;
534 case "UnsupportedNode":
535 lines.push(`${indent(d)}UnsupportedNode {`);
536 lines.push(
537 `${indent(d + 1)}type: ${value.node != null ? value.node.type : "unknown"}`
538 );
539 lines.push(`${indent(d)}}`);
540 break;
541 // Reactive-only value types that may appear:
542 case "LogicalExpression":
543 lines.push(`${indent(d)}LogicalExpression {`);
544 lines.push(`${indent(d + 1)}operator: ${value.operator}`);
545 lines.push(`${indent(d)}}`);
546 break;
547 case "ConditionalExpression":
548 lines.push(`${indent(d)}ConditionalExpression {}`);
549 break;
550 case "SequenceExpression":
551 lines.push(`${indent(d)}SequenceExpression {}`);
552 break;
553 case "OptionalExpression":
554 lines.push(`${indent(d)}OptionalExpression {`);
555 lines.push(`${indent(d + 1)}optional: ${value.optional}`);
556 lines.push(`${indent(d)}}`);
557 break;
558 default:
559 lines.push(`${indent(d)}${kind} {}`);
560 break;
561 }
562 return lines.join("\n");
563 }
564
565 // ---------------------------------------------------------------------------
566 // Instruction printing
567 // ---------------------------------------------------------------------------
568
569 function printInstruction(instr, depth) {
570 const lines = [];
571 lines.push(`${indent(depth)}[${instr.id}] Instruction {`);
572 const d = depth + 1;
573 lines.push(`${indent(d)}id: ${instr.id}`);
574 // lvalue
575 lines.push(`${indent(d)}lvalue:`);
576 lines.push(printPlaceInline(instr.lvalue, d + 1));
577 // value
578 lines.push(`${indent(d)}value:`);
579 lines.push(printInstructionValueFields(instr.value, d + 1));
580 // effects
581 if (instr.effects != null) {
582 lines.push(`${indent(d)}effects: [${instr.effects.length} effects]`);
583 } else {
584 lines.push(`${indent(d)}effects: null`);
585 }
586 lines.push(`${indent(d)}loc: ${formatLoc(instr.loc)}`);
587 lines.push(`${indent(depth)}}`);
588 return lines.join("\n");
589 }
590
591 // ---------------------------------------------------------------------------
592 // Terminal printing
593 // ---------------------------------------------------------------------------
594
595 function printTerminal(terminal, depth) {
596 const lines = [];
597 const d = depth;
598 const kind = terminal.kind;
599 lines.push(`${indent(d)}${terminalName(kind)} {`);
600 lines.push(`${indent(d + 1)}id: ${terminal.id}`);
601
602 switch (kind) {
603 case "if":
604 lines.push(`${indent(d + 1)}test:`);
605 lines.push(printPlaceInline(terminal.test, d + 2));
606 lines.push(`${indent(d + 1)}consequent: bb${terminal.consequent}`);
607 lines.push(`${indent(d + 1)}alternate: bb${terminal.alternate}`);
608 lines.push(
609 `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}`
610 );
611 break;
612 case "branch":
613 lines.push(`${indent(d + 1)}test:`);
614 lines.push(printPlaceInline(terminal.test, d + 2));
615 lines.push(`${indent(d + 1)}consequent: bb${terminal.consequent}`);
616 lines.push(`${indent(d + 1)}alternate: bb${terminal.alternate}`);
617 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
618 break;
619 case "return":
620 lines.push(`${indent(d + 1)}returnVariant: ${terminal.returnVariant}`);
621 lines.push(`${indent(d + 1)}value:`);
622 lines.push(printPlaceInline(terminal.value, d + 2));
623 if (terminal.effects != null) {
624 lines.push(
625 `${indent(d + 1)}effects: [${terminal.effects.length} effects]`
626 );
627 } else {
628 lines.push(`${indent(d + 1)}effects: null`);
629 }
630 break;
631 case "throw":
632 lines.push(`${indent(d + 1)}value:`);
633 lines.push(printPlaceInline(terminal.value, d + 2));
634 break;
635 case "goto":
636 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
637 lines.push(`${indent(d + 1)}variant: ${terminal.variant}`);
638 break;
639 case "switch":
640 lines.push(`${indent(d + 1)}test:`);
641 lines.push(printPlaceInline(terminal.test, d + 2));
642 lines.push(`${indent(d + 1)}cases:`);
643 for (const c of terminal.cases) {
644 if (c.test != null) {
645 lines.push(`${indent(d + 2)}case $${c.test.identifier.id}: bb${c.block}`);
646 } else {
647 lines.push(`${indent(d + 2)}default: bb${c.block}`);
648 }
649 }
650 lines.push(
651 `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}`
652 );
653 break;
654 case "do-while":
655 lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`);
656 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
657 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
658 break;
659 case "while":
660 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
661 lines.push(
662 `${indent(d + 1)}loop: ${terminal.loop != null ? `bb${terminal.loop}` : "null"}`
663 );
664 lines.push(
665 `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}`
666 );
667 break;
668 case "for":
669 lines.push(`${indent(d + 1)}init: bb${terminal.init}`);
670 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
671 lines.push(
672 `${indent(d + 1)}update: ${terminal.update != null ? `bb${terminal.update}` : "null"}`
673 );
674 lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`);
675 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
676 break;
677 case "for-of":
678 lines.push(`${indent(d + 1)}init: bb${terminal.init}`);
679 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
680 lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`);
681 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
682 break;
683 case "for-in":
684 lines.push(`${indent(d + 1)}init: bb${terminal.init}`);
685 lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`);
686 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
687 break;
688 case "logical":
689 lines.push(`${indent(d + 1)}operator: ${terminal.operator}`);
690 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
691 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
692 break;
693 case "ternary":
694 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
695 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
696 break;
697 case "optional":
698 lines.push(`${indent(d + 1)}optional: ${terminal.optional}`);
699 lines.push(`${indent(d + 1)}test: bb${terminal.test}`);
700 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
701 break;
702 case "label":
703 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
704 lines.push(
705 `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}`
706 );
707 break;
708 case "sequence":
709 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
710 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
711 break;
712 case "maybe-throw":
713 lines.push(`${indent(d + 1)}continuation: bb${terminal.continuation}`);
714 lines.push(
715 `${indent(d + 1)}handler: ${terminal.handler != null ? `bb${terminal.handler}` : "null"}`
716 );
717 if (terminal.effects != null) {
718 lines.push(
719 `${indent(d + 1)}effects: [${terminal.effects.length} effects]`
720 );
721 } else {
722 lines.push(`${indent(d + 1)}effects: null`);
723 }
724 break;
725 case "try":
726 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
727 if (terminal.handlerBinding != null) {
728 lines.push(`${indent(d + 1)}handlerBinding:`);
729 lines.push(printPlaceInline(terminal.handlerBinding, d + 2));
730 } else {
731 lines.push(`${indent(d + 1)}handlerBinding: null`);
732 }
733 lines.push(`${indent(d + 1)}handler: bb${terminal.handler}`);
734 lines.push(
735 `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}`
736 );
737 break;
738 case "scope":
739 lines.push(`${indent(d + 1)}scope: @${terminal.scope.id}`);
740 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
741 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
742 break;
743 case "pruned-scope":
744 lines.push(`${indent(d + 1)}scope: @${terminal.scope.id}`);
745 lines.push(`${indent(d + 1)}block: bb${terminal.block}`);
746 lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`);
747 break;
748 case "unreachable":
749 break;
750 case "unsupported":
751 break;
752 default:
753 break;
754 }
755
756 lines.push(`${indent(d + 1)}loc: ${formatLoc(terminal.loc)}`);
757 lines.push(`${indent(d)}}`);
758 return lines.join("\n");
759 }
760
761 function terminalName(kind) {
762 const names = {
763 if: "If",
764 branch: "Branch",
765 return: "Return",
766 throw: "Throw",
767 goto: "Goto",
768 switch: "Switch",
769 "do-while": "DoWhile",
770 while: "While",
771 for: "For",
772 "for-of": "ForOf",
773 "for-in": "ForIn",
774 logical: "Logical",
775 ternary: "Ternary",
776 optional: "Optional",
777 label: "Label",
778 sequence: "Sequence",
779 "maybe-throw": "MaybeThrow",
780 try: "Try",
781 scope: "Scope",
782 "pruned-scope": "PrunedScope",
783 unreachable: "Unreachable",
784 unsupported: "Unsupported",
785 };
786 return names[kind] ?? kind;
787 }
788
789 // ---------------------------------------------------------------------------
790 // Phi printing
791 // ---------------------------------------------------------------------------
792
793 function printPhi(phi, depth) {
794 const lines = [];
795 lines.push(`${indent(depth)}Phi {`);
796 lines.push(`${indent(depth + 1)}place: $${phi.place.identifier.id}`);
797 lines.push(`${indent(depth + 1)}operands:`);
798 // phi.operands is a Map<BlockId, Place>
799 const sortedOperands = [...phi.operands].sort((a, b) => a[0] - b[0]);
800 for (const [blockId, place] of sortedOperands) {
801 lines.push(
802 `${indent(depth + 2)}bb${blockId}: $${place.identifier.id}`
803 );
804 }
805 lines.push(`${indent(depth)}}`);
806 return lines.join("\n");
807 }
808
809 // ---------------------------------------------------------------------------
810 // Main function printer
811 // ---------------------------------------------------------------------------
812
813 function printHIRFunction(fn, functionIndex, outlinedCollector) {
814 const lines = [];
815 const d0 = 0;
816 const d1 = 1;
817 const d2 = 2;
818
819 lines.push(`${indent(d0)}Function #${functionIndex}:`);
820
821 // id
822 lines.push(
823 `${indent(d1)}id: ${fn.id != null ? JSON.stringify(fn.id) : "null"}`
824 );
825
826 // params
827 lines.push(`${indent(d1)}params:`);
828 for (let i = 0; i < fn.params.length; i++) {
829 const param = fn.params[i];
830 if (param.kind === "Identifier") {
831 lines.push(`${indent(d2)}[${i}]`);
832 lines.push(printPlaceInline(param, d2 + 1));
833 } else {
834 // Spread
835 lines.push(`${indent(d2)}[${i}] ...`);
836 lines.push(printPlaceInline(param.place, d2 + 1));
837 }
838 }
839
840 // returns
841 lines.push(`${indent(d1)}returns:`);
842 lines.push(printPlaceInline(fn.returns, d2));
843
844 // returnTypeAnnotation
845 if (fn.returnTypeAnnotation != null) {
846 lines.push(`${indent(d1)}returnTypeAnnotation: ${JSON.stringify(fn.returnTypeAnnotation)}`);
847 } else {
848 lines.push(`${indent(d1)}returnTypeAnnotation: null`);
849 }
850
851 // context
852 if (fn.context.length > 0) {
853 lines.push(`${indent(d1)}context:`);
854 for (const ctx of fn.context) {
855 lines.push(printPlaceInline(ctx, d2));
856 }
857 } else {
858 lines.push(`${indent(d1)}context: []`);
859 }
860
861 // directives
862 if (fn.directives.length > 0) {
863 lines.push(
864 `${indent(d1)}directives: [${fn.directives.map((d) => JSON.stringify(d)).join(", ")}]`
865 );
866 } else {
867 lines.push(`${indent(d1)}directives: []`);
868 }
869
870 // generator / async
871 lines.push(`${indent(d1)}generator: ${fn.generator}`);
872 lines.push(`${indent(d1)}async: ${fn.async}`);
873
874 // aliasingEffects
875 if (fn.aliasingEffects != null) {
876 lines.push(
877 `${indent(d1)}aliasingEffects: [${fn.aliasingEffects.length} effects]`
878 );
879 } else {
880 lines.push(`${indent(d1)}aliasingEffects: null`);
881 }
882
883 // Collect all identifiers from the function
884 const identifiers = new Map();
885 collectIdentifiers(fn, identifiers);
886
887 lines.push("");
888 lines.push(`${indent(d1)}Identifiers:`);
889 const sortedIds = [...identifiers.entries()].sort((a, b) => a[0] - b[0]);
890 for (const [, identifier] of sortedIds) {
891 lines.push(printIdentifierEntry(identifier, d2));
892 }
893
894 // Blocks (in order from body.blocks, which is RPO)
895 lines.push("");
896 lines.push(`${indent(d1)}Blocks:`);
897 for (const [blockId, block] of fn.body.blocks) {
898 lines.push(`${indent(d2)}bb${blockId} (${block.kind}):`);
899 const d3 = d2 + 1;
900
901 // preds
902 const preds = [...block.preds].sort((a, b) => a - b);
903 lines.push(`${indent(d3)}preds: [${preds.map((p) => `bb${p}`).join(", ")}]`);
904
905 // phis
906 if (block.phis.size > 0) {
907 lines.push(`${indent(d3)}phis:`);
908 for (const phi of block.phis) {
909 lines.push(printPhi(phi, d3 + 1));
910 }
911 } else {
912 lines.push(`${indent(d3)}phis: []`);
913 }
914
915 // instructions
916 lines.push(`${indent(d3)}instructions:`);
917 for (const instr of block.instructions) {
918 lines.push(printInstruction(instr, d3 + 1));
919 // Collect outlined functions
920 if (
921 instr.value.kind === "FunctionExpression" ||
922 instr.value.kind === "ObjectMethod"
923 ) {
924 outlinedCollector.push(instr.value.loweredFunc.func);
925 }
926 }
927
928 // terminal
929 lines.push(`${indent(d3)}terminal:`);
930 lines.push(printTerminal(block.terminal, d3 + 1));
931 }
932
933 return lines.join("\n");
934 }
935
936 // ---------------------------------------------------------------------------
937 // Identifier collection
938 // ---------------------------------------------------------------------------
939
940 function collectIdentifiers(fn, map) {
941 // From params
942 for (const param of fn.params) {
943 if (param.kind === "Identifier") {
944 addIdentifier(map, param.identifier);
945 } else {
946 addIdentifier(map, param.place.identifier);
947 }
948 }
949
950 // returns
951 addIdentifier(map, fn.returns.identifier);
952
953 // context
954 for (const ctx of fn.context) {
955 addIdentifier(map, ctx.identifier);
956 }
957
958 // From blocks
959 for (const [, block] of fn.body.blocks) {
960 // phis
961 for (const phi of block.phis) {
962 addIdentifier(map, phi.place.identifier);
963 for (const [, place] of phi.operands) {
964 addIdentifier(map, place.identifier);
965 }
966 }
967
968 // instructions
969 for (const instr of block.instructions) {
970 addIdentifier(map, instr.lvalue.identifier);
971 collectIdentifiersFromValue(instr.value, map);
972 }
973
974 // terminal
975 collectIdentifiersFromTerminal(block.terminal, map);
976 }
977 }
978
979 function addIdentifier(map, identifier) {
980 if (!map.has(identifier.id)) {
981 map.set(identifier.id, identifier);
982 }
983 }
984
985 function collectIdentifiersFromPlace(place, map) {
986 if (place != null) {
987 addIdentifier(map, place.identifier);
988 }
989 }
990
991 function collectIdentifiersFromValue(value, map) {
992 if (value == null) return;
993 switch (value.kind) {
994 case "LoadLocal":
995 case "LoadContext":
996 collectIdentifiersFromPlace(value.place, map);
997 break;
998 case "DeclareLocal":
999 case "DeclareContext":
1000 collectIdentifiersFromPlace(value.lvalue.place, map);
1001 break;
1002 case "StoreLocal":
1003 case "StoreContext":
1004 collectIdentifiersFromPlace(value.lvalue.place, map);
1005 collectIdentifiersFromPlace(value.value, map);
1006 break;
1007 case "Destructure":
1008 collectIdentifiersFromPattern(value.lvalue.pattern, map);
1009 collectIdentifiersFromPlace(value.value, map);
1010 break;
1011 case "BinaryExpression":
1012 collectIdentifiersFromPlace(value.left, map);
1013 collectIdentifiersFromPlace(value.right, map);
1014 break;
1015 case "UnaryExpression":
1016 collectIdentifiersFromPlace(value.value, map);
1017 break;
1018 case "CallExpression":
1019 case "NewExpression":
1020 collectIdentifiersFromPlace(value.callee, map);
1021 for (const arg of value.args) {
1022 if (arg.kind === "Identifier") collectIdentifiersFromPlace(arg, map);
1023 else if (arg.kind === "Spread")
1024 collectIdentifiersFromPlace(arg.place, map);
1025 }
1026 break;
1027 case "MethodCall":
1028 collectIdentifiersFromPlace(value.receiver, map);
1029 collectIdentifiersFromPlace(value.property, map);
1030 for (const arg of value.args) {
1031 if (arg.kind === "Identifier") collectIdentifiersFromPlace(arg, map);
1032 else if (arg.kind === "Spread")
1033 collectIdentifiersFromPlace(arg.place, map);
1034 }
1035 break;
1036 case "ObjectExpression":
1037 if (value.properties != null) {
1038 for (const prop of value.properties) {
1039 if (prop.kind === "ObjectProperty") {
1040 collectIdentifiersFromPlace(prop.place, map);
1041 if (prop.key.kind === "computed")
1042 collectIdentifiersFromPlace(prop.key.name, map);
1043 } else {
1044 collectIdentifiersFromPlace(prop.place, map);
1045 }
1046 }
1047 }
1048 break;
1049 case "ArrayExpression":
1050 for (const el of value.elements) {
1051 if (el.kind === "Identifier") collectIdentifiersFromPlace(el, map);
1052 else if (el.kind === "Spread")
1053 collectIdentifiersFromPlace(el.place, map);
1054 }
1055 break;
1056 case "PropertyLoad":
1057 case "PropertyDelete":
1058 collectIdentifiersFromPlace(value.object, map);
1059 break;
1060 case "PropertyStore":
1061 collectIdentifiersFromPlace(value.object, map);
1062 collectIdentifiersFromPlace(value.value, map);
1063 break;
1064 case "ComputedLoad":
1065 case "ComputedDelete":
1066 collectIdentifiersFromPlace(value.object, map);
1067 collectIdentifiersFromPlace(value.property, map);
1068 break;
1069 case "ComputedStore":
1070 collectIdentifiersFromPlace(value.object, map);
1071 collectIdentifiersFromPlace(value.property, map);
1072 collectIdentifiersFromPlace(value.value, map);
1073 break;
1074 case "StoreGlobal":
1075 collectIdentifiersFromPlace(value.value, map);
1076 break;
1077 case "TypeCastExpression":
1078 collectIdentifiersFromPlace(value.value, map);
1079 break;
1080 case "JsxExpression":
1081 if (value.tag.kind === "Identifier")
1082 collectIdentifiersFromPlace(value.tag, map);
1083 for (const attr of value.props) {
1084 if (attr.kind === "JsxAttribute")
1085 collectIdentifiersFromPlace(attr.place, map);
1086 else collectIdentifiersFromPlace(attr.argument, map);
1087 }
1088 if (value.children != null) {
1089 for (const child of value.children)
1090 collectIdentifiersFromPlace(child, map);
1091 }
1092 break;
1093 case "JsxFragment":
1094 for (const child of value.children)
1095 collectIdentifiersFromPlace(child, map);
1096 break;
1097 case "FunctionExpression":
1098 case "ObjectMethod":
1099 // context of lowered func
1100 for (const ctx of value.loweredFunc.func.context) {
1101 collectIdentifiersFromPlace(ctx, map);
1102 }
1103 break;
1104 case "TaggedTemplateExpression":
1105 collectIdentifiersFromPlace(value.tag, map);
1106 break;
1107 case "TemplateLiteral":
1108 for (const s of value.subexprs) collectIdentifiersFromPlace(s, map);
1109 break;
1110 case "Await":
1111 collectIdentifiersFromPlace(value.value, map);
1112 break;
1113 case "GetIterator":
1114 collectIdentifiersFromPlace(value.collection, map);
1115 break;
1116 case "IteratorNext":
1117 collectIdentifiersFromPlace(value.iterator, map);
1118 collectIdentifiersFromPlace(value.collection, map);
1119 break;
1120 case "NextPropertyOf":
1121 collectIdentifiersFromPlace(value.value, map);
1122 break;
1123 case "PostfixUpdate":
1124 case "PrefixUpdate":
1125 collectIdentifiersFromPlace(value.lvalue, map);
1126 collectIdentifiersFromPlace(value.value, map);
1127 break;
1128 case "FinishMemoize":
1129 collectIdentifiersFromPlace(value.decl, map);
1130 break;
1131 case "StartMemoize":
1132 if (value.deps != null) {
1133 for (const dep of value.deps) {
1134 if (dep.root.kind === "NamedLocal") {
1135 collectIdentifiersFromPlace(dep.root.value, map);
1136 }
1137 }
1138 }
1139 break;
1140 default:
1141 break;
1142 }
1143 }
1144
1145 function collectIdentifiersFromPattern(pattern, map) {
1146 switch (pattern.kind) {
1147 case "ArrayPattern":
1148 for (const item of pattern.items) {
1149 if (item.kind === "Identifier") collectIdentifiersFromPlace(item, map);
1150 else if (item.kind === "Spread")
1151 collectIdentifiersFromPlace(item.place, map);
1152 }
1153 break;
1154 case "ObjectPattern":
1155 for (const prop of pattern.properties) {
1156 if (prop.kind === "ObjectProperty") {
1157 collectIdentifiersFromPlace(prop.place, map);
1158 if (prop.key.kind === "computed")
1159 collectIdentifiersFromPlace(prop.key.name, map);
1160 } else {
1161 collectIdentifiersFromPlace(prop.place, map);
1162 }
1163 }
1164 break;
1165 }
1166 }
1167
1168 function collectIdentifiersFromTerminal(terminal, map) {
1169 switch (terminal.kind) {
1170 case "if":
1171 case "branch":
1172 collectIdentifiersFromPlace(terminal.test, map);
1173 break;
1174 case "return":
1175 collectIdentifiersFromPlace(terminal.value, map);
1176 break;
1177 case "throw":
1178 collectIdentifiersFromPlace(terminal.value, map);
1179 break;
1180 case "switch":
1181 collectIdentifiersFromPlace(terminal.test, map);
1182 for (const c of terminal.cases) {
1183 if (c.test != null) collectIdentifiersFromPlace(c.test, map);
1184 }
1185 break;
1186 case "try":
1187 if (terminal.handlerBinding != null)
1188 collectIdentifiersFromPlace(terminal.handlerBinding, map);
1189 break;
1190 default:
1191 break;
1192 }
1193 }