/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
compiler/packages/babel-plugin-react-compiler/src/HIR/Dominator.ts
311 строк
9 KB
lauren
[compiler] Migrate CompilerError.invariant to new CompilerDiagnostic infra (#34403)
06 сен 2025, 19:58
Не верифицирован
06 сен 2025, 19:58
474f258
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import prettyFormat from 'pretty-format'; import {CompilerError} from '../CompilerError'; import {BlockId, HIRFunction} from './HIR'; import {eachTerminalSuccessor} from './visitors'; /* * Computes the dominator tree of the given function. The returned `Dominator` stores the immediate * dominator of each node in the function, which can be retrieved with `Dominator.prototype.get()`. * * A block X dominates block Y in the CFG if all paths to Y must flow through X. Thus the entry * block dominates all other blocks. See https://en.wikipedia.org/wiki/Dominator_(graph_theory) * for more. */ export function computeDominatorTree(fn: HIRFunction): Dominator<BlockId> { const graph = buildGraph(fn); const nodes = computeImmediateDominators(graph); return new Dominator(graph.entry, nodes); } /* * Similar to `computeDominatorTree()` but computes the post dominators of the function. The returned * `PostDominator` stores the immediate post-dominators of each node in the function. * * A block Y post-dominates block X in the CFG if all paths from X to the exit must flow through Y. * The caller must specify whether to consider `throw` statements as exit nodes. If set to false, * only return statements are considered exit nodes. */ export function computePostDominatorTree( fn: HIRFunction, options: {includeThrowsAsExitNode: boolean}, ): PostDominator<BlockId> { const graph = buildReverseGraph(fn, options.includeThrowsAsExitNode); const nodes = computeImmediateDominators(graph); /* * When options.includeThrowsAsExitNode is false, nodes that flow into a throws * terminal and don't reach the exit node won't be in the node map. Add them * with themselves as dominator to reflect that they don't flow into the exit. */ if (!options.includeThrowsAsExitNode) { for (const [id] of fn.body.blocks) { if (!nodes.has(id)) { nodes.set(id, id); } } } return new PostDominator(graph.entry, nodes); } type Node<T> = { id: T; index: number; preds: Set<T>; succs: Set<T>; }; type Graph<T> = { entry: T; nodes: Map<T, Node<T>>; }; // A dominator tree that stores the immediate dominator for each block in function. export class Dominator<T> { #entry: T; #nodes: Map<T, T>; constructor(entry: T, nodes: Map<T, T>) { this.#entry = entry; this.#nodes = nodes; } // Returns the entry node get entry(): T { return this.#entry; } /* * Returns the immediate dominator of the block with @param id if present. Returns null * if there is no immediate dominator (ie if the dominator is @param id itself). */ get(id: T): T | null { const dominator = this.#nodes.get(id); CompilerError.invariant(dominator !== undefined, { reason: 'Unknown node', description: null, details: [ { kind: 'error', loc: null, message: null, }, ], suggestions: null, }); return dominator === id ? null : dominator; } debug(): string { const dominators = new Map(); for (const [key, value] of this.#nodes) { dominators.set(`bb${key}`, `bb${value}`); } return prettyFormat({ entry: `bb${this.#entry}`, dominators, }); } } export class PostDominator<T> { #exit: T; #nodes: Map<T, T>; constructor(exit: T, nodes: Map<T, T>) { this.#exit = exit; this.#nodes = nodes; } // Returns the node representing normal exit from the function, ie return terminals. get exit(): T { return this.#exit; } /* * Returns the immediate dominator of the block with @param id if present. Returns null * if there is no immediate dominator (ie if the dominator is @param id itself). */ get(id: T): T | null { const dominator = this.#nodes.get(id); CompilerError.invariant(dominator !== undefined, { reason: 'Unknown node', description: null, details: [ { kind: 'error', loc: null, message: null, }, ], suggestions: null, }); return dominator === id ? null : dominator; } debug(): string { const postDominators = new Map(); for (const [key, value] of this.#nodes) { postDominators.set(`bb${key}`, `bb${value}`); } return prettyFormat({ exit: `bb${this.exit}`, postDominators, }); } } /* * The implementation is a straightforward adaptation of https://www.cs.rice.edu/~keith/Embed/dom.pdf * except that CFG nodes ordering is inverted (so the comparison functions are swapped) */ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> { const nodes: Map<T, T> = new Map(); nodes.set(graph.entry, graph.entry); let changed = true; while (changed) { changed = false; for (const [id, node] of graph.nodes) { // Skip start node if (node.id === graph.entry) { continue; } // first processed predecessor let newIdom: T | null = null; for (const pred of node.preds) { if (nodes.has(pred)) { newIdom = pred; break; } } CompilerError.invariant(newIdom !== null, { reason: `At least one predecessor must have been visited for block ${id}`, description: null, details: [ { kind: 'error', loc: null, message: null, }, ], suggestions: null, }); for (const pred of node.preds) { // For all other predecessors if (pred === newIdom) { continue; } const predDom = nodes.get(pred); if (predDom !== undefined) { newIdom = intersect(pred, newIdom, graph, nodes); } } if (nodes.get(id) !== newIdom) { nodes.set(id, newIdom); changed = true; } } } return nodes; } function intersect<T>(a: T, b: T, graph: Graph<T>, nodes: Map<T, T>): T { let block1: Node<T> = graph.nodes.get(a)!; let block2: Node<T> = graph.nodes.get(b)!; while (block1 !== block2) { while (block1.index > block2.index) { const dom = nodes.get(block1.id)!; block1 = graph.nodes.get(dom)!; } while (block2.index > block1.index) { const dom = nodes.get(block2.id)!; block2 = graph.nodes.get(dom)!; } } return block1.id; } // Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation function buildGraph(fn: HIRFunction): Graph<BlockId> { const graph: Graph<BlockId> = {entry: fn.body.entry, nodes: new Map()}; let index = 0; for (const [id, block] of fn.body.blocks) { graph.nodes.set(id, { id, index: index++, preds: block.preds, succs: new Set(eachTerminalSuccessor(block.terminal)), }); } return graph; } /* * Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation, * notably this version flips the graph and puts the reversed form back into RPO (such that successors are before predecessors). * Note that RPO of the reversed graph isn't the same as reversed RPO of the forward graph because of loops. */ function buildReverseGraph( fn: HIRFunction, includeThrowsAsExitNode: boolean, ): Graph<BlockId> { const nodes: Map<BlockId, Node<BlockId>> = new Map(); const exitId = fn.env.nextBlockId; const exit: Node<BlockId> = { id: exitId, index: 0, preds: new Set(), succs: new Set(), }; nodes.set(exitId, exit); for (const [id, block] of fn.body.blocks) { const node: Node<BlockId> = { id, index: 0, preds: new Set(eachTerminalSuccessor(block.terminal)), succs: new Set(block.preds), }; if (block.terminal.kind === 'return') { node.preds.add(exitId); exit.succs.add(id); } else if (block.terminal.kind === 'throw' && includeThrowsAsExitNode) { node.preds.add(exitId); exit.succs.add(id); } nodes.set(id, node); } // Put nodes into RPO form const visited = new Set<BlockId>(); const postorder: Array<BlockId> = []; function visit(id: BlockId): void { if (visited.has(id)) { return; } visited.add(id); const node = nodes.get(id)!; for (const successor of node.succs) { visit(successor); } postorder.push(id); } visit(exitId); const rpo: Graph<BlockId> = {entry: exitId, nodes: new Map()}; let index = 0; for (const id of postorder.reverse()) { const node = nodes.get(id)!; node.index = index++; rpo.nodes.set(id, node); } return rpo; }