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';
import {
fixScopeAndIdentifierRanges,
markInstructionIds,
markPredecessors,
reversePostorderBlocks,
} from './HIRBuilder';
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 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);
}
}
}
}
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
}
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;
}