main
ts 97 lines 2.87 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 {visitReactiveFunction} from '.';
9 import {CompilerError} from '..';
10 import {
11 InstructionId,
12 Place,
13 ReactiveFunction,
14 ReactiveScopeBlock,
15 ScopeId,
16 } from '../HIR';
17 import {getPlaceScope} from '../HIR/HIR';
18 import {ReactiveFunctionVisitor} from './visitors';
19
20 /*
21 * Internal validation pass that checks all the instructions involved in creating
22 * values for a given scope are within the corresponding ReactiveScopeBlock. Errors
23 * in HIR/ReactiveFunction structure and alias analysis could theoretically create
24 * a structure such as:
25 *
26 * Function
27 * LabelTerminal
28 * Instruction in scope 0
29 * Instruction in scope 0
30 *
31 * Because ReactiveScopeBlocks are closed when their surrounding block ends, this
32 * structure would create reactive scopes as follows:
33 *
34 * Function
35 * LabelTerminal
36 * ReactiveScopeBlock scope=0
37 * Instruction in scope 0
38 * Instruction in scope 0
39 *
40 * This pass asserts we didn't accidentally end up with such a structure, as a guard
41 * against compiler coding mistakes in earlier passes.
42 */
43 export function assertScopeInstructionsWithinScopes(
44 fn: ReactiveFunction,
45 ): void {
46 const existingScopes = new Set<ScopeId>();
47 visitReactiveFunction(fn, new FindAllScopesVisitor(), existingScopes);
48 visitReactiveFunction(
49 fn,
50 new CheckInstructionsAgainstScopesVisitor(),
51 existingScopes,
52 );
53 }
54
55 class FindAllScopesVisitor extends ReactiveFunctionVisitor<Set<ScopeId>> {
56 override visitScope(block: ReactiveScopeBlock, state: Set<ScopeId>): void {
57 this.traverseScope(block, state);
58 state.add(block.scope.id);
59 }
60 }
61
62 class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
63 Set<ScopeId>
64 > {
65 activeScopes: Set<ScopeId> = new Set();
66
67 override visitPlace(
68 id: InstructionId,
69 place: Place,
70 state: Set<ScopeId>,
71 ): void {
72 const scope = getPlaceScope(id, place);
73 if (
74 scope !== null &&
75 // is there a scope for this at all, or did we end up pruning this scope?
76 state.has(scope.id) &&
77 /*
78 * if the scope exists somewhere, it must be active or else this is a straggler
79 * instruction
80 */
81 !this.activeScopes.has(scope.id)
82 ) {
83 CompilerError.invariant(false, {
84 reason:
85 'Encountered an instruction that should be part of a scope, but where that scope has already completed',
86 description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed`,
87 loc: place.loc,
88 });
89 }
90 }
91
92 override visitScope(block: ReactiveScopeBlock, state: Set<ScopeId>): void {
93 this.activeScopes.add(block.scope.id);
94 this.traverseScope(block, state);
95 this.activeScopes.delete(block.scope.id);
96 }
97 }