import {CompilerError} from '..';
import {
BasicBlock,
Environment,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
InstructionId,
Place,
isExpressionBlockKind,
makeInstructionId,
markInstructionIds,
} from '../HIR';
import {printInstruction} from '../HIR/PrintHIR';
import {
eachInstructionLValue,
eachInstructionValueLValue,
eachInstructionValueOperand,
eachTerminalOperand,
} from '../HIR/visitors';
import {getOrInsertWith} from '../Utils/utils';
export function instructionReordering(fn: HIRFunction): void {
const shared: Nodes = new Map();
const references = findReferencedRangeOfTemporaries(fn);
for (const [, block] of fn.body.blocks) {
reorderBlock(fn.env, block, shared, references);
}
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>;
reorderability: Reorderability;
depth: number | null;
};
type References = {
singleUseIdentifiers: SingleUseIdentifiers;
lastAssignments: LastAssignments;
};
type LastAssignments = Map<string, InstructionId>;
type SingleUseIdentifiers = Set<IdentifierId>;
enum ReferenceKind {
Read,
Write,
}
function findReferencedRangeOfTemporaries(fn: HIRFunction): References {
const singleUseIdentifiers = new Map<IdentifierId, number>();
const lastAssignments: LastAssignments = new Map();
function reference(
instr: InstructionId,
place: Place,
kind: ReferenceKind,
): void {
if (
place.identifier.name !== null &&
place.identifier.name.kind === 'named'
) {
if (kind === ReferenceKind.Write) {
const name = place.identifier.name.value;
const previous = lastAssignments.get(name);
if (previous === undefined) {
lastAssignments.set(name, instr);
} else {
lastAssignments.set(
name,
makeInstructionId(Math.max(previous, instr)),
);
}
}
return;
} else if (kind === ReferenceKind.Read) {
const previousCount = singleUseIdentifiers.get(place.identifier.id) ?? 0;
singleUseIdentifiers.set(place.identifier.id, previousCount + 1);
}
}
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
for (const operand of eachInstructionValueLValue(instr.value)) {
reference(instr.id, operand, ReferenceKind.Read);
}
for (const lvalue of eachInstructionLValue(instr)) {
reference(instr.id, lvalue, ReferenceKind.Write);
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
reference(block.terminal.id, operand, ReferenceKind.Read);
}
}
return {
singleUseIdentifiers: new Set(
[...singleUseIdentifiers]
.filter(([, count]) => count === 1)
.map(([id]) => id),
),
lastAssignments,
};
}
function reorderBlock(
env: Environment,
block: BasicBlock,
shared: Nodes,
references: References,
): 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 reorderability = getReorderability(instr, references);
const node = getOrInsertWith(
locals,
lvalue.identifier.id,
() =>
({
instruction: instr,
dependencies: new Set(),
reorderability,
depth: null,
}) as Node,
);
if (reorderability === 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 (isExpressionBlockKind(block.kind)) {
if (previous !== null) {
DEBUG && console.log(`(last non-reorderable instruction)`);
DEBUG && print(env, locals, shared, seen, previous);
emit(env, locals, shared, nextInstructions, previous);
}
if (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.reorderability === 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);
}
} else {
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 of Array.from(locals.keys()).reverse()) {
const node = locals.get(id);
if (node === undefined) {
continue;
}
if (node.reorderability === Reorderability.Reorderable) {
DEBUG && console.log(`save shared: $${id}`);
shared.set(id, node);
} else {
DEBUG && console.log('leftover');
DEBUG && print(env, locals, shared, seen, id);
emit(env, locals, shared, nextInstructions, id);
}
}
}
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.reorderability === Reorderability.Reorderable ? 1 : 10;
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)) {
DEBUG && 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);
}
DEBUG &&
console.log(
`${'| '.repeat(depth)}$${id} ${printNode(node)} deps=[${deps
.map(x => `$${x}`)
.join(', ')}] depth=${node.depth}`,
);
}
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 getReorderability(
instr: Instruction,
references: References,
): Reorderability {
switch (instr.value.kind) {
case 'JsxExpression':
case 'JsxFragment':
case 'JSXText':
case 'LoadGlobal':
case 'Primitive':
case 'TemplateLiteral':
case 'BinaryExpression':
case 'UnaryExpression': {
return Reorderability.Reorderable;
}
case 'LoadLocal': {
const name = instr.value.place.identifier.name;
if (name !== null && name.kind === 'named') {
const lastAssignment = references.lastAssignments.get(name.value);
if (
lastAssignment !== undefined &&
lastAssignment < instr.id &&
references.singleUseIdentifiers.has(instr.lvalue.identifier.id)
) {
return Reorderability.Reorderable;
}
}
return Reorderability.Nonreorderable;
}
default: {
return Reorderability.Nonreorderable;
}
}
}