| 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 | } |