import { CompilerError } from "../CompilerError";
import { getScopes, recursivelyTraverseItems } from "./AssertValidBlockNesting";
import { Environment } from "./Environment";
import {
BasicBlock,
BlockId,
GeneratedSource,
GotoTerminal,
GotoVariant,
HIRFunction,
InstructionId,
ReactiveScope,
ReactiveScopeTerminal,
ScopeId,
} from "./HIR";
export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
const queuedRewrites: Array<TerminalRewriteInfo> = [];
recursivelyTraverseItems(
[...getScopes(fn)],
(scope) => scope.range,
{
fallthroughs: new Map(),
rewrites: queuedRewrites,
env: fn.env,
},
pushStartScopeTerminal,
pushEndScopeTerminal
);
const rewrittenFinalBlocks = new Map<BlockId, BlockId>();
const nextBlocks = new Map<BlockId, BasicBlock>();
queuedRewrites.reverse();
for (const [, block] of fn.body.blocks) {
const context: RewriteContext = {
nextBlockId: block.id,
rewrites: [],
nextPreds: block.preds,
instrSliceIdx: 0,
source: block,
};
for (let i = 0; i < block.instructions.length + 1; i++) {
const instrId =
i < block.instructions.length
? block.instructions[i].id
: block.terminal.id;
let rewrite = queuedRewrites.at(-1);
while (rewrite != null && rewrite.instrId <= instrId) {
handleRewrite(rewrite, i, context);
queuedRewrites.pop();
rewrite = queuedRewrites.at(-1);
}
}
if (context.rewrites.length > 0) {
const finalBlock: BasicBlock = {
id: context.nextBlockId,
kind: block.kind,
preds: context.nextPreds,
terminal: block.terminal,
instructions: block.instructions.slice(context.instrSliceIdx),
phis: new Set(),
};
context.rewrites.push(finalBlock);
for (const b of context.rewrites) {
nextBlocks.set(b.id, b);
}
rewrittenFinalBlocks.set(block.id, finalBlock.id);
} else {
nextBlocks.set(block.id, block);
}
}
const originalBlocks = fn.body.blocks;
fn.body.blocks = nextBlocks;
for (const [, block] of originalBlocks) {
for (const pred of block.preds) {
const newId = rewrittenFinalBlocks.get(pred);
if (newId != null) {
block.preds.delete(pred);
block.preds.add(newId);
}
}
for (const phi of block.phis) {
for (const [originalId, value] of phi.operands) {
const newId = rewrittenFinalBlocks.get(originalId);
if (newId != null) {
phi.operands.delete(originalId);
phi.operands.set(newId, value);
}
}
}
}
}
type TerminalRewriteInfo =
| {
kind: "StartScope";
blockId: BlockId;
fallthroughId: BlockId;
instrId: InstructionId;
scope: ReactiveScope;
}
| {
kind: "EndScope";
instrId: InstructionId;
fallthroughId: BlockId;
};
type ScopeTraversalContext = {
fallthroughs: Map<ScopeId, BlockId>;
rewrites: Array<TerminalRewriteInfo>;
env: Environment;
};
function pushStartScopeTerminal(
scope: ReactiveScope,
context: ScopeTraversalContext
): void {
const blockId = context.env.nextBlockId;
const fallthroughId = context.env.nextBlockId;
context.rewrites.push({
kind: "StartScope",
blockId,
fallthroughId,
instrId: scope.range.start,
scope,
});
context.fallthroughs.set(scope.id, fallthroughId);
}
function pushEndScopeTerminal(
scope: ReactiveScope,
context: ScopeTraversalContext
): void {
const fallthroughId = context.fallthroughs.get(scope.id);
CompilerError.invariant(fallthroughId != null, {
reason: "Expected scope to exist",
loc: GeneratedSource,
});
context.rewrites.push({
kind: "EndScope",
fallthroughId,
instrId: scope.range.end,
});
}
type RewriteContext = {
source: BasicBlock;
instrSliceIdx: number;
nextPreds: Set<BlockId>;
nextBlockId: BlockId;
rewrites: Array<BasicBlock>;
};
function handleRewrite(
terminalInfo: TerminalRewriteInfo,
idx: number,
context: RewriteContext
): void {
const terminal: ReactiveScopeTerminal | GotoTerminal =
terminalInfo.kind === "StartScope"
? {
kind: "scope",
fallthrough: terminalInfo.fallthroughId,
block: terminalInfo.blockId,
scope: terminalInfo.scope,
id: terminalInfo.instrId,
loc: GeneratedSource,
}
: {
kind: "goto",
variant: GotoVariant.Break,
block: terminalInfo.fallthroughId,
id: terminalInfo.instrId,
loc: GeneratedSource,
};
const currBlockId = context.nextBlockId;
context.rewrites.push({
kind: context.source.kind,
id: currBlockId,
instructions: context.source.instructions.slice(context.instrSliceIdx, idx),
preds: context.nextPreds,
phis: context.rewrites.length === 0 ? context.source.phis : new Set(),
terminal,
});
context.nextPreds = new Set([currBlockId]);
context.nextBlockId =
terminalInfo.kind === "StartScope"
? terminalInfo.blockId
: terminalInfo.fallthroughId;
context.instrSliceIdx = idx;
}