2025-01-17 06:44:53 +00:00
|
|
|
import { Block, BlockId, Fn } from "./mir.ts";
|
2025-01-03 00:15:05 +00:00
|
|
|
|
|
|
|
export function createCfg(fn: Fn): Cfg {
|
|
|
|
return new CfgBuilder(fn).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Cfg {
|
|
|
|
public constructor(
|
|
|
|
private graph: Map<BlockId, CfgNode>,
|
2025-01-17 06:44:53 +00:00
|
|
|
private entry_: BlockId,
|
2025-01-03 00:15:05 +00:00
|
|
|
private exit: BlockId,
|
|
|
|
) {}
|
|
|
|
|
2025-01-17 06:44:53 +00:00
|
|
|
public entry(): Block {
|
|
|
|
return this.graph.get(this.entry_)!.block;
|
|
|
|
}
|
|
|
|
|
2025-01-03 00:15:05 +00:00
|
|
|
public parents(block: Block): Block[] {
|
|
|
|
return this.graph
|
|
|
|
.get(block.id)!.parents
|
|
|
|
.map((id) => this.graph.get(id)!.block);
|
|
|
|
}
|
|
|
|
|
|
|
|
public children(block: Block): Block[] {
|
|
|
|
return this.graph
|
|
|
|
.get(block.id)!.children
|
|
|
|
.map((id) => this.graph.get(id)!.block);
|
|
|
|
}
|
|
|
|
|
|
|
|
public index(block: Block): number {
|
|
|
|
return this.graph.get(block.id)!.index;
|
|
|
|
}
|
|
|
|
|
|
|
|
public print() {
|
|
|
|
for (const [id, node] of this.graph.entries()) {
|
|
|
|
const l = <T>(v: T[]) => v.map((v) => `${v}`).join(", ");
|
|
|
|
|
|
|
|
console.log(`graph[${id}] = {`);
|
|
|
|
console.log(` id: ${node.block.id},`);
|
|
|
|
console.log(` index: ${node.index},`);
|
|
|
|
console.log(` parents: [${l(node.parents)}],`);
|
|
|
|
console.log(` children: [${l(node.children)}],`);
|
|
|
|
console.log(`}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type CfgNode = {
|
|
|
|
block: Block;
|
|
|
|
index: number;
|
|
|
|
parents: BlockId[];
|
|
|
|
children: BlockId[];
|
|
|
|
};
|
|
|
|
|
|
|
|
class CfgBuilder {
|
|
|
|
private nodes: [Block, number][] = [];
|
|
|
|
private edges: [BlockId, BlockId][] = [];
|
|
|
|
|
|
|
|
public constructor(private fn: Fn) {}
|
|
|
|
|
|
|
|
public build(): Cfg {
|
|
|
|
for (
|
|
|
|
const [block, index] of this.fn.blocks
|
|
|
|
.map((v, i) => [v, i] as const)
|
|
|
|
) {
|
|
|
|
this.addNode(block, index);
|
|
|
|
|
|
|
|
const tk = block.ter.kind;
|
|
|
|
switch (tk.type) {
|
|
|
|
case "error":
|
|
|
|
break;
|
|
|
|
case "return":
|
|
|
|
break;
|
|
|
|
case "jump":
|
|
|
|
this.addEdge(block.id, tk.target);
|
|
|
|
break;
|
|
|
|
case "if":
|
|
|
|
this.addEdge(block.id, tk.truthy);
|
|
|
|
this.addEdge(block.id, tk.falsy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const graph = new Map<BlockId, CfgNode>();
|
|
|
|
for (const [block, index] of this.nodes) {
|
|
|
|
const parents = this.edges
|
|
|
|
.filter(([_from, to]) => to === block.id)
|
|
|
|
.map(([from, _to]) => from);
|
|
|
|
|
|
|
|
const children = this.edges
|
|
|
|
.filter(([from, _to]) => from === block.id)
|
|
|
|
.map(([_from, to]) => to);
|
|
|
|
|
|
|
|
graph.set(block.id, { block, index, parents, children });
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Cfg(graph, this.fn.entry, this.fn.exit);
|
|
|
|
}
|
|
|
|
|
|
|
|
private addNode(block: Block, index: number) {
|
|
|
|
this.nodes.push([block, index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private addEdge(from: BlockId, to: BlockId) {
|
|
|
|
this.edges.push([from, to]);
|
|
|
|
}
|
|
|
|
}
|