main
ts 1,310 lines 30.4 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 import {assertExhaustive} from '../Utils/utils';
9 import {CompilerError} from '..';
10 import {
11 BasicBlock,
12 BlockId,
13 Instruction,
14 InstructionKind,
15 InstructionValue,
16 makeInstructionId,
17 Pattern,
18 Place,
19 ReactiveInstruction,
20 ReactiveScope,
21 ReactiveValue,
22 ScopeId,
23 SpreadPattern,
24 Terminal,
25 } from './HIR';
26
27 export function* eachInstructionLValue(
28 instr: ReactiveInstruction,
29 ): Iterable<Place> {
30 if (instr.lvalue !== null) {
31 yield instr.lvalue;
32 }
33 yield* eachInstructionValueLValue(instr.value);
34 }
35
36 export function* eachInstructionLValueWithKind(
37 instr: ReactiveInstruction,
38 ): Iterable<[Place, InstructionKind]> {
39 switch (instr.value.kind) {
40 case 'DeclareContext':
41 case 'StoreContext':
42 case 'DeclareLocal':
43 case 'StoreLocal': {
44 yield [instr.value.lvalue.place, instr.value.lvalue.kind];
45 break;
46 }
47 case 'Destructure': {
48 const kind = instr.value.lvalue.kind;
49 for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
50 yield [place, kind];
51 }
52 break;
53 }
54 case 'PostfixUpdate':
55 case 'PrefixUpdate': {
56 yield [instr.value.lvalue, InstructionKind.Reassign];
57 break;
58 }
59 }
60 }
61
62 export function* eachInstructionValueLValue(
63 value: ReactiveValue,
64 ): Iterable<Place> {
65 switch (value.kind) {
66 case 'DeclareContext':
67 case 'StoreContext':
68 case 'DeclareLocal':
69 case 'StoreLocal': {
70 yield value.lvalue.place;
71 break;
72 }
73 case 'Destructure': {
74 yield* eachPatternOperand(value.lvalue.pattern);
75 break;
76 }
77 case 'PostfixUpdate':
78 case 'PrefixUpdate': {
79 yield value.lvalue;
80 break;
81 }
82 }
83 }
84
85 export function* eachInstructionOperand(instr: Instruction): Iterable<Place> {
86 yield* eachInstructionValueOperand(instr.value);
87 }
88 export function* eachInstructionValueOperand(
89 instrValue: InstructionValue,
90 ): Iterable<Place> {
91 switch (instrValue.kind) {
92 case 'NewExpression':
93 case 'CallExpression': {
94 yield instrValue.callee;
95 yield* eachCallArgument(instrValue.args);
96 break;
97 }
98 case 'BinaryExpression': {
99 yield instrValue.left;
100 yield instrValue.right;
101 break;
102 }
103 case 'MethodCall': {
104 yield instrValue.receiver;
105 yield instrValue.property;
106 yield* eachCallArgument(instrValue.args);
107 break;
108 }
109 case 'DeclareContext':
110 case 'DeclareLocal': {
111 break;
112 }
113 case 'LoadLocal':
114 case 'LoadContext': {
115 yield instrValue.place;
116 break;
117 }
118 case 'StoreLocal': {
119 yield instrValue.value;
120 break;
121 }
122 case 'StoreContext': {
123 yield instrValue.lvalue.place;
124 yield instrValue.value;
125 break;
126 }
127 case 'StoreGlobal': {
128 yield instrValue.value;
129 break;
130 }
131 case 'Destructure': {
132 yield instrValue.value;
133 break;
134 }
135 case 'PropertyLoad': {
136 yield instrValue.object;
137 break;
138 }
139 case 'PropertyDelete': {
140 yield instrValue.object;
141 break;
142 }
143 case 'PropertyStore': {
144 yield instrValue.object;
145 yield instrValue.value;
146 break;
147 }
148 case 'ComputedLoad': {
149 yield instrValue.object;
150 yield instrValue.property;
151 break;
152 }
153 case 'ComputedDelete': {
154 yield instrValue.object;
155 yield instrValue.property;
156 break;
157 }
158 case 'ComputedStore': {
159 yield instrValue.object;
160 yield instrValue.property;
161 yield instrValue.value;
162 break;
163 }
164 case 'UnaryExpression': {
165 yield instrValue.value;
166 break;
167 }
168 case 'JsxExpression': {
169 if (instrValue.tag.kind === 'Identifier') {
170 yield instrValue.tag;
171 }
172 for (const attribute of instrValue.props) {
173 switch (attribute.kind) {
174 case 'JsxAttribute': {
175 yield attribute.place;
176 break;
177 }
178 case 'JsxSpreadAttribute': {
179 yield attribute.argument;
180 break;
181 }
182 default: {
183 assertExhaustive(
184 attribute,
185 `Unexpected attribute kind \`${(attribute as any).kind}\``,
186 );
187 }
188 }
189 }
190 if (instrValue.children) {
191 yield* instrValue.children;
192 }
193 break;
194 }
195 case 'JsxFragment': {
196 yield* instrValue.children;
197 break;
198 }
199 case 'ObjectExpression': {
200 for (const property of instrValue.properties) {
201 if (
202 property.kind === 'ObjectProperty' &&
203 property.key.kind === 'computed'
204 ) {
205 yield property.key.name;
206 }
207 yield property.place;
208 }
209 break;
210 }
211 case 'ArrayExpression': {
212 for (const element of instrValue.elements) {
213 if (element.kind === 'Identifier') {
214 yield element;
215 } else if (element.kind === 'Spread') {
216 yield element.place;
217 }
218 }
219 break;
220 }
221 case 'ObjectMethod':
222 case 'FunctionExpression': {
223 yield* instrValue.loweredFunc.func.context;
224 break;
225 }
226 case 'TaggedTemplateExpression': {
227 yield instrValue.tag;
228 break;
229 }
230 case 'TypeCastExpression': {
231 yield instrValue.value;
232 break;
233 }
234 case 'TemplateLiteral': {
235 yield* instrValue.subexprs;
236 break;
237 }
238 case 'Await': {
239 yield instrValue.value;
240 break;
241 }
242 case 'GetIterator': {
243 yield instrValue.collection;
244 break;
245 }
246 case 'IteratorNext': {
247 yield instrValue.iterator;
248 yield instrValue.collection;
249 break;
250 }
251 case 'NextPropertyOf': {
252 yield instrValue.value;
253 break;
254 }
255 case 'PostfixUpdate':
256 case 'PrefixUpdate': {
257 yield instrValue.value;
258 break;
259 }
260 case 'StartMemoize': {
261 if (instrValue.deps != null) {
262 for (const dep of instrValue.deps) {
263 if (dep.root.kind === 'NamedLocal') {
264 yield dep.root.value;
265 }
266 }
267 }
268 break;
269 }
270 case 'FinishMemoize': {
271 yield instrValue.decl;
272 break;
273 }
274 case 'Debugger':
275 case 'RegExpLiteral':
276 case 'MetaProperty':
277 case 'LoadGlobal':
278 case 'UnsupportedNode':
279 case 'Primitive':
280 case 'JSXText': {
281 break;
282 }
283 default: {
284 assertExhaustive(
285 instrValue,
286 `Unexpected instruction kind \`${(instrValue as any).kind}\``,
287 );
288 }
289 }
290 }
291
292 export function* eachCallArgument(
293 args: Array<Place | SpreadPattern>,
294 ): Iterable<Place> {
295 for (const arg of args) {
296 if (arg.kind === 'Identifier') {
297 yield arg;
298 } else {
299 yield arg.place;
300 }
301 }
302 }
303
304 export function doesPatternContainSpreadElement(pattern: Pattern): boolean {
305 switch (pattern.kind) {
306 case 'ArrayPattern': {
307 for (const item of pattern.items) {
308 if (item.kind === 'Spread') {
309 return true;
310 }
311 }
312 break;
313 }
314 case 'ObjectPattern': {
315 for (const property of pattern.properties) {
316 if (property.kind === 'Spread') {
317 return true;
318 }
319 }
320 break;
321 }
322 default: {
323 assertExhaustive(
324 pattern,
325 `Unexpected pattern kind \`${(pattern as any).kind}\``,
326 );
327 }
328 }
329 return false;
330 }
331
332 export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
333 switch (pattern.kind) {
334 case 'ArrayPattern': {
335 for (const item of pattern.items) {
336 if (item.kind === 'Identifier') {
337 yield item;
338 } else if (item.kind === 'Spread') {
339 yield item.place;
340 } else if (item.kind === 'Hole') {
341 continue;
342 } else {
343 assertExhaustive(
344 item,
345 `Unexpected item kind \`${(item as any).kind}\``,
346 );
347 }
348 }
349 break;
350 }
351 case 'ObjectPattern': {
352 for (const property of pattern.properties) {
353 if (property.kind === 'ObjectProperty') {
354 yield property.place;
355 } else if (property.kind === 'Spread') {
356 yield property.place;
357 } else {
358 assertExhaustive(
359 property,
360 `Unexpected item kind \`${(property as any).kind}\``,
361 );
362 }
363 }
364 break;
365 }
366 default: {
367 assertExhaustive(
368 pattern,
369 `Unexpected pattern kind \`${(pattern as any).kind}\``,
370 );
371 }
372 }
373 }
374
375 export function* eachPatternItem(
376 pattern: Pattern,
377 ): Iterable<Place | SpreadPattern> {
378 switch (pattern.kind) {
379 case 'ArrayPattern': {
380 for (const item of pattern.items) {
381 if (item.kind === 'Identifier') {
382 yield item;
383 } else if (item.kind === 'Spread') {
384 yield item;
385 } else if (item.kind === 'Hole') {
386 continue;
387 } else {
388 assertExhaustive(
389 item,
390 `Unexpected item kind \`${(item as any).kind}\``,
391 );
392 }
393 }
394 break;
395 }
396 case 'ObjectPattern': {
397 for (const property of pattern.properties) {
398 if (property.kind === 'ObjectProperty') {
399 yield property.place;
400 } else if (property.kind === 'Spread') {
401 yield property;
402 } else {
403 assertExhaustive(
404 property,
405 `Unexpected item kind \`${(property as any).kind}\``,
406 );
407 }
408 }
409 break;
410 }
411 default: {
412 assertExhaustive(
413 pattern,
414 `Unexpected pattern kind \`${(pattern as any).kind}\``,
415 );
416 }
417 }
418 }
419
420 export function mapInstructionLValues(
421 instr: Instruction,
422 fn: (place: Place) => Place,
423 ): void {
424 switch (instr.value.kind) {
425 case 'DeclareLocal':
426 case 'StoreLocal': {
427 const lvalue = instr.value.lvalue;
428 lvalue.place = fn(lvalue.place);
429 break;
430 }
431 case 'Destructure': {
432 mapPatternOperands(instr.value.lvalue.pattern, fn);
433 break;
434 }
435 case 'PostfixUpdate':
436 case 'PrefixUpdate': {
437 instr.value.lvalue = fn(instr.value.lvalue);
438 break;
439 }
440 }
441 if (instr.lvalue !== null) {
442 instr.lvalue = fn(instr.lvalue);
443 }
444 }
445
446 export function mapInstructionOperands(
447 instr: Instruction,
448 fn: (place: Place) => Place,
449 ): void {
450 mapInstructionValueOperands(instr.value, fn);
451 }
452
453 export function mapInstructionValueOperands(
454 instrValue: InstructionValue,
455 fn: (place: Place) => Place,
456 ): void {
457 switch (instrValue.kind) {
458 case 'BinaryExpression': {
459 instrValue.left = fn(instrValue.left);
460 instrValue.right = fn(instrValue.right);
461 break;
462 }
463 case 'PropertyLoad': {
464 instrValue.object = fn(instrValue.object);
465 break;
466 }
467 case 'PropertyDelete': {
468 instrValue.object = fn(instrValue.object);
469 break;
470 }
471 case 'PropertyStore': {
472 instrValue.object = fn(instrValue.object);
473 instrValue.value = fn(instrValue.value);
474 break;
475 }
476 case 'ComputedLoad': {
477 instrValue.object = fn(instrValue.object);
478 instrValue.property = fn(instrValue.property);
479 break;
480 }
481 case 'ComputedDelete': {
482 instrValue.object = fn(instrValue.object);
483 instrValue.property = fn(instrValue.property);
484 break;
485 }
486 case 'ComputedStore': {
487 instrValue.object = fn(instrValue.object);
488 instrValue.property = fn(instrValue.property);
489 instrValue.value = fn(instrValue.value);
490 break;
491 }
492 case 'DeclareContext':
493 case 'DeclareLocal': {
494 break;
495 }
496 case 'LoadLocal':
497 case 'LoadContext': {
498 instrValue.place = fn(instrValue.place);
499 break;
500 }
501 case 'StoreLocal': {
502 instrValue.value = fn(instrValue.value);
503 break;
504 }
505 case 'StoreContext': {
506 instrValue.lvalue.place = fn(instrValue.lvalue.place);
507 instrValue.value = fn(instrValue.value);
508 break;
509 }
510 case 'StoreGlobal': {
511 instrValue.value = fn(instrValue.value);
512 break;
513 }
514 case 'Destructure': {
515 instrValue.value = fn(instrValue.value);
516 break;
517 }
518 case 'NewExpression':
519 case 'CallExpression': {
520 instrValue.callee = fn(instrValue.callee);
521 instrValue.args = mapCallArguments(instrValue.args, fn);
522 break;
523 }
524 case 'MethodCall': {
525 instrValue.receiver = fn(instrValue.receiver);
526 instrValue.property = fn(instrValue.property);
527 instrValue.args = mapCallArguments(instrValue.args, fn);
528 break;
529 }
530 case 'UnaryExpression': {
531 instrValue.value = fn(instrValue.value);
532 break;
533 }
534 case 'JsxExpression': {
535 if (instrValue.tag.kind === 'Identifier') {
536 instrValue.tag = fn(instrValue.tag);
537 }
538 for (const attribute of instrValue.props) {
539 switch (attribute.kind) {
540 case 'JsxAttribute': {
541 attribute.place = fn(attribute.place);
542 break;
543 }
544 case 'JsxSpreadAttribute': {
545 attribute.argument = fn(attribute.argument);
546 break;
547 }
548 default: {
549 assertExhaustive(
550 attribute,
551 `Unexpected attribute kind \`${(attribute as any).kind}\``,
552 );
553 }
554 }
555 }
556 if (instrValue.children) {
557 instrValue.children = instrValue.children.map(p => fn(p));
558 }
559 break;
560 }
561 case 'ObjectExpression': {
562 for (const property of instrValue.properties) {
563 if (
564 property.kind === 'ObjectProperty' &&
565 property.key.kind === 'computed'
566 ) {
567 property.key.name = fn(property.key.name);
568 }
569 property.place = fn(property.place);
570 }
571 break;
572 }
573 case 'ArrayExpression': {
574 instrValue.elements = instrValue.elements.map(element => {
575 if (element.kind === 'Identifier') {
576 return fn(element);
577 } else if (element.kind === 'Spread') {
578 element.place = fn(element.place);
579 return element;
580 } else {
581 return element;
582 }
583 });
584 break;
585 }
586 case 'JsxFragment': {
587 instrValue.children = instrValue.children.map(e => fn(e));
588 break;
589 }
590 case 'ObjectMethod':
591 case 'FunctionExpression': {
592 instrValue.loweredFunc.func.context =
593 instrValue.loweredFunc.func.context.map(d => fn(d));
594
595 break;
596 }
597 case 'TaggedTemplateExpression': {
598 instrValue.tag = fn(instrValue.tag);
599 break;
600 }
601 case 'TypeCastExpression': {
602 instrValue.value = fn(instrValue.value);
603 break;
604 }
605 case 'TemplateLiteral': {
606 instrValue.subexprs = instrValue.subexprs.map(fn);
607 break;
608 }
609 case 'Await': {
610 instrValue.value = fn(instrValue.value);
611 break;
612 }
613 case 'GetIterator': {
614 instrValue.collection = fn(instrValue.collection);
615 break;
616 }
617 case 'IteratorNext': {
618 instrValue.iterator = fn(instrValue.iterator);
619 instrValue.collection = fn(instrValue.collection);
620 break;
621 }
622 case 'NextPropertyOf': {
623 instrValue.value = fn(instrValue.value);
624 break;
625 }
626 case 'PostfixUpdate':
627 case 'PrefixUpdate': {
628 instrValue.value = fn(instrValue.value);
629 break;
630 }
631 case 'StartMemoize': {
632 if (instrValue.deps != null) {
633 for (const dep of instrValue.deps) {
634 if (dep.root.kind === 'NamedLocal') {
635 dep.root.value = fn(dep.root.value);
636 }
637 }
638 }
639 break;
640 }
641 case 'FinishMemoize': {
642 instrValue.decl = fn(instrValue.decl);
643 break;
644 }
645 case 'Debugger':
646 case 'RegExpLiteral':
647 case 'MetaProperty':
648 case 'LoadGlobal':
649 case 'UnsupportedNode':
650 case 'Primitive':
651 case 'JSXText': {
652 break;
653 }
654 default: {
655 assertExhaustive(instrValue, 'Unexpected instruction kind');
656 }
657 }
658 }
659
660 export function mapCallArguments(
661 args: Array<Place | SpreadPattern>,
662 fn: (place: Place) => Place,
663 ): Array<Place | SpreadPattern> {
664 return args.map(arg => {
665 if (arg.kind === 'Identifier') {
666 return fn(arg);
667 } else {
668 arg.place = fn(arg.place);
669 return arg;
670 }
671 });
672 }
673
674 export function mapPatternOperands(
675 pattern: Pattern,
676 fn: (place: Place) => Place,
677 ): void {
678 switch (pattern.kind) {
679 case 'ArrayPattern': {
680 pattern.items = pattern.items.map(item => {
681 if (item.kind === 'Identifier') {
682 return fn(item);
683 } else if (item.kind === 'Spread') {
684 item.place = fn(item.place);
685 return item;
686 } else {
687 return item;
688 }
689 });
690 break;
691 }
692 case 'ObjectPattern': {
693 for (const property of pattern.properties) {
694 property.place = fn(property.place);
695 }
696 break;
697 }
698 default: {
699 assertExhaustive(
700 pattern,
701 `Unexpected pattern kind \`${(pattern as any).kind}\``,
702 );
703 }
704 }
705 }
706
707 // Maps a terminal node's block assignments using the provided function.
708 export function mapTerminalSuccessors(
709 terminal: Terminal,
710 fn: (block: BlockId) => BlockId,
711 ): Terminal {
712 switch (terminal.kind) {
713 case 'goto': {
714 const target = fn(terminal.block);
715 return {
716 kind: 'goto',
717 block: target,
718 variant: terminal.variant,
719 id: makeInstructionId(0),
720 loc: terminal.loc,
721 };
722 }
723 case 'if': {
724 const consequent = fn(terminal.consequent);
725 const alternate = fn(terminal.alternate);
726 const fallthrough = fn(terminal.fallthrough);
727 return {
728 kind: 'if',
729 test: terminal.test,
730 consequent,
731 alternate,
732 fallthrough,
733 id: makeInstructionId(0),
734 loc: terminal.loc,
735 };
736 }
737 case 'branch': {
738 const consequent = fn(terminal.consequent);
739 const alternate = fn(terminal.alternate);
740 const fallthrough = fn(terminal.fallthrough);
741 return {
742 kind: 'branch',
743 test: terminal.test,
744 consequent,
745 alternate,
746 fallthrough,
747 id: makeInstructionId(0),
748 loc: terminal.loc,
749 };
750 }
751 case 'switch': {
752 const cases = terminal.cases.map(case_ => {
753 const target = fn(case_.block);
754 return {
755 test: case_.test,
756 block: target,
757 };
758 });
759 const fallthrough = fn(terminal.fallthrough);
760 return {
761 kind: 'switch',
762 test: terminal.test,
763 cases,
764 fallthrough,
765 id: makeInstructionId(0),
766 loc: terminal.loc,
767 };
768 }
769 case 'logical': {
770 const test = fn(terminal.test);
771 const fallthrough = fn(terminal.fallthrough);
772 return {
773 kind: 'logical',
774 test,
775 fallthrough,
776 operator: terminal.operator,
777 id: makeInstructionId(0),
778 loc: terminal.loc,
779 };
780 }
781 case 'ternary': {
782 const test = fn(terminal.test);
783 const fallthrough = fn(terminal.fallthrough);
784 return {
785 kind: 'ternary',
786 test,
787 fallthrough,
788 id: makeInstructionId(0),
789 loc: terminal.loc,
790 };
791 }
792 case 'optional': {
793 const test = fn(terminal.test);
794 const fallthrough = fn(terminal.fallthrough);
795 return {
796 kind: 'optional',
797 optional: terminal.optional,
798 test,
799 fallthrough,
800 id: makeInstructionId(0),
801 loc: terminal.loc,
802 };
803 }
804 case 'return': {
805 return {
806 kind: 'return',
807 returnVariant: terminal.returnVariant,
808 loc: terminal.loc,
809 value: terminal.value,
810 id: makeInstructionId(0),
811 effects: terminal.effects,
812 };
813 }
814 case 'throw': {
815 return terminal;
816 }
817 case 'do-while': {
818 const loop = fn(terminal.loop);
819 const test = fn(terminal.test);
820 const fallthrough = fn(terminal.fallthrough);
821 return {
822 kind: 'do-while',
823 loc: terminal.loc,
824 test,
825 loop,
826 fallthrough,
827 id: makeInstructionId(0),
828 };
829 }
830 case 'while': {
831 const test = fn(terminal.test);
832 const loop = fn(terminal.loop);
833 const fallthrough = fn(terminal.fallthrough);
834 return {
835 kind: 'while',
836 loc: terminal.loc,
837 test,
838 loop,
839 fallthrough,
840 id: makeInstructionId(0),
841 };
842 }
843 case 'for': {
844 const init = fn(terminal.init);
845 const test = fn(terminal.test);
846 const update = terminal.update !== null ? fn(terminal.update) : null;
847 const loop = fn(terminal.loop);
848 const fallthrough = fn(terminal.fallthrough);
849 return {
850 kind: 'for',
851 loc: terminal.loc,
852 init,
853 test,
854 update,
855 loop,
856 fallthrough,
857 id: makeInstructionId(0),
858 };
859 }
860 case 'for-of': {
861 const init = fn(terminal.init);
862 const loop = fn(terminal.loop);
863 const test = fn(terminal.test);
864 const fallthrough = fn(terminal.fallthrough);
865 return {
866 kind: 'for-of',
867 loc: terminal.loc,
868 init,
869 test,
870 loop,
871 fallthrough,
872 id: makeInstructionId(0),
873 };
874 }
875 case 'for-in': {
876 const init = fn(terminal.init);
877 const loop = fn(terminal.loop);
878 const fallthrough = fn(terminal.fallthrough);
879 return {
880 kind: 'for-in',
881 loc: terminal.loc,
882 init,
883 loop,
884 fallthrough,
885 id: makeInstructionId(0),
886 };
887 }
888 case 'label': {
889 const block = fn(terminal.block);
890 const fallthrough = fn(terminal.fallthrough);
891 return {
892 kind: 'label',
893 block,
894 fallthrough,
895 id: makeInstructionId(0),
896 loc: terminal.loc,
897 };
898 }
899 case 'sequence': {
900 const block = fn(terminal.block);
901 const fallthrough = fn(terminal.fallthrough);
902 return {
903 kind: 'sequence',
904 block,
905 fallthrough,
906 id: makeInstructionId(0),
907 loc: terminal.loc,
908 };
909 }
910 case 'maybe-throw': {
911 const continuation = fn(terminal.continuation);
912 const handler = terminal.handler !== null ? fn(terminal.handler) : null;
913 return {
914 kind: 'maybe-throw',
915 continuation,
916 handler,
917 id: makeInstructionId(0),
918 loc: terminal.loc,
919 effects: terminal.effects,
920 };
921 }
922 case 'try': {
923 const block = fn(terminal.block);
924 const handler = fn(terminal.handler);
925 const fallthrough = fn(terminal.fallthrough);
926 return {
927 kind: 'try',
928 block,
929 handlerBinding: terminal.handlerBinding,
930 handler,
931 fallthrough,
932 id: makeInstructionId(0),
933 loc: terminal.loc,
934 };
935 }
936 case 'scope':
937 case 'pruned-scope': {
938 const block = fn(terminal.block);
939 const fallthrough = fn(terminal.fallthrough);
940 return {
941 kind: terminal.kind,
942 scope: terminal.scope,
943 block,
944 fallthrough,
945 id: makeInstructionId(0),
946 loc: terminal.loc,
947 };
948 }
949 case 'unreachable':
950 case 'unsupported': {
951 return terminal;
952 }
953 default: {
954 assertExhaustive(
955 terminal,
956 `Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``,
957 );
958 }
959 }
960 }
961
962 export function terminalHasFallthrough<
963 T extends Terminal,
964 U extends T & {fallthrough: BlockId},
965 >(terminal: T): terminal is U {
966 switch (terminal.kind) {
967 case 'maybe-throw':
968 case 'goto':
969 case 'return':
970 case 'throw':
971 case 'unreachable':
972 case 'unsupported': {
973 const _: undefined = terminal.fallthrough;
974 return false;
975 }
976 case 'branch':
977 case 'try':
978 case 'do-while':
979 case 'for-of':
980 case 'for-in':
981 case 'for':
982 case 'if':
983 case 'label':
984 case 'logical':
985 case 'optional':
986 case 'sequence':
987 case 'switch':
988 case 'ternary':
989 case 'while':
990 case 'scope':
991 case 'pruned-scope': {
992 const _: BlockId = terminal.fallthrough;
993 return true;
994 }
995 default: {
996 assertExhaustive(
997 terminal,
998 `Unexpected terminal kind \`${(terminal as any).kind}\``,
999 );
1000 }
1001 }
1002 }
1003
1004 /*
1005 * Helper to get a terminal's fallthrough. The main reason to extract this as a helper
1006 * function is to ensure that we use an exhaustive switch to ensure that we add new terminal
1007 * variants as appropriate.
1008 */
1009 export function terminalFallthrough(terminal: Terminal): BlockId | null {
1010 if (terminalHasFallthrough(terminal)) {
1011 return terminal.fallthrough;
1012 } else {
1013 return null;
1014 }
1015 }
1016
1017 /*
1018 * Iterates over the successor block ids of the provided terminal. The function is called
1019 * specifically for the successors that define the standard control flow, and not
1020 * pseduo-successors such as fallthroughs.
1021 */
1022 export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
1023 switch (terminal.kind) {
1024 case 'goto': {
1025 yield terminal.block;
1026 break;
1027 }
1028 case 'if': {
1029 yield terminal.consequent;
1030 yield terminal.alternate;
1031 break;
1032 }
1033 case 'branch': {
1034 yield terminal.consequent;
1035 yield terminal.alternate;
1036 break;
1037 }
1038 case 'switch': {
1039 for (const case_ of terminal.cases) {
1040 yield case_.block;
1041 }
1042 break;
1043 }
1044 case 'optional':
1045 case 'ternary':
1046 case 'logical': {
1047 yield terminal.test;
1048 break;
1049 }
1050 case 'return': {
1051 break;
1052 }
1053 case 'throw': {
1054 break;
1055 }
1056 case 'do-while': {
1057 yield terminal.loop;
1058 break;
1059 }
1060 case 'while': {
1061 yield terminal.test;
1062 break;
1063 }
1064 case 'for': {
1065 yield terminal.init;
1066 break;
1067 }
1068 case 'for-of': {
1069 yield terminal.init;
1070 break;
1071 }
1072 case 'for-in': {
1073 yield terminal.init;
1074 break;
1075 }
1076 case 'label': {
1077 yield terminal.block;
1078 break;
1079 }
1080 case 'sequence': {
1081 yield terminal.block;
1082 break;
1083 }
1084 case 'maybe-throw': {
1085 yield terminal.continuation;
1086 if (terminal.handler !== null) {
1087 yield terminal.handler;
1088 }
1089 break;
1090 }
1091 case 'try': {
1092 yield terminal.block;
1093 break;
1094 }
1095 case 'scope':
1096 case 'pruned-scope': {
1097 yield terminal.block;
1098 break;
1099 }
1100 case 'unreachable':
1101 case 'unsupported':
1102 break;
1103 default: {
1104 assertExhaustive(
1105 terminal,
1106 `Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``,
1107 );
1108 }
1109 }
1110 }
1111
1112 export function mapTerminalOperands(
1113 terminal: Terminal,
1114 fn: (place: Place) => Place,
1115 ): void {
1116 switch (terminal.kind) {
1117 case 'if': {
1118 terminal.test = fn(terminal.test);
1119 break;
1120 }
1121 case 'branch': {
1122 terminal.test = fn(terminal.test);
1123 break;
1124 }
1125 case 'switch': {
1126 terminal.test = fn(terminal.test);
1127 for (const case_ of terminal.cases) {
1128 if (case_.test === null) {
1129 continue;
1130 }
1131 case_.test = fn(case_.test);
1132 }
1133 break;
1134 }
1135 case 'return':
1136 case 'throw': {
1137 terminal.value = fn(terminal.value);
1138 break;
1139 }
1140 case 'try': {
1141 if (terminal.handlerBinding !== null) {
1142 terminal.handlerBinding = fn(terminal.handlerBinding);
1143 } else {
1144 terminal.handlerBinding = null;
1145 }
1146 break;
1147 }
1148 case 'maybe-throw':
1149 case 'sequence':
1150 case 'label':
1151 case 'optional':
1152 case 'ternary':
1153 case 'logical':
1154 case 'do-while':
1155 case 'while':
1156 case 'for':
1157 case 'for-of':
1158 case 'for-in':
1159 case 'goto':
1160 case 'unreachable':
1161 case 'unsupported':
1162 case 'scope':
1163 case 'pruned-scope': {
1164 // no-op
1165 break;
1166 }
1167 default: {
1168 assertExhaustive(
1169 terminal,
1170 `Unexpected terminal kind \`${(terminal as any).kind}\``,
1171 );
1172 }
1173 }
1174 }
1175
1176 export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1177 switch (terminal.kind) {
1178 case 'if': {
1179 yield terminal.test;
1180 break;
1181 }
1182 case 'branch': {
1183 yield terminal.test;
1184 break;
1185 }
1186 case 'switch': {
1187 yield terminal.test;
1188 for (const case_ of terminal.cases) {
1189 if (case_.test === null) {
1190 continue;
1191 }
1192 yield case_.test;
1193 }
1194 break;
1195 }
1196 case 'return':
1197 case 'throw': {
1198 yield terminal.value;
1199 break;
1200 }
1201 case 'try': {
1202 if (terminal.handlerBinding !== null) {
1203 yield terminal.handlerBinding;
1204 }
1205 break;
1206 }
1207 case 'maybe-throw':
1208 case 'sequence':
1209 case 'label':
1210 case 'optional':
1211 case 'ternary':
1212 case 'logical':
1213 case 'do-while':
1214 case 'while':
1215 case 'for':
1216 case 'for-of':
1217 case 'for-in':
1218 case 'goto':
1219 case 'unreachable':
1220 case 'unsupported':
1221 case 'scope':
1222 case 'pruned-scope': {
1223 // no-op
1224 break;
1225 }
1226 default: {
1227 assertExhaustive(
1228 terminal,
1229 `Unexpected terminal kind \`${(terminal as any).kind}\``,
1230 );
1231 }
1232 }
1233 }
1234
1235 /**
1236 * Helper class for traversing scope blocks in HIR-form.
1237 */
1238 export class ScopeBlockTraversal {
1239 // Live stack of active scopes
1240 #activeScopes: Array<ScopeId> = [];
1241 blockInfos: Map<
1242 BlockId,
1243 | {
1244 kind: 'end';
1245 scope: ReactiveScope;
1246 pruned: boolean;
1247 }
1248 | {
1249 kind: 'begin';
1250 scope: ReactiveScope;
1251 pruned: boolean;
1252 fallthrough: BlockId;
1253 }
1254 > = new Map();
1255
1256 recordScopes(block: BasicBlock): void {
1257 const blockInfo = this.blockInfos.get(block.id);
1258 if (blockInfo?.kind === 'begin') {
1259 this.#activeScopes.push(blockInfo.scope.id);
1260 } else if (blockInfo?.kind === 'end') {
1261 const top = this.#activeScopes.at(-1);
1262 CompilerError.invariant(blockInfo.scope.id === top, {
1263 reason:
1264 'Expected traversed block fallthrough to match top-most active scope',
1265 loc: block.instructions[0]?.loc ?? block.terminal.loc,
1266 });
1267 this.#activeScopes.pop();
1268 }
1269
1270 if (
1271 block.terminal.kind === 'scope' ||
1272 block.terminal.kind === 'pruned-scope'
1273 ) {
1274 CompilerError.invariant(
1275 !this.blockInfos.has(block.terminal.block) &&
1276 !this.blockInfos.has(block.terminal.fallthrough),
1277 {
1278 reason: 'Expected unique scope blocks and fallthroughs',
1279 loc: block.terminal.loc,
1280 },
1281 );
1282 this.blockInfos.set(block.terminal.block, {
1283 kind: 'begin',
1284 scope: block.terminal.scope,
1285 pruned: block.terminal.kind === 'pruned-scope',
1286 fallthrough: block.terminal.fallthrough,
1287 });
1288 this.blockInfos.set(block.terminal.fallthrough, {
1289 kind: 'end',
1290 scope: block.terminal.scope,
1291 pruned: block.terminal.kind === 'pruned-scope',
1292 });
1293 }
1294 }
1295
1296 /**
1297 * @returns if the given scope is currently 'active', i.e. if the scope start
1298 * block but not the scope fallthrough has been recorded.
1299 */
1300 isScopeActive(scopeId: ScopeId): boolean {
1301 return this.#activeScopes.indexOf(scopeId) !== -1;
1302 }
1303
1304 /**
1305 * The current, innermost active scope.
1306 */
1307 get currentScope(): ScopeId | null {
1308 return this.#activeScopes.at(-1) ?? null;
1309 }
1310 }