1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import {Binding, NodePath} from '@babel/traverse';9import * as t from '@babel/types';10import {11 CompilerError,12 CompilerDiagnostic,13 CompilerErrorDetail,14 ErrorCategory,15} from '../CompilerError';16import {Environment} from './Environment';17import {18 BasicBlock,19 BlockId,20 BlockKind,21 Effect,22 GeneratedSource,23 GotoVariant,24 HIR,25 Identifier,26 IdentifierId,27 Instruction,28 Place,29 SourceLocation,30 Terminal,31 VariableBinding,32 makeBlockId,33 makeDeclarationId,34 makeIdentifierName,35 makeInstructionId,36 makeTemporaryIdentifier,37 makeType,38} from './HIR';39import {printInstruction} from './PrintHIR';40import {41 eachTerminalSuccessor,42 mapTerminalSuccessors,43 terminalFallthrough,44} from './visitors';4546/*47 * *******************************************************************************************48 * *******************************************************************************************49 * ************************************* Lowering to HIR *************************************50 * *******************************************************************************************51 * *******************************************************************************************52 */5354// A work-in-progress block that does not yet have a terminator55export type WipBlock = {56 id: BlockId;57 instructions: Array<Instruction>;58 kind: BlockKind;59};6061type Scope = LoopScope | LabelScope | SwitchScope;6263type LoopScope = {64 kind: 'loop';65 label: string | null;66 continueBlock: BlockId;67 breakBlock: BlockId;68};6970type SwitchScope = {71 kind: 'switch';72 breakBlock: BlockId;73 label: string | null;74};7576type LabelScope = {77 kind: 'label';78 label: string;79 breakBlock: BlockId;80};8182function newBlock(id: BlockId, kind: BlockKind): WipBlock {83 return {id, kind, instructions: []};84}8586export type Bindings = Map<87 string,88 {node: t.Identifier; identifier: Identifier}89>;9091/*92 * Determines how instructions should be constructed in order to preserve93 * exception semantics94 */95export type ExceptionsMode =96 /*97 * Mode used for code not covered by explicit exception handling, any98 * errors are assumed to be thrown out of the function99 */100 | {kind: 'ThrowExceptions'}101 /*102 * Mode used for code that *is* covered by explicit exception handling103 * (ie try/catch), which requires modeling the possibility of control104 * flow to the exception handler.105 */106 | {kind: 'CatchExceptions'; handler: BlockId};107108// Helper class for constructing a CFG109export default class HIRBuilder {110 #completed: Map<BlockId, BasicBlock> = new Map();111 #current: WipBlock;112 #entry: BlockId;113 #scopes: Array<Scope> = [];114 #context: Map<t.Identifier, SourceLocation>;115 #bindings: Bindings;116 #env: Environment;117 #exceptionHandlerStack: Array<BlockId> = [];118 /**119 * Traversal context: counts the number of `fbt` tag parents120 * of the current babel node.121 */122 fbtDepth: number = 0;123124 get nextIdentifierId(): IdentifierId {125 return this.#env.nextIdentifierId;126 }127128 get context(): Map<t.Identifier, SourceLocation> {129 return this.#context;130 }131132 get bindings(): Bindings {133 return this.#bindings;134 }135136 get environment(): Environment {137 return this.#env;138 }139140 constructor(141 env: Environment,142 options?: {143 bindings?: Bindings | null;144 context?: Map<t.Identifier, SourceLocation>;145 entryBlockKind?: BlockKind;146 },147 ) {148 this.#env = env;149 this.#bindings = options?.bindings ?? new Map();150 this.#context = options?.context ?? new Map();151 this.#entry = makeBlockId(env.nextBlockId);152 this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');153 }154155 recordError(error: CompilerDiagnostic | CompilerErrorDetail): void {156 this.#env.recordError(error);157 }158159 currentBlockKind(): BlockKind {160 return this.#current.kind;161 }162163 // Push a statement or expression onto the current block164 push(instruction: Instruction): void {165 this.#current.instructions.push(instruction);166 const exceptionHandler = this.#exceptionHandlerStack.at(-1);167 if (exceptionHandler !== undefined) {168 const continuationBlock = this.reserve(this.currentBlockKind());169 this.terminateWithContinuation(170 {171 kind: 'maybe-throw',172 continuation: continuationBlock.id,173 handler: exceptionHandler,174 id: makeInstructionId(0),175 loc: instruction.loc,176 effects: null,177 },178 continuationBlock,179 );180 }181 }182183 enterTryCatch(handler: BlockId, fn: () => void): void {184 this.#exceptionHandlerStack.push(handler);185 fn();186 this.#exceptionHandlerStack.pop();187 }188189 resolveThrowHandler(): BlockId | null {190 const handler = this.#exceptionHandlerStack.at(-1);191 return handler ?? null;192 }193194 makeTemporary(loc: SourceLocation): Identifier {195 const id = this.nextIdentifierId;196 return makeTemporaryIdentifier(id, loc);197 }198199 #resolveBabelBinding(200 path: NodePath<t.Identifier | t.JSXIdentifier>,201 ): Binding | null {202 const originalName = path.node.name;203 const binding = path.scope.getBinding(originalName);204 if (binding == null) {205 return null;206 }207 return binding;208 }209210 /*211 * Maps an Identifier (or JSX identifier) Babel node to an internal `Identifier`212 * which represents the variable being referenced, according to the JS scoping rules.213 *214 * Because Forget does not preserve _all_ block scopes in the input (only those that215 * happen to occur from control flow), this resolution ensures that different variables216 * with the same name are mapped to a unique name. Concretely, this function maintains217 * the invariant that all references to a given variable will return an `Identifier`218 * with the same (unique for the function) `name` and `id`.219 *220 * Example:221 *222 * ```javascript223 * function foo() {224 * const x = 0;225 * {226 * const x = 1;227 * }228 * return x;229 * }230 * ```231 *232 * The above converts as follows:233 *234 * ```235 * Const Identifier { name: 'x', id: 0 } = Primitive { value: 0 };236 * Const Identifier { name: 'x_0', id: 1 } = Primitive { value: 1 };237 * Return Identifier { name: 'x', id: 0};238 * ```239 */240 resolveIdentifier(241 path: NodePath<t.Identifier | t.JSXIdentifier>,242 ): VariableBinding {243 const originalName = path.node.name;244 const babelBinding = this.#resolveBabelBinding(path);245 if (babelBinding == null) {246 return {kind: 'Global', name: originalName};247 }248249 // Check if the binding is from module scope250 const outerBinding =251 this.#env.parentFunction.scope.parent.getBinding(originalName);252 if (babelBinding === outerBinding) {253 const path = babelBinding.path;254 if (path.isImportDefaultSpecifier()) {255 const importDeclaration =256 path.parentPath as NodePath<t.ImportDeclaration>;257 return {258 kind: 'ImportDefault',259 name: originalName,260 module: importDeclaration.node.source.value,261 };262 } else if (path.isImportSpecifier()) {263 const importDeclaration =264 path.parentPath as NodePath<t.ImportDeclaration>;265 return {266 kind: 'ImportSpecifier',267 name: originalName,268 module: importDeclaration.node.source.value,269 imported:270 path.node.imported.type === 'Identifier'271 ? path.node.imported.name272 : path.node.imported.value,273 };274 } else if (path.isImportNamespaceSpecifier()) {275 const importDeclaration =276 path.parentPath as NodePath<t.ImportDeclaration>;277 return {278 kind: 'ImportNamespace',279 name: originalName,280 module: importDeclaration.node.source.value,281 };282 } else {283 return {284 kind: 'ModuleLocal',285 name: originalName,286 };287 }288 }289290 const resolvedBinding = this.resolveBinding(babelBinding.identifier);291 if (resolvedBinding.name && resolvedBinding.name.value !== originalName) {292 babelBinding.scope.rename(originalName, resolvedBinding.name.value);293 }294 return {295 kind: 'Identifier',296 identifier: resolvedBinding,297 bindingKind: babelBinding.kind,298 };299 }300301 isContextIdentifier(path: NodePath<t.Identifier | t.JSXIdentifier>): boolean {302 const binding = this.#resolveBabelBinding(path);303 if (binding) {304 // Check if the binding is from module scope, if so return null305 const outerBinding = this.#env.parentFunction.scope.parent.getBinding(306 path.node.name,307 );308 if (binding === outerBinding) {309 return false;310 }311 return this.#env.isContextIdentifier(binding.identifier);312 } else {313 return false;314 }315 }316317 resolveBinding(node: t.Identifier): Identifier {318 if (node.name === 'fbt') {319 this.recordError(320 new CompilerErrorDetail({321 category: ErrorCategory.Todo,322 reason: 'Support local variables named `fbt`',323 description:324 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',325 loc: node.loc ?? GeneratedSource,326 suggestions: null,327 }),328 );329 }330 if (node.name === 'this') {331 this.recordError(332 new CompilerErrorDetail({333 category: ErrorCategory.UnsupportedSyntax,334 reason: '`this` is not supported syntax',335 description:336 'React Compiler does not support compiling functions that use `this`',337 loc: node.loc ?? GeneratedSource,338 suggestions: null,339 }),340 );341 }342 const originalName = node.name;343 let name = originalName;344 let index = 0;345 while (true) {346 const mapping = this.#bindings.get(name);347 if (mapping === undefined) {348 const id = this.nextIdentifierId;349 const identifier: Identifier = {350 id,351 declarationId: makeDeclarationId(id),352 name: makeIdentifierName(name),353 mutableRange: {354 start: makeInstructionId(0),355 end: makeInstructionId(0),356 },357 scope: null,358 type: makeType(),359 loc: node.loc ?? GeneratedSource,360 };361 this.#env.programContext.addNewReference(name);362 this.#bindings.set(name, {node, identifier});363 return identifier;364 } else if (mapping.node === node) {365 return mapping.identifier;366 } else {367 name = `${originalName}_${index++}`;368 }369 }370 }371372 // Construct a final CFG from this context373 build(): HIR {374 let ir: HIR = {375 blocks: this.#completed,376 entry: this.#entry,377 };378 const rpoBlocks = getReversePostorderedBlocks(ir);379 for (const [id, block] of ir.blocks) {380 if (381 !rpoBlocks.has(id) &&382 block.instructions.some(383 instr => instr.value.kind === 'FunctionExpression',384 )385 ) {386 this.recordError(387 new CompilerErrorDetail({388 reason: `Support functions with unreachable code that may contain hoisted declarations`,389 loc: block.instructions[0]?.loc ?? block.terminal.loc,390 description: null,391 suggestions: null,392 category: ErrorCategory.Todo,393 }),394 );395 }396 }397 ir.blocks = rpoBlocks;398399 removeUnreachableForUpdates(ir);400 removeDeadDoWhileStatements(ir);401 removeUnnecessaryTryCatch(ir);402 markInstructionIds(ir);403 markPredecessors(ir);404405 return ir;406 }407408 // Terminate the current block w the given terminal, and start a new block409 terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId {410 const {id: blockId, kind, instructions} = this.#current;411 this.#completed.set(blockId, {412 kind,413 id: blockId,414 instructions,415 terminal,416 preds: new Set(),417 phis: new Set(),418 });419 if (nextBlockKind) {420 const nextId = this.#env.nextBlockId;421 this.#current = newBlock(nextId, nextBlockKind);422 }423 return blockId;424 }425426 /*427 * Terminate the current block w the given terminal, and set the previously428 * reserved block as the new current block429 */430 terminateWithContinuation(terminal: Terminal, continuation: WipBlock): void {431 const {id: blockId, kind, instructions} = this.#current;432 this.#completed.set(blockId, {433 kind: kind,434 id: blockId,435 instructions,436 terminal: terminal,437 preds: new Set(),438 phis: new Set(),439 });440 this.#current = continuation;441 }442443 /*444 * Reserve a block so that it can be referenced prior to construction.445 * Make this the current block with `terminateWithContinuation()` or446 * call `complete()` to save it without setting it as the current block.447 */448 reserve(kind: BlockKind): WipBlock {449 return newBlock(makeBlockId(this.#env.nextBlockId), kind);450 }451452 // Save a previously reserved block as completed453 complete(block: WipBlock, terminal: Terminal): void {454 const {id: blockId, kind, instructions} = block;455 this.#completed.set(blockId, {456 kind,457 id: blockId,458 instructions,459 terminal,460 preds: new Set(),461 phis: new Set(),462 });463 }464465 /*466 * Sets the given wip block as the current block, executes the provided callback to populate the block467 * up to its terminal, and then resets the previous actively block.468 */469 enterReserved(wip: WipBlock, fn: () => Terminal): void {470 const current = this.#current;471 this.#current = wip;472 const terminal = fn();473 const {id: blockId, kind, instructions} = this.#current;474 this.#completed.set(blockId, {475 kind,476 id: blockId,477 instructions,478 terminal,479 preds: new Set(),480 phis: new Set(),481 });482 this.#current = current;483 }484485 /*486 * Create a new block and execute the provided callback with the new block487 * set as the current, resetting to the previously active block upon exit.488 * The lambda must return a terminal node, which is used to terminate the489 * newly constructed block.490 */491 enter(nextBlockKind: BlockKind, fn: (blockId: BlockId) => Terminal): BlockId {492 const wip = this.reserve(nextBlockKind);493 this.enterReserved(wip, () => {494 return fn(wip.id);495 });496 return wip.id;497 }498499 label<T>(label: string, breakBlock: BlockId, fn: () => T): T {500 this.#scopes.push({501 kind: 'label',502 breakBlock,503 label,504 });505 const value = fn();506 const last = this.#scopes.pop();507 CompilerError.invariant(508 last != null &&509 last.kind === 'label' &&510 last.label === label &&511 last.breakBlock === breakBlock,512 {513 reason: 'Mismatched label',514 loc: GeneratedSource,515 },516 );517 return value;518 }519520 switch<T>(label: string | null, breakBlock: BlockId, fn: () => T): T {521 this.#scopes.push({522 kind: 'switch',523 breakBlock,524 label,525 });526 const value = fn();527 const last = this.#scopes.pop();528 CompilerError.invariant(529 last != null &&530 last.kind === 'switch' &&531 last.label === label &&532 last.breakBlock === breakBlock,533 {534 reason: 'Mismatched label',535 loc: GeneratedSource,536 },537 );538 return value;539 }540541 /*542 * Executes the provided lambda inside a scope in which the provided loop543 * information is cached for lookup with `lookupBreak()` and `lookupContinue()`544 */545 loop<T>(546 label: string | null,547 // block of the loop body. "continue" jumps here.548 continueBlock: BlockId,549 // block following the loop. "break" jumps here.550 breakBlock: BlockId,551 fn: () => T,552 ): T {553 this.#scopes.push({554 kind: 'loop',555 label,556 continueBlock,557 breakBlock,558 });559 const value = fn();560 const last = this.#scopes.pop();561 CompilerError.invariant(562 last != null &&563 last.kind === 'loop' &&564 last.label === label &&565 last.continueBlock === continueBlock &&566 last.breakBlock === breakBlock,567 {568 reason: 'Mismatched loops',569 loc: GeneratedSource,570 },571 );572 return value;573 }574575 /*576 * Lookup the block target for a break statement, based on loops and switch statements577 * in scope. Throws if there is no available location to break.578 */579 lookupBreak(label: string | null): BlockId {580 for (let ii = this.#scopes.length - 1; ii >= 0; ii--) {581 const scope = this.#scopes[ii];582 if (583 (label === null &&584 (scope.kind === 'loop' || scope.kind === 'switch')) ||585 label === scope.label586 ) {587 return scope.breakBlock;588 }589 }590 CompilerError.invariant(false, {591 reason: 'Expected a loop or switch to be in scope',592 loc: GeneratedSource,593 });594 }595596 /*597 * Lookup the block target for a continue statement, based on loops598 * in scope. Throws if there is no available location to continue, or if the given599 * label does not correspond to a loop (this should also be validated at parse time).600 */601 lookupContinue(label: string | null): BlockId {602 for (let ii = this.#scopes.length - 1; ii >= 0; ii--) {603 const scope = this.#scopes[ii];604 if (scope.kind === 'loop') {605 if (label === null || label === scope.label) {606 return scope.continueBlock;607 }608 } else if (label !== null && scope.label === label) {609 CompilerError.invariant(false, {610 reason: 'Continue may only refer to a labeled loop',611 loc: GeneratedSource,612 });613 }614 }615 CompilerError.invariant(false, {616 reason: 'Expected a loop to be in scope',617 loc: GeneratedSource,618 });619 }620}621622// Helper to shrink a CFG eliminate jump-only blocks.623function _shrink(func: HIR): void {624 const gotos = new Map();625 /*626 * Given a target block for some terminator, resolves the ideal block that should be627 * targeted instead. This transitively resolves any blocks that are simple indirections628 * (empty blocks that terminate in a goto).629 */630 function resolveBlockTarget(blockId: BlockId): BlockId {631 let target = gotos.get(blockId) ?? null;632 if (target !== null) {633 return target;634 }635 const block = func.blocks.get(blockId);636 CompilerError.invariant(block != null, {637 reason: `expected block ${blockId} to exist`,638 loc: GeneratedSource,639 });640 target = getTargetIfIndirection(block);641 if (target !== null) {642 // the target might also be a simple goto, recurse643 target = resolveBlockTarget(target) ?? target;644 gotos.set(blockId, target);645 return target;646 } else {647 // If the block wasn't an indirection, return the original input.648 return blockId;649 }650 }651652 const queue = [func.entry];653 const reachable = new Set<BlockId>();654 while (queue.length !== 0) {655 const blockId = queue.shift()!;656 if (reachable.has(blockId)) {657 continue;658 }659 reachable.add(blockId);660 const block = func.blocks.get(blockId)!;661 block.terminal = mapTerminalSuccessors(block.terminal, prevTarget => {662 const target = resolveBlockTarget(prevTarget);663 queue.push(target);664 return target;665 });666 }667 for (const [blockId] of func.blocks) {668 if (!reachable.has(blockId)) {669 func.blocks.delete(blockId);670 }671 }672}673674export function removeUnreachableForUpdates(fn: HIR): void {675 for (const [, block] of fn.blocks) {676 if (677 block.terminal.kind === 'for' &&678 block.terminal.update !== null &&679 !fn.blocks.has(block.terminal.update)680 ) {681 block.terminal.update = null;682 }683 }684}685686export function removeDeadDoWhileStatements(func: HIR): void {687 const visited: Set<BlockId> = new Set();688 for (const [_, block] of func.blocks) {689 visited.add(block.id);690 }691692 /*693 * If the test condition of a DoWhile is unreachable, the terminal is effectively deadcode and we694 * can just inline the loop body. We replace the terminal with a goto to the loop block and695 * MergeConsecutiveBlocks figures out how to merge as appropriate.696 */697 for (const [_, block] of func.blocks) {698 if (block.terminal.kind === 'do-while') {699 if (!visited.has(block.terminal.test)) {700 block.terminal = {701 kind: 'goto',702 block: block.terminal.loop,703 variant: GotoVariant.Break,704 id: block.terminal.id,705 loc: block.terminal.loc,706 };707 }708 }709 }710}711712/*713 * Converts the graph to reverse-postorder, with predecessor blocks appearing714 * before successors except in the case of back edges (ie loops).715 */716export function reversePostorderBlocks(func: HIR): void {717 const rpoBlocks = getReversePostorderedBlocks(func);718 func.blocks = rpoBlocks;719}720721/**722 * Returns a mapping of BlockId => BasicBlock where the insertion order of the map723 * has blocks in reverse-postorder, with predecessor blocks appearing before successors724 * except in the case of back edges (ie loops). Note that not all blocks in the input725 * may be in the output: blocks will be removed in the case of unreachable code in726 * the input.727 */728function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {729 const visited: Set<BlockId> = new Set();730 const used: Set<BlockId> = new Set();731 const usedFallthroughs: Set<BlockId> = new Set();732 const postorder: Array<BlockId> = [];733 function visit(blockId: BlockId, isUsed: boolean): void {734 const wasUsed = used.has(blockId);735 const wasVisited = visited.has(blockId);736 visited.add(blockId);737 if (isUsed) {738 used.add(blockId);739 }740 if (wasVisited && (wasUsed || !isUsed)) {741 return;742 }743744 /*745 * Note that we visit successors in reverse order. This ensures that when we746 * reverse the list at the end, that "sibling" edges appear in-order. For example,747 * ```748 * // bb0749 * let x;750 * if (c) {751 * // bb1752 * x = 1;753 * } else {754 * // bb2755 * x = 2;756 * }757 * // bb3758 * x;759 * ```760 *761 * We want the output to be bb0, bb1, bb2, bb3 just to line up with the original762 * program order for visual debugging. By visiting the successors in reverse order763 * (eg bb2 then bb1), we ensure that they get reversed back to the correct order.764 */765 const block = func.blocks.get(blockId)!;766 CompilerError.invariant(block != null, {767 reason: '[HIRBuilder] Unexpected null block',768 description: `expected block ${blockId} to exist`,769 loc: GeneratedSource,770 });771 const successors = [...eachTerminalSuccessor(block.terminal)].reverse();772 const fallthrough = terminalFallthrough(block.terminal);773774 /**775 * Fallthrough blocks are only used to record original program block structure. If the776 * fallthrough is actually reachable, it will be reached through terminal successors.777 * To retain program structure, we visit fallthrough blocks first (marking them as not778 * actually used yet) to ensure their block IDs emitted in the correct order.779 */780 if (fallthrough != null) {781 if (isUsed) {782 usedFallthroughs.add(fallthrough);783 }784 visit(fallthrough, false);785 }786 for (const successor of successors) {787 visit(successor, isUsed);788 }789790 if (!wasVisited) {791 postorder.push(blockId);792 }793 }794 visit(func.entry, true);795 const blocks = new Map<BlockId, BasicBlock>();796 for (const blockId of postorder.reverse()) {797 const block = func.blocks.get(blockId)!;798 if (used.has(blockId)) {799 blocks.set(blockId, func.blocks.get(blockId)!);800 } else if (usedFallthroughs.has(blockId)) {801 blocks.set(blockId, {802 ...block,803 instructions: [],804 terminal: {805 kind: 'unreachable',806 id: block.terminal.id,807 loc: block.terminal.loc,808 },809 });810 }811 // otherwise this block is unreachable812 }813814 return blocks;815}816817export function markInstructionIds(func: HIR): void {818 let id = 0;819 const visited = new Set<Instruction>();820 for (const [_, block] of func.blocks) {821 for (const instr of block.instructions) {822 CompilerError.invariant(!visited.has(instr), {823 reason: `${printInstruction(instr)} already visited!`,824 loc: instr.loc,825 });826 visited.add(instr);827 instr.id = makeInstructionId(++id);828 }829 block.terminal.id = makeInstructionId(++id);830 }831}832833export function markPredecessors(func: HIR): void {834 for (const [, block] of func.blocks) {835 block.preds.clear();836 }837 const visited: Set<BlockId> = new Set();838 function visit(blockId: BlockId, prevBlock: BasicBlock | null): void {839 const block = func.blocks.get(blockId)!;840 if (block == null) {841 return;842 }843 CompilerError.invariant(block != null, {844 reason: 'unexpected missing block',845 description: `block ${blockId}`,846 loc: GeneratedSource,847 });848 if (prevBlock) {849 block.preds.add(prevBlock.id);850 }851852 if (visited.has(blockId)) {853 return;854 }855 visited.add(blockId);856857 const {terminal} = block;858859 for (const successor of eachTerminalSuccessor(terminal)) {860 visit(successor, block);861 }862 }863 visit(func.entry, null);864}865866/*867 * If the given block is a simple indirection — empty terminated with a goto(break) —868 * returns the block being pointed to. Otherwise returns null.869 */870function getTargetIfIndirection(block: BasicBlock): number | null {871 return block.instructions.length === 0 &&872 block.terminal.kind === 'goto' &&873 block.terminal.variant === GotoVariant.Break874 ? block.terminal.block875 : null;876}877878/*879 * Finds try terminals where the handler is unreachable, and converts the try880 * to a goto(terminal.block)881 */882export function removeUnnecessaryTryCatch(fn: HIR): void {883 for (const [, block] of fn.blocks) {884 if (885 block.terminal.kind === 'try' &&886 !fn.blocks.has(block.terminal.handler)887 ) {888 const handlerId = block.terminal.handler;889 const fallthroughId = block.terminal.fallthrough;890 const fallthrough = fn.blocks.get(fallthroughId);891 block.terminal = {892 kind: 'goto',893 block: block.terminal.block,894 id: makeInstructionId(0),895 loc: block.terminal.loc,896 variant: GotoVariant.Break,897 };898899 if (fallthrough != null) {900 if (fallthrough.preds.size === 1 && fallthrough.preds.has(handlerId)) {901 // delete fallthrough902 fn.blocks.delete(fallthroughId);903 } else {904 fallthrough.preds.delete(handlerId);905 }906 }907 }908 }909}910911export function createTemporaryPlace(912 env: Environment,913 loc: SourceLocation,914): Place {915 return {916 kind: 'Identifier',917 identifier: makeTemporaryIdentifier(env.nextIdentifierId, loc),918 reactive: false,919 effect: Effect.Unknown,920 loc: GeneratedSource,921 };922}923924/**925 * Clones an existing Place, returning a new temporary Place that shares the926 * same metadata properties as the original place (effect, reactive flag, type)927 * but has a new, temporary Identifier.928 */929export function clonePlaceToTemporary(env: Environment, place: Place): Place {930 const temp = createTemporaryPlace(env, place.loc);931 temp.effect = place.effect;932 temp.identifier.type = place.identifier.type;933 temp.reactive = place.reactive;934 return temp;935}936937/**938 * Fix scope and identifier ranges to account for renumbered instructions939 */940export function fixScopeAndIdentifierRanges(func: HIR): void {941 for (const [, block] of func.blocks) {942 const terminal = block.terminal;943 if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {944 /*945 * Scope ranges should always align to start at the 'scope' terminal946 * and end at the first instruction of the fallthrough block947 */948 const fallthroughBlock = func.blocks.get(terminal.fallthrough)!;949 const firstId =950 fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;951 terminal.scope.range.start = terminal.id;952 terminal.scope.range.end = firstId;953 }954 }955}
Findings
✓ No findings reported for this file.