@samitouri / QOS-React / commits / 555100ca83

[HIR] Nonnullable block fallthroughs

ghstack-source-id: 6f24b60056c741e5d4e9836f91bc4fce0f9e8fdd Pull Request resolved: https://github.com/facebook/react-forget/pull/2865

Mofei Zhang committed Apr 23, 2024 at 10:18 UTC 555100ca83049ce94cac210098ed7c367fd8751c
11 files changed +172 -295
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+20 -4
@@ -362,6 +362,8 @@ export type Terminal =
362 | TryTerminal
363 | ReactiveScopeTerminal;
364
365 +export type TerminalWithFallthrough = Terminal & { fallthrough: BlockId };
366 +
367 function _staticInvariantTerminalHasLocation(
368 terminal: Terminal
369 ): SourceLocation {
@@ -376,6 +378,13 @@ function _staticInvariantTerminalHasInstructionId(
378 return terminal.id;
379 }
380
381 +function _staticInvariantTerminalHasFallthrough(
382 + terminal: Terminal
383 +): BlockId | never | undefined {
384 + // If this fails, it is because a variant of Terminal is missing a fallthrough annotation
385 + return terminal.fallthrough;
386 +}
387 +
388 /*
389 * Terminal nodes allowed for a value block
390 * A terminal that couldn't be lowered correctly.
@@ -384,6 +393,7 @@ export type UnsupportedTerminal = {
393 kind: "unsupported";
394 id: InstructionId;
395 loc: SourceLocation;
396 + fallthrough?: never;
397 };
398
399 /**
@@ -395,6 +405,7 @@ export type UnreachableTerminal = {
405 kind: "unreachable";
406 id: InstructionId;
407 loc: SourceLocation;
408 + fallthrough?: never;
409 };
410
411 export type ThrowTerminal = {
@@ -402,6 +413,7 @@ export type ThrowTerminal = {
413 value: Place;
414 id: InstructionId;
415 loc: SourceLocation;
416 + fallthrough?: never;
417 };
418 export type Case = { test: Place | null; block: BlockId };
419
@@ -410,6 +422,7 @@ export type ReturnTerminal = {
422 loc: SourceLocation;
423 value: Place;
424 id: InstructionId;
425 + fallthrough?: never;
426 };
427
428 export type GotoTerminal = {
@@ -418,6 +431,7 @@ export type GotoTerminal = {
431 variant: GotoVariant;
432 id: InstructionId;
433 loc: SourceLocation;
434 + fallthrough?: never;
435 };
436
437 export enum GotoVariant {
@@ -431,7 +445,7 @@ export type IfTerminal = {
445 test: Place;
446 consequent: BlockId;
447 alternate: BlockId;
434 - fallthrough: BlockId | null;
448 + fallthrough: BlockId;
449 id: InstructionId;
450 loc: SourceLocation;
451 };
@@ -443,13 +457,14 @@ export type BranchTerminal = {
457 alternate: BlockId;
458 id: InstructionId;
459 loc: SourceLocation;
460 + fallthrough?: never;
461 };
462
463 export type SwitchTerminal = {
464 kind: "switch";
465 test: Place;
466 cases: Array<Case>;
452 - fallthrough: BlockId | null;
467 + fallthrough: BlockId;
468 id: InstructionId;
469 loc: SourceLocation;
470 };
@@ -521,7 +536,7 @@ export type TernaryTerminal = {
536 export type LabelTerminal = {
537 kind: "label";
538 block: BlockId;
524 - fallthrough: BlockId | null;
539 + fallthrough: BlockId;
540 id: InstructionId;
541 loc: SourceLocation;
542 };
@@ -555,7 +570,7 @@ export type TryTerminal = {
570 handlerBinding: Place | null;
571 handler: BlockId;
572 // TODO: support `finally`
558 - fallthrough: BlockId | null;
573 + fallthrough: BlockId;
574 id: InstructionId;
575 loc: SourceLocation;
576 };
@@ -566,6 +581,7 @@ export type MaybeThrowTerminal = {
581 handler: BlockId;
582 id: InstructionId;
583 loc: SourceLocation;
584 + fallthrough?: never;
585 };
586
587 export type ReactiveScopeTerminal = {
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+71 -120
@@ -8,7 +8,6 @@
8 import { Binding, NodePath } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import { CompilerError } from "../CompilerError";
11 -import { assertExhaustive } from "../Utils/utils";
11 import { Environment } from "./Environment";
12 import { Global } from "./Globals";
13 import {
@@ -32,8 +31,8 @@ import {
31 import { printInstruction } from "./PrintHIR";
32 import {
33 eachTerminalSuccessor,
35 - mapOptionalFallthroughs,
34 mapTerminalSuccessors,
35 + terminalFallthrough,
36 } from "./visitors";
37
38 /*
@@ -329,7 +328,6 @@ export default class HIRBuilder {
328 ir.blocks = rpoBlocks;
329
330 removeUnreachableForUpdates(ir);
332 - removeUnreachableFallthroughs(ir);
331 removeDeadDoWhileStatements(ir);
332 removeUnnecessaryTryCatch(ir);
333 markInstructionIds(ir);
@@ -629,24 +627,6 @@ export function removeUnreachableForUpdates(fn: HIR): void {
627 }
628 }
629
632 -export function removeUnreachableFallthroughs(func: HIR): void {
633 - const visited: Set<BlockId> = new Set();
634 - for (const [_, block] of func.blocks) {
635 - visited.add(block.id);
636 - }
637 -
638 - // Cleanup any fallthrough blocks that weren't visited
639 - for (const [_, block] of func.blocks) {
640 - mapOptionalFallthroughs(block.terminal, (fallthrough) => {
641 - if (visited.has(fallthrough)) {
642 - return fallthrough;
643 - } else {
644 - return null;
645 - }
646 - });
647 - }
648 -}
649 -
630 export function removeDeadDoWhileStatements(func: HIR): void {
631 const visited: Set<BlockId> = new Set();
632 for (const [_, block] of func.blocks) {
@@ -691,14 +671,19 @@ export function reversePostorderBlocks(func: HIR): void {
671 */
672 function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
673 const visited: Set<BlockId> = new Set();
674 + const used: Set<BlockId> = new Set();
675 + const usedFallthroughs: Set<BlockId> = new Set();
676 const postorder: Array<BlockId> = [];
695 - function visit(blockId: BlockId): void {
696 - if (visited.has(blockId)) {
677 + function visit(blockId: BlockId, isUsed: boolean): void {
678 + const wasUsed = used.has(blockId);
679 + const wasVisited = visited.has(blockId);
680 + visited.add(blockId);
681 + if (isUsed) {
682 + used.add(blockId);
683 + }
684 + if (wasVisited && (wasUsed || !isUsed)) {
685 return;
686 }
699 - visited.add(blockId);
700 - const block = func.blocks.get(blockId)!;
701 - const { terminal } = block;
687
688 /*
689 * Note that we visit successors in reverse order. This ensures that when we
@@ -710,7 +695,7 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
695 * // bb1
696 * x = 1;
697 * } else {
713 - * // b2
698 + * // bb2
699 * x = 2;
700 * }
701 * // bb3
@@ -721,104 +706,50 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
706 * program order for visual debugging. By visiting the successors in reverse order
707 * (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
708 */
724 - switch (terminal.kind) {
725 - case "return":
726 - case "throw": {
727 - // no-op, no successors
728 - break;
729 - }
730 - case "goto": {
731 - visit(terminal.block);
732 - break;
733 - }
734 - case "if": {
735 - /*
736 - * can ignore fallthrough, if its reachable it will be reached through
737 - * consequent/alternate
738 - */
739 - const { consequent, alternate } = terminal;
740 - visit(alternate);
741 - visit(consequent);
742 - break;
743 - }
744 - case "branch": {
745 - const { consequent, alternate } = terminal;
746 - visit(alternate);
747 - visit(consequent);
748 - break;
749 - }
750 - case "switch": {
751 - /*
752 - * can ignore fallthrough, if its reachable it will be reached through
753 - * a case
754 - */
755 - const { cases } = terminal;
756 - for (const case_ of [...cases].reverse()) {
757 - visit(case_.block);
758 - }
759 - break;
760 - }
761 - case "optional":
762 - case "ternary":
763 - case "logical": {
764 - visit(terminal.test);
765 - break;
766 - }
767 - case "do-while": {
768 - visit(terminal.loop);
769 - break;
770 - }
771 - case "while": {
772 - visit(terminal.test);
773 - break;
774 - }
775 - case "for":
776 - case "for-in":
777 - case "for-of": {
778 - visit(terminal.init);
779 - break;
780 - }
781 - case "label": {
782 - visit(terminal.block);
783 - break;
784 - }
785 - case "sequence": {
786 - visit(terminal.block);
787 - break;
788 - }
789 - case "maybe-throw": {
790 - visit(terminal.handler);
791 - visit(terminal.continuation);
792 - break;
793 - }
794 - case "try": {
795 - visit(terminal.block);
796 - break;
797 - }
798 - case "scope": {
799 - visit(terminal.block);
800 - break;
801 - }
802 - case "unreachable":
803 - case "unsupported": {
804 - break;
805 - }
806 - default: {
807 - assertExhaustive(
808 - terminal,
809 - `Unexpected terminal kind \`${(terminal as any).kind}\``
810 - );
709 + const block = func.blocks.get(blockId)!;
710 + const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
711 + const fallthrough = terminalFallthrough(block.terminal);
712 +
713 + /**
714 + * Fallthrough blocks are only used to record original program block structure. If the
715 + * fallthrough is actually reachable, it will be reached through terminal successors.
716 + * To retain program structure, we visit fallthrough blocks first (marking them as not
717 + * actually used yet) to ensure their block IDs emitted in the correct order.
718 + */
719 + if (fallthrough != null) {
720 + if (isUsed) {
721 + usedFallthroughs.add(fallthrough);
722 }
723 + visit(fallthrough, false);
724 + }
725 + for (const successor of successors) {
726 + visit(successor, isUsed);
727 }
728
814 - postorder.push(blockId);
729 + if (!wasVisited) {
730 + postorder.push(blockId);
731 + }
732 }
816 - visit(func.entry);
817 -
818 - const blocks = new Map();
733 + visit(func.entry, true);
734 + const blocks = new Map<BlockId, BasicBlock>();
735 for (const blockId of postorder.reverse()) {
820 - blocks.set(blockId, func.blocks.get(blockId)!);
736 + const block = func.blocks.get(blockId)!;
737 + if (used.has(blockId)) {
738 + blocks.set(blockId, func.blocks.get(blockId)!);
739 + } else if (usedFallthroughs.has(blockId)) {
740 + blocks.set(blockId, {
741 + ...block,
742 + instructions: [],
743 + terminal: {
744 + kind: "unreachable",
745 + id: block.terminal.id,
746 + loc: block.terminal.loc,
747 + },
748 + });
749 + }
750 + // otherwise this block is unreachable
751 }
752 +
753 return blocks;
754 }
755
@@ -847,6 +778,14 @@ export function markPredecessors(func: HIR): void {
778 const visited: Set<BlockId> = new Set();
779 function visit(blockId: BlockId, prevBlock: BasicBlock | null): void {
780 const block = func.blocks.get(blockId)!;
781 + if (block == null) {
782 + return;
783 + }
784 + CompilerError.invariant(block != null, {
785 + reason: "unexpected missing block",
786 + description: `block ${blockId}`,
787 + loc: GeneratedSource,
788 + });
789 if (prevBlock) {
790 block.preds.add(prevBlock.id);
791 }
@@ -879,7 +818,7 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
818
819 /*
820 * Finds try terminals where the handler is unreachable, and converts the try
882 - * to a goto(terminal.fallthrough)
821 + * to a goto(terminal.block)
822 */
823 export function removeUnnecessaryTryCatch(fn: HIR): void {
824 for (const [, block] of fn.blocks) {
@@ -887,6 +826,9 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
826 block.terminal.kind === "try" &&
827 !fn.blocks.has(block.terminal.handler)
828 ) {
829 + const handlerId = block.terminal.handler;
830 + const fallthroughId = block.terminal.fallthrough;
831 + const fallthrough = fn.blocks.get(fallthroughId);
832 block.terminal = {
833 kind: "goto",
834 block: block.terminal.block,
@@ -894,6 +836,15 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
836 loc: block.terminal.loc,
837 variant: GotoVariant.Break,
838 };
839 +
840 + if (fallthrough != null) {
841 + if (fallthrough.preds.size === 1 && fallthrough.preds.has(handlerId)) {
842 + // delete fallthrough
843 + fn.blocks.delete(fallthroughId);
844 + } else {
845 + fallthrough.preds.delete(handlerId);
846 + }
847 + }
848 }
849 }
850 }
compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts
+6 -5
@@ -13,8 +13,8 @@ import {
13 HIRFunction,
14 Instruction,
15 } from "./HIR";
16 -import { markPredecessors, removeUnreachableFallthroughs } from "./HIRBuilder";
17 -import { mapOptionalFallthroughs, terminalFallthrough } from "./visitors";
16 +import { markPredecessors } from "./HIRBuilder";
17 +import { terminalFallthrough, terminalHasFallthrough } from "./visitors";
18
19 /*
20 * Merges sequences of blocks that will always execute consecutively —
@@ -113,10 +113,11 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
113 fn.body.blocks.delete(block.id);
114 }
115 markPredecessors(fn.body);
116 - for (const [, block] of fn.body.blocks) {
117 - mapOptionalFallthroughs(block.terminal, (blockId) => merged.get(blockId));
116 + for (const [, { terminal }] of fn.body.blocks) {
117 + if (terminalHasFallthrough(terminal)) {
118 + terminal.fallthrough = merged.get(terminal.fallthrough);
119 + }
120 }
119 - removeUnreachableFallthroughs(fn.body);
121 }
122
123 class MergedBlocks {
compiler/packages/babel-plugin-react-forget/src/HIR/index.ts
-1
@@ -23,7 +23,6 @@ export {
23 markInstructionIds,
24 markPredecessors,
25 removeUnnecessaryTryCatch,
26 - removeUnreachableFallthroughs,
26 reversePostorderBlocks,
27 } from "./HIRBuilder";
28 export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+22 -114
@@ -634,8 +634,7 @@ export function mapTerminalSuccessors(
634 case "if": {
635 const consequent = fn(terminal.consequent);
636 const alternate = fn(terminal.alternate);
637 - const fallthrough =
638 - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
637 + const fallthrough = fn(terminal.fallthrough);
638 return {
639 kind: "if",
640 test: terminal.test,
@@ -666,8 +665,7 @@ export function mapTerminalSuccessors(
665 block: target,
666 };
667 });
669 - const fallthrough =
670 - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
668 + const fallthrough = fn(terminal.fallthrough);
669 return {
670 kind: "switch",
671 test: terminal.test,
@@ -794,8 +792,7 @@ export function mapTerminalSuccessors(
792 }
793 case "label": {
794 const block = fn(terminal.block);
797 - const fallthrough =
798 - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
795 + const fallthrough = fn(terminal.fallthrough);
796 return {
797 kind: "label",
798 block,
@@ -829,8 +826,7 @@ export function mapTerminalSuccessors(
826 case "try": {
827 const block = fn(terminal.block);
828 const handler = fn(terminal.handler);
832 - const fallthrough =
833 - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
829 + const fallthrough = fn(terminal.fallthrough);
830 return {
831 kind: "try",
832 block,
@@ -866,12 +862,10 @@ export function mapTerminalSuccessors(
862 }
863 }
864
869 -/*
870 - * Helper to get a terminal's fallthrough. The main reason to extract this as a helper
871 - * function is to ensure that we use an exhaustive switch to ensure that we add new terminal
872 - * variants as appropriate.
873 - */
874 -export function terminalFallthrough(terminal: Terminal): BlockId | null {
865 +export function terminalHasFallthrough<
866 + T extends Terminal,
867 + U extends T & { fallthrough: BlockId }
868 +>(terminal: T): terminal is U {
869 switch (terminal.kind) {
870 case "maybe-throw":
871 case "branch":
@@ -880,7 +874,8 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
874 case "throw":
875 case "unreachable":
876 case "unsupported": {
883 - return null;
877 + const _: undefined = terminal.fallthrough;
878 + return false;
879 }
880 case "try":
881 case "do-while":
@@ -896,7 +891,8 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
891 case "ternary":
892 case "while":
893 case "scope": {
899 - return terminal.fallthrough;
894 + const _: BlockId = terminal.fallthrough;
895 + return true;
896 }
897 default: {
898 assertExhaustive(
@@ -907,104 +903,16 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
903 }
904 }
905
910 -export function mapOptionalFallthroughs(
911 - terminal: Terminal,
912 - fn: (block: BlockId) => BlockId | null
913 -): void {
914 - switch (terminal.kind) {
915 - case "maybe-throw":
916 - case "branch":
917 - case "goto":
918 - case "return":
919 - case "throw":
920 - case "unreachable":
921 - case "unsupported": {
922 - return;
923 - }
924 - /*
925 - * NOTE: TypeScript has a bug where it does not correctly model properties whose values are
926 - * non-null in some cases and nullable in other cases, if those cases are joined together.
927 - * Thus we use one block per case here to ensure that any changes to the types will cause
928 - * a compiler error.
929 - */
930 - case "do-while": {
931 - const _: BlockId = terminal.fallthrough;
932 - break;
933 - }
934 - case "for-of": {
935 - const _: BlockId = terminal.fallthrough;
936 - break;
937 - }
938 - case "for-in": {
939 - const _: BlockId = terminal.fallthrough;
940 - break;
941 - }
942 - case "for": {
943 - const _: BlockId = terminal.fallthrough;
944 - break;
945 - }
946 - case "logical": {
947 - const _: BlockId = terminal.fallthrough;
948 - break;
949 - }
950 - case "optional": {
951 - const _: BlockId = terminal.fallthrough;
952 - break;
953 - }
954 - case "ternary": {
955 - const _: BlockId = terminal.fallthrough;
956 - break;
957 - }
958 - case "while": {
959 - const _: BlockId = terminal.fallthrough;
960 - break;
961 - }
962 - case "scope": {
963 - const _: BlockId = terminal.fallthrough;
964 - break;
965 - }
966 - case "switch": {
967 - if (terminal.fallthrough !== null) {
968 - terminal.fallthrough = fn(terminal.fallthrough);
969 - } else {
970 - terminal.fallthrough = null;
971 - }
972 - break;
973 - }
974 - case "if": {
975 - if (terminal.fallthrough !== null) {
976 - terminal.fallthrough = fn(terminal.fallthrough);
977 - } else {
978 - terminal.fallthrough = null;
979 - }
980 - break;
981 - }
982 - case "label": {
983 - if (terminal.fallthrough !== null) {
984 - terminal.fallthrough = fn(terminal.fallthrough);
985 - } else {
986 - terminal.fallthrough = null;
987 - }
988 - break;
989 - }
990 - case "sequence": {
991 - const _: BlockId = terminal.fallthrough;
992 - break;
993 - }
994 - case "try": {
995 - if (terminal.fallthrough !== null) {
996 - terminal.fallthrough = fn(terminal.fallthrough);
997 - } else {
998 - terminal.fallthrough = null;
999 - }
1000 - break;
1001 - }
1002 - default: {
1003 - assertExhaustive(
1004 - terminal,
1005 - `Unexpected terminal kind \`${(terminal as any).kind}\``
1006 - );
1007 - }
906 +/*
907 + * Helper to get a terminal's fallthrough. The main reason to extract this as a helper
908 + * function is to ensure that we use an exhaustive switch to ensure that we add new terminal
909 + * variants as appropriate.
910 + */
911 +export function terminalFallthrough(terminal: Terminal): BlockId | null {
912 + if (terminalHasFallthrough(terminal)) {
913 + return terminal.fallthrough;
914 + } else {
915 + return null;
916 }
917 }
918
compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts
-2
@@ -23,7 +23,6 @@ import {
23 markInstructionIds,
24 markPredecessors,
25 mergeConsecutiveBlocks,
26 - removeUnreachableFallthroughs,
26 reversePostorderBlocks,
27 } from "../HIR";
28 import {
@@ -72,7 +71,6 @@ function constantPropagationImpl(fn: HIRFunction, constants: Constants): void {
71 * Re-run minification of the graph (incl reordering instruction ids)
72 */
73 reversePostorderBlocks(fn.body);
75 - removeUnreachableFallthroughs(fn.body);
74 removeUnreachableForUpdates(fn.body);
75 removeDeadDoWhileStatements(fn.body);
76 removeUnnecessaryTryCatch(fn.body);
compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts
-2
@@ -15,7 +15,6 @@ import {
15 assertConsistentIdentifiers,
16 assertTerminalSuccessorsExist,
17 mergeConsecutiveBlocks,
18 - removeUnreachableFallthroughs,
18 reversePostorderBlocks,
19 } from "../HIR";
20 import {
@@ -39,7 +38,6 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
38 * Re-run minification of the graph (incl reordering instruction ids)
39 */
40 reversePostorderBlocks(fn.body);
42 - removeUnreachableFallthroughs(fn.body);
41 removeUnreachableForUpdates(fn.body);
42 removeDeadDoWhileStatements(fn.body);
43 removeUnnecessaryTryCatch(fn.body);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+19 -20
@@ -112,7 +112,7 @@ class Driver {
112 }
113 case "if": {
114 const fallthroughId =
115 - terminal.fallthrough !== null &&
115 + this.cx.reachable(terminal.fallthrough) &&
116 !this.cx.isScheduled(terminal.fallthrough)
117 ? terminal.fallthrough
118 : null;
@@ -176,7 +176,7 @@ class Driver {
176 }
177 case "switch": {
178 const fallthroughId =
179 - terminal.fallthrough !== null &&
179 + this.cx.reachable(terminal.fallthrough) &&
180 !this.cx.isScheduled(terminal.fallthrough)
181 ? terminal.fallthrough
182 : null;
@@ -289,7 +289,7 @@ class Driver {
289 }
290 case "while": {
291 const fallthroughId =
292 - terminal.fallthrough !== null &&
292 + this.cx.reachable(terminal.fallthrough) &&
293 !this.cx.isScheduled(terminal.fallthrough)
294 ? terminal.fallthrough
295 : null;
@@ -350,11 +350,9 @@ class Driver {
350 ? terminal.loop
351 : null;
352
353 - const fallthroughId =
354 - terminal.fallthrough !== null &&
355 - !this.cx.isScheduled(terminal.fallthrough)
356 - ? terminal.fallthrough
357 - : null;
353 + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough)
354 + ? terminal.fallthrough
355 + : null;
356
357 const scheduleId = this.cx.scheduleLoop(
358 terminal.fallthrough,
@@ -437,11 +435,9 @@ class Driver {
435 ? terminal.loop
436 : null;
437
440 - const fallthroughId =
441 - terminal.fallthrough !== null &&
442 - !this.cx.isScheduled(terminal.fallthrough)
443 - ? terminal.fallthrough
444 - : null;
438 + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough)
439 + ? terminal.fallthrough
440 + : null;
441
442 const scheduleId = this.cx.scheduleLoop(
443 terminal.fallthrough,
@@ -512,11 +508,9 @@ class Driver {
508 ? terminal.loop
509 : null;
510
515 - const fallthroughId =
516 - terminal.fallthrough !== null &&
517 - !this.cx.isScheduled(terminal.fallthrough)
518 - ? terminal.fallthrough
519 - : null;
511 + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough)
512 + ? terminal.fallthrough
513 + : null;
514
515 const scheduleId = this.cx.scheduleLoop(
516 terminal.fallthrough,
@@ -626,7 +620,7 @@ class Driver {
620 }
621 case "label": {
622 const fallthroughId =
629 - terminal.fallthrough !== null &&
623 + this.cx.reachable(terminal.fallthrough) &&
624 !this.cx.isScheduled(terminal.fallthrough)
625 ? terminal.fallthrough
626 : null;
@@ -747,7 +741,7 @@ class Driver {
741 }
742 case "try": {
743 const fallthroughId =
750 - terminal.fallthrough !== null &&
744 + this.cx.reachable(terminal.fallthrough) &&
745 !this.cx.isScheduled(terminal.fallthrough)
746 ? terminal.fallthrough
747 : null;
@@ -1241,6 +1235,11 @@ class Context {
1235 this.#catchHandlers.add(block);
1236 }
1237
1238 + reachable(id: BlockId): boolean {
1239 + const block = this.ir.blocks.get(id)!;
1240 + return block.terminal.kind !== "unreachable";
1241 + }
1242 +
1243 /*
1244 * Record that the given block will be emitted (eg by the codegen of a parent node)
1245 * so that child nodes can avoid re-emitting it.
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md
+25 -17
@@ -40,38 +40,46 @@ import { unstable_useMemoCache as useMemoCache } from "react";
40 import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime";
41
42 function useFoo(t0) {
43 - const $ = useMemoCache(3);
43 + const $ = useMemoCache(6);
44 const { value, cond } = t0;
45 + let y;
46 let t1;
47 if ($[0] !== value || $[1] !== cond) {
48 t1 = Symbol.for("react.early_return_sentinel");
49 bb0: {
49 - const y = [value];
50 - const x = { cond };
51 - try {
52 - mutate(x);
53 - throwErrorWithMessageIf(x.cond, "error");
54 - } catch {
55 - setProperty(x, "henderson");
56 - t1 = x;
57 - break bb0;
58 - }
50 + y = [value];
51 + let x;
52 + if ($[4] !== cond) {
53 + x = { cond };
54 + try {
55 + mutate(x);
56 + throwErrorWithMessageIf(x.cond, "error");
57 + } catch {
58 + setProperty(x, "henderson");
59 + t1 = x;
60 + break bb0;
61 + }
62
60 - setProperty(x, "nevada");
63 + setProperty(x, "nevada");
64 + $[4] = cond;
65 + $[5] = x;
66 + } else {
67 + x = $[5];
68 + }
69 y.push(x);
62 -
63 - t1 = y;
64 - break bb0;
70 }
71 $[0] = value;
72 $[1] = cond;
68 - $[2] = t1;
73 + $[2] = y;
74 + $[3] = t1;
75 } else {
70 - t1 = $[2];
76 + y = $[2];
77 + t1 = $[3];
78 }
79 if (t1 !== Symbol.for("react.early_return_sentinel")) {
80 return t1;
81 }
82 + return y;
83 }
84
85 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md
+1 -3
@@ -45,9 +45,6 @@ function Component(props) {
45 t0 = e;
46 break bb0;
47 }
48 -
49 - t0 = null;
50 - break bb0;
48 }
49 $[0] = props.y;
50 $[1] = props.e;
@@ -58,6 +55,7 @@ function Component(props) {
55 if (t0 !== Symbol.for("react.early_return_sentinel")) {
56 return t0;
57 }
58 + return null;
59 }
60
61 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md
+8 -7
@@ -31,12 +31,13 @@ import { unstable_useMemoCache as useMemoCache } from "react";
31 const { throwInput } = require("shared-runtime");
32
33 function Component(props) {
34 - const $ = useMemoCache(1);
34 + const $ = useMemoCache(2);
35 + let x;
36 let t0;
37 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38 t0 = Symbol.for("react.early_return_sentinel");
39 bb0: {
39 - const x = [];
40 + x = [];
41 try {
42 throwInput(x);
43 } catch (t1) {
@@ -45,17 +46,17 @@ function Component(props) {
46 t0 = e;
47 break bb0;
48 }
48 -
49 - t0 = x;
50 - break bb0;
49 }
52 - $[0] = t0;
50 + $[0] = x;
51 + $[1] = t0;
52 } else {
54 - t0 = $[0];
53 + x = $[0];
54 + t0 = $[1];
55 }
56 if (t0 !== Symbol.for("react.early_return_sentinel")) {
57 return t0;
58 }
59 + return x;
60 }
61
62 export const FIXTURE_ENTRYPOINT = {