import { CompilerError } from "..";
import {
BasicBlock,
Environment,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
isExpressionBlockKind,
markInstructionIds,
} from "../HIR";
import { printInstruction } from "../HIR/PrintHIR";
import {
eachInstructionValueLValue,
eachInstructionValueOperand,
eachTerminalOperand,
} from "../HIR/visitors";
import { mayAllocate } from "../ReactiveScopes/InferReactiveScopeVariables";
import { getOrInsertWith } from "../Utils/utils";
export function instructionReordering(fn: HIRFunction): void {
const shared: Nodes = new Map();
for (const [, block] of fn.body.blocks) {
reorderBlock(fn.env, block, shared);
}
CompilerError.invariant(shared.size === 0, {
reason: `InstructionReordering: expected all reorderable nodes to have been emitted`,
loc:
[...shared.values()]
.map((node) => node.instruction?.loc)
.filter((loc) => loc != null)[0] ?? GeneratedSource,
});
markInstructionIds(fn.body);
}
const DEBUG = false;
type Nodes = Map<IdentifierId, Node>;
type Node = {
instruction: Instruction | null;
dependencies: Set<IdentifierId>;
depth: number | null;
};
function reorderBlock(
env: Environment,
block: BasicBlock,
shared: Nodes
): void {
const locals: Nodes = new Map();
const named: Map<string, IdentifierId> = new Map();
let previous: IdentifierId | null = null;
for (const instr of block.instructions) {
const { lvalue, value } = instr;
const node = getOrInsertWith(
locals,
lvalue.identifier.id,
() =>
({
instruction: instr,
dependencies: new Set(),
depth: null,
}) as Node
);
if (getReoderability(instr) === Reorderability.Nonreorderable) {
if (previous !== null) {
node.dependencies.add(previous);
}
previous = lvalue.identifier.id;
}
for (const operand of eachInstructionValueOperand(value)) {
const { name, id } = operand.identifier;
if (name !== null && name.kind === "named") {
const previous = named.get(name.value);
if (previous !== undefined) {
node.dependencies.add(previous);
}
named.set(name.value, lvalue.identifier.id);
} else if (locals.has(id) || shared.has(id)) {
node.dependencies.add(id);
}
}
for (const lvalueOperand of eachInstructionValueLValue(value)) {
const lvalueNode = getOrInsertWith(
locals,
lvalueOperand.identifier.id,
() =>
({
instruction: null,
dependencies: new Set(),
depth: null,
}) as Node
);
lvalueNode.dependencies.add(lvalue.identifier.id);
const name = lvalueOperand.identifier.name;
if (name !== null && name.kind === "named") {
const previous = named.get(name.value);
if (previous !== undefined) {
node.dependencies.add(previous);
}
named.set(name.value, lvalue.identifier.id);
}
}
}
const nextInstructions: Array<Instruction> = [];
const seen = new Set<IdentifierId>();
DEBUG && console.log(`bb${block.id}`);
if (previous !== null) {
DEBUG && console.log(`(last non-reorderable instruction)`);
DEBUG && print(env, locals, shared, seen, previous);
emit(env, locals, shared, nextInstructions, previous);
}
if (isExpressionBlockKind(block.kind) && block.instructions.length !== 0) {
DEBUG && console.log(`(block value)`);
DEBUG &&
print(
env,
locals,
shared,
seen,
block.instructions.at(-1)!.lvalue.identifier.id
);
emit(
env,
locals,
shared,
nextInstructions,
block.instructions.at(-1)!.lvalue.identifier.id
);
}
for (const operand of eachTerminalOperand(block.terminal)) {
DEBUG && console.log(`(terminal operand)`);
DEBUG && print(env, locals, shared, seen, operand.identifier.id);
emit(env, locals, shared, nextInstructions, operand.identifier.id);
}
for (const [id, node] of locals) {
if (node.instruction == null) {
continue;
}
CompilerError.invariant(
node.instruction != null &&
getReoderability(node.instruction) === Reorderability.Reorderable,
{
reason: `Expected all remaining instructions to be reorderable`,
loc: node.instruction?.loc ?? block.terminal.loc,
description:
node.instruction != null
? `Instruction [${node.instruction.id}] was not emitted yet but is not reorderable`
: `Lvalue $${id} was not emitted yet but is not reorderable`,
}
);
DEBUG && console.log(`save shared: $${id}`);
shared.set(id, node);
}
block.instructions = nextInstructions;
DEBUG && console.log();
}
function getDepth(env: Environment, nodes: Nodes, id: IdentifierId): number {
const node = nodes.get(id)!;
if (node == null) {
return 0;
}
if (node.depth != null) {
return node.depth;
}
node.depth = 0;
let depth =
node.instruction != null && mayAllocate(env, node.instruction) ? 1 : 0;
for (const dep of node.dependencies) {
depth += getDepth(env, nodes, dep);
}
node.depth = depth;
return depth;
}
function print(
env: Environment,
locals: Nodes,
shared: Nodes,
seen: Set<IdentifierId>,
id: IdentifierId,
depth: number = 0
): void {
if (seen.has(id)) {
console.log(`${"| ".repeat(depth)}$${id} <skipped>`);
return;
}
seen.add(id);
const node = locals.get(id) ?? shared.get(id);
if (node == null) {
return;
}
const deps = [...node.dependencies];
deps.sort((a, b) => {
const aDepth = getDepth(env, locals, a);
const bDepth = getDepth(env, locals, b);
return bDepth - aDepth;
});
for (const dep of deps) {
print(env, locals, shared, seen, dep, depth + 1);
}
console.log(
`${"| ".repeat(depth)}$${id} ${printNode(node)} deps=[${deps.map((x) => `$${x}`).join(", ")}]`
);
}
function printNode(node: Node): string {
const { instruction } = node;
if (instruction === null) {
return "<lvalue-only>";
}
switch (instruction.value.kind) {
case "FunctionExpression":
case "ObjectMethod": {
return `[${instruction.id}] ${instruction.value.kind}`;
}
default: {
return printInstruction(instruction);
}
}
}
function emit(
env: Environment,
locals: Nodes,
shared: Nodes,
instructions: Array<Instruction>,
id: IdentifierId
): void {
const node = locals.get(id) ?? shared.get(id);
if (node == null) {
return;
}
locals.delete(id);
shared.delete(id);
const deps = [...node.dependencies];
deps.sort((a, b) => {
const aDepth = getDepth(env, locals, a);
const bDepth = getDepth(env, locals, b);
return bDepth - aDepth;
});
for (const dep of deps) {
emit(env, locals, shared, instructions, dep);
}
if (node.instruction !== null) {
instructions.push(node.instruction);
}
}
enum Reorderability {
Reorderable,
Nonreorderable,
}
function getReoderability(instr: Instruction): Reorderability {
switch (instr.value.kind) {
case "JsxExpression":
case "JsxFragment":
case "JSXText":
case "LoadGlobal":
case "Primitive":
case "TemplateLiteral":
case "BinaryExpression":
case "UnaryExpression": {
return Reorderability.Reorderable;
}
default: {
return Reorderability.Nonreorderable;
}
}
}