mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Mechanical PR to migrate existing invariants to use the new CompilerDiagnostic infra @josephsavona added. Will tackle the others at a later time. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34403). * #34409 * #34404 * __->__ #34403
312 lines
8.6 KiB
TypeScript
312 lines
8.6 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|