From ad06f80f727cb84b5eb69be8cd592998cd0385ed Mon Sep 17 00:00:00 2001 From: sfja Date: Wed, 26 Mar 2025 23:09:32 +0100 Subject: [PATCH] compiler: optimize mir --- sbc/asm_gen.ts | 6 ++- sbc/lir_gen.ts | 2 + sbc/lir_optimize.ts | 16 +++----- sbc/main.ts | 4 +- sbc/mir.ts | 6 ++- sbc/mir_optimize.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++ sbc/ty.ts | 2 +- 7 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 sbc/mir_optimize.ts diff --git a/sbc/asm_gen.ts b/sbc/asm_gen.ts index dba7b90..464d203 100644 --- a/sbc/asm_gen.ts +++ b/sbc/asm_gen.ts @@ -132,13 +132,15 @@ export class AsmGen { } private generateFnBody(fn: lir.Fn) { + let bodyIdx = 0; const allocator = new StackAllocator(); - for (const { ins } of fn.lines) { + for (const [i, { ins }] of fn.lines.entries()) { if (ins.tag === "alloc_param") { allocator.allocParam(ins.reg, ins.size); } else if (ins.tag === "alloc_local") { allocator.allocLocal(ins.reg, ins.size); } else { + bodyIdx = i; break; } } @@ -154,7 +156,7 @@ export class AsmGen { this.writeIns(`sub rsp, ${this.layout.frameSize}`); this.writeIns(`jmp .L${fn.mir.entry.id}`); - for (const line of fn.lines) { + for (const line of fn.lines.slice(bodyIdx)) { for (const label of line.labels) { this.writeln(`.L${label}:`); } diff --git a/sbc/lir_gen.ts b/sbc/lir_gen.ts index aa9231b..49952b3 100644 --- a/sbc/lir_gen.ts +++ b/sbc/lir_gen.ts @@ -10,6 +10,7 @@ import { import { MirGen } from "./mir_gen.ts"; import * as ast from "./ast.ts"; import * as mir from "./mir.ts"; +import { optimizeMirFn } from "./mir_optimize.ts"; export class LirGen { private strings = new StringIntern(); @@ -29,6 +30,7 @@ export class LirGen { throw new Error("only functions can compile top level"); } const mir = this.mirGen.fnMir(stmt, stmt.kind); + optimizeMirFn(mir); const id = this.fnIds++; const label = `sbc__${stmt.kind.ident}`; const fn: Fn = { diff --git a/sbc/lir_optimize.ts b/sbc/lir_optimize.ts index d4d47fa..3b227bc 100644 --- a/sbc/lir_optimize.ts +++ b/sbc/lir_optimize.ts @@ -8,18 +8,17 @@ import { Reg, } from "./lir.ts"; -export function lirOptimize(program: Program) { - console.log("=== BEFORE OPTIMIZATION ==="); - console.log(new ProgramStringifyer(program).stringify()); +export function optimizeLir(program: Program) { + //console.log("=== BEFORE OPTIMIZATION ==="); + //console.log(new ProgramStringifyer(program).stringify()); - let changed = true; let sizeBefore = program.fns .reduce((acc, fn) => acc + fn.lines.length, 0); const sizeHistory = new Set([sizeBefore]); let repeats = 0; - while (changed && repeats < 3) { + while (repeats < 1) { for (const fn of program.fns) { eliminatePushPop(fn); eliminateMovFnCall(fn); @@ -28,9 +27,6 @@ export function lirOptimize(program: Program) { } const sizeAfter = program.fns .reduce((acc, fn) => acc + fn.lines.length, 0); - if (sizeAfter !== sizeBefore) { - changed = true; - } sizeBefore = sizeAfter; if (sizeHistory.has(sizeBefore)) { repeats += 1; @@ -38,8 +34,8 @@ export function lirOptimize(program: Program) { sizeHistory.add(sizeBefore); } - console.log("=== AFTER OPTIMIZATION ==="); - console.log(new ProgramStringifyer(program).stringify()); + //console.log("=== AFTER OPTIMIZATION ==="); + //console.log(new ProgramStringifyer(program).stringify()); } function eliminatePushPop(fn: Fn) { diff --git a/sbc/main.ts b/sbc/main.ts index 61d4dd4..568279b 100644 --- a/sbc/main.ts +++ b/sbc/main.ts @@ -5,7 +5,7 @@ import { FnStringifyer } from "./mir.ts"; import { LirGen } from "./lir_gen.ts"; import { ProgramStringifyer } from "./lir.ts"; import { AsmGen } from "./asm_gen.ts"; -import { lirOptimize } from "./lir_optimize.ts"; +import { optimizeLir } from "./lir_optimize.ts"; async function main() { const text = await Deno.readTextFile(Deno.args[0]); @@ -32,7 +32,7 @@ async function main() { // console.log("=== LIR ==="); // console.log(new ProgramStringifyer(lir).stringify()); - lirOptimize(lir); + optimizeLir(lir); const asm = new AsmGen(lir).generate(); // console.log("=== ASM ==="); diff --git a/sbc/mir.ts b/sbc/mir.ts index a0b9609..1fd221f 100644 --- a/sbc/mir.ts +++ b/sbc/mir.ts @@ -73,7 +73,11 @@ export class FnStringifyer { } return `${kind.ident}:\n${ this.fn.locals - .map((local) => ` %${local.id}: ${tyToString(local.ty)}\n`) + .map((local) => + ` %${local.id} :: ${tyToString(local.ty)}${ + local.ident ? ` '${local.ident}'` : "" + }\n` + ) .join("") }${ this.fn.blocks diff --git a/sbc/mir_optimize.ts b/sbc/mir_optimize.ts new file mode 100644 index 0000000..4a6def5 --- /dev/null +++ b/sbc/mir_optimize.ts @@ -0,0 +1,97 @@ +import * as ast from "./ast.ts"; +import { Block, Fn, FnStringifyer } from "./mir.ts"; + +export function optimizeMirFn(fn: Fn) { + //console.log(`=== OPTIMIZING ${(fn.stmt.kind as ast.FnStmt).ident} ===`); + //console.log("=== BEFORE OPTIMIZATION ==="); + //console.log(new FnStringifyer(fn).stringify()); + + const blockSize = fn.blocks + .map((block) => block.stmts.length) + .toSorted() + .at(-1)! + 1; + let sizeBefore = fnSize(fn, blockSize); + + const sizeHistory = new Set([sizeBefore]); + let repeats = 0; + + while (repeats < 1) { + eliminateUnreachable(fn); + joinSequentialBlocks(fn); + + const sizeAfter = fnSize(fn, blockSize); + sizeBefore = sizeAfter; + if (sizeHistory.has(sizeBefore)) { + repeats += 1; + } + sizeHistory.add(sizeBefore); + } + + //console.log("=== AFTER OPTIMIZATION ==="); + //console.log(new FnStringifyer(fn).stringify()); +} + +function fnSize(fn: Fn, blockSize: number): number { + return fn.blocks + .reduce((acc, block) => acc + blockSize + block.stmts.length, 0); +} + +function eliminateUnreachable(fn: Fn) { + const toRemove = new Set(); + + for (const block of fn.blocks) { + const preds = cfgPredecessors(fn, block); + + if (block.id === fn.entry.id || preds.length !== 0) { + continue; + } + toRemove.add(block.id); + } + + fn.blocks = fn.blocks + .filter((block) => !toRemove.has(block.id)); +} + +function joinSequentialBlocks(fn: Fn) { + const toRemove = new Set(); + + for (const first of fn.blocks) { + const firstSuccs = cfgSuccessors(first); + if (firstSuccs.length !== 1) { + continue; + } + const [second] = firstSuccs; + const secondPreds = cfgPredecessors(fn, second); + if (secondPreds.length !== 1) { + continue; + } + first.stmts.push(...second.stmts); + first.ter = second.ter; + toRemove.add(second.id); + if (second.id === fn.exit.id) { + fn.exit = first; + } + } + + fn.blocks = fn.blocks + .filter((block) => !toRemove.has(block.id)); +} + +function cfgPredecessors(fn: Fn, block: Block): Block[] { + return fn.blocks + .filter((b) => cfgSuccessors(b).some((s) => s.id === block.id)); +} + +function cfgSuccessors(block: Block): Block[] { + const tk = block.ter.kind; + switch (tk.tag) { + case "error": + case "unset": + case "return": + return []; + case "goto": + return [tk.target]; + case "if": + return [tk.truthy, tk.falsy]; + } +} diff --git a/sbc/ty.ts b/sbc/ty.ts index 08b8886..8d9054b 100644 --- a/sbc/ty.ts +++ b/sbc/ty.ts @@ -23,7 +23,7 @@ export function tyToString(ty: Ty): string { case "fn": { const k = ty.stmt.kind as ast.StmtKind & { tag: "fn" }; const params = ty.params - .map((param, i) => `${k.params[i]}: ${tyToString(param)}`) + .map((param, i) => `${k.params[i].ident}: ${tyToString(param)}`) .join(", "); const returnTy = tyToString(ty.returnTy); return `fn ${k.ident}(${params}) -> ${returnTy}`;