import {CompilerError} from '../CompilerError';
import {
BlockId,
Effect,
GeneratedSource,
HIRFunction,
Instruction,
} from './HIR';
import {markPredecessors} from './HIRBuilder';
import {terminalFallthrough, terminalHasFallthrough} from './visitors';
export function mergeConsecutiveBlocks(fn: HIRFunction): void {
const merged = new MergedBlocks();
const fallthroughBlocks = new Set<BlockId>();
for (const [, block] of fn.body.blocks) {
const fallthrough = terminalFallthrough(block.terminal);
if (fallthrough !== null) {
fallthroughBlocks.add(fallthrough);
}
for (const instr of block.instructions) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
mergeConsecutiveBlocks(instr.value.loweredFunc.func);
}
}
if (
block.preds.size !== 1 ||
block.kind !== 'block' ||
fallthroughBlocks.has(block.id)
) {
continue;
}
const originalPredecessorId = Array.from(block.preds)[0]!;
const predecessorId = merged.get(originalPredecessorId);
const predecessor = fn.body.blocks.get(predecessorId);
CompilerError.invariant(predecessor !== undefined, {
reason: `Expected predecessor ${predecessorId} to exist`,
description: null,
loc: null,
suggestions: null,
});
if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
continue;
}
for (const phi of block.phis) {
CompilerError.invariant(phi.operands.size === 1, {
reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
description: null,
loc: null,
suggestions: null,
});
const operand = Array.from(phi.operands.values())[0]!;
const instr: Instruction = {
id: predecessor.terminal.id,
lvalue: {
kind: 'Identifier',
identifier: phi.id,
effect: Effect.ConditionallyMutate,
reactive: false,
loc: GeneratedSource,
},
value: {
kind: 'LoadLocal',
place: {
kind: 'Identifier',
identifier: operand,
effect: Effect.Read,
reactive: false,
loc: GeneratedSource,
},
loc: GeneratedSource,
},
loc: GeneratedSource,
};
predecessor.instructions.push(instr);
}
predecessor.instructions.push(...block.instructions);
predecessor.terminal = block.terminal;
merged.merge(block.id, predecessorId);
fn.body.blocks.delete(block.id);
}
markPredecessors(fn.body);
for (const [, {terminal}] of fn.body.blocks) {
if (terminalHasFallthrough(terminal)) {
terminal.fallthrough = merged.get(terminal.fallthrough);
}
}
}
class MergedBlocks {
#map: Map<BlockId, BlockId> = new Map();
merge(block: BlockId, into: BlockId): void {
const target = this.get(into);
this.#map.set(block, target);
}
get(block: BlockId): BlockId {
let current = block;
while (this.#map.has(current)) {
current = this.#map.get(current) ?? current;
}
return current;
}
}