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<number>();
+
+    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<number>();
+
+    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}`;