diff --git a/backup-compiler/.clang-format b/backup-compiler/.clang-format new file mode 100644 index 0000000..a56cbd0 --- /dev/null +++ b/backup-compiler/.clang-format @@ -0,0 +1,14 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true + diff --git a/backup-compiler/.gitignore b/backup-compiler/.gitignore new file mode 100644 index 0000000..bbc4cc4 --- /dev/null +++ b/backup-compiler/.gitignore @@ -0,0 +1,4 @@ +out.nasm +*.o +out + diff --git a/backup-compiler/Makefile b/backup-compiler/Makefile new file mode 100644 index 0000000..c783d04 --- /dev/null +++ b/backup-compiler/Makefile @@ -0,0 +1,18 @@ + +all: out + +out: entry.o out.o lib.o + gcc $^ -o $@ -no-pie + +%.o: %.c + gcc -c -o $@ -std=c17 -Wall -Wextra -Wpedantic -pedantic -pedantic-errors $^ + +%.o: %.nasm + nasm -f elf64 $< -o $@ + +out.nasm: program.sbl + deno run --allow-read --allow-write --check main.ts $< + +clean: + rm -rf out.asm out.o lib.o entry.o out + diff --git a/backup-compiler/asm_gen.ts b/backup-compiler/asm_gen.ts index 381a5b3..23814a9 100644 --- a/backup-compiler/asm_gen.ts +++ b/backup-compiler/asm_gen.ts @@ -25,17 +25,28 @@ export class AsmGen { } this.writeln(`section .text`); for (const fn of this.lir.fns) { - this.writeln(`align 8`); - this.writeln(`global ${fn.label}:`); - this.writeln(`${fn.label}:`); - - this.generateFnBody(fn); + this.generateFn(fn); } + this.writeln(`; vim: syntax=nasm commentstring=;\\ %s`); + this.writeln(""); return this.writer.finalize(); } - private generateFnBody(fn: lir.Fn) { + private generateFn(fn: lir.Fn) { + const query = this.queryCFunction(fn); + if (query.found) { + const { label, args } = query; + this.generateCFunctionBody(fn, label, args); + return; + } + + this.generateFnBody(fn); + } + + private queryCFunction( + fn: lir.Fn, + ): { found: false } | { found: true; label: string; args: number } { const stmtKind = fn.mir.stmt.kind; if (stmtKind.tag !== "fn") { throw new Error(); @@ -45,12 +56,41 @@ export class AsmGen { if (!arg || arg.kind.tag !== "string") { throw new Error("incorrect args for attribute"); } - const label = arg.kind.val; - this.generateCFunctionBody(label, fn.mir.paramLocals.size); - return; + return { + found: true, + label: arg.kind.val, + args: fn.mir.paramLocals.size, + }; + } + return { found: false }; + } + + private generateCFunctionBody(fn: lir.Fn, label: string, args: number) { + this.writeln(`extern ${label}`); + this.writeln(`global ${fn.label}`); + this.writeln(`${fn.label}:`); + + this.writeIns(`push rbp`); + this.writeIns(`mov rbp, rsp`); + for (let i = 0; i < args; ++i) { + this.writeIns(`mov rax, ${this.relative((i + 2) * 8)}`); + this.writeIns(`push rax`); } - this.writeIns(`push r12`); + const argRegs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; + for (const reg of argRegs.slice(0, args + 1)) { + this.writeIns(`pop ${reg}`); + } + + this.writeIns(`call ${label}`); + this.writeIns(`mov rsp, rbp`); + this.writeIns(`pop rbp`); + this.writeIns(`ret`); + } + + private generateFnBody(fn: lir.Fn) { + this.writeln(`global ${fn.label}:`); + this.writeln(`${fn.label}:`); this.writeIns(`push rbp`); this.writeIns(`mov rbp, rsp`); this.writeIns(`sub rsp, ${fn.frameSize}`); @@ -64,30 +104,11 @@ export class AsmGen { } this.writeln(`.exit:`); + const returnLocalOffset = fn.localOffsets.get(fn.mir.returnLocal.id)!; + this.writeIns(`mov rax, QWORD ${this.relative(returnLocalOffset)}`); this.writeIns(`mov rsp, rbp`); this.writeIns(`pop rbp`); - this.writeIns(`pop r12`); - } - - private generateCFunctionBody(label: string, args: number) { - const argRegs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - const returnReg = "rax"; - if (args > argRegs.length) { - throw new Error( - `arg count (${args}) > ${argRegs.length} not supported`, - ); - } - this.writeIns(`push ${returnReg}`); - for (const reg of argRegs.slice(0, args + 1)) { - this.writeIns(`push ${reg}`); - } - this.writeIns(`call ${label}`); - this.writeIns(`mov r12, rax`); - for (const reg of argRegs.slice(0, args + 1).toReversed()) { - this.writeIns(`push ${reg}`); - } - this.writeIns(`pop ${returnReg}`); - this.writeIns(`push r12`); + this.writeIns(`ret`); } private generateIns(ins: lir.Ins) { @@ -115,16 +136,27 @@ export class AsmGen { this.writeIns(`pop ${r(ins.reg)}`); return; case "load": - this.writeIns(`mov QWORD [rbp${ins.offset}], ${r(ins.reg)}`); + this.writeIns( + `mov ${r(ins.reg)}, QWORD ${this.relative(ins.offset)}`, + ); return; - case "store": - this.writeIns(`mov ${r(ins.reg)}, QWORD [rbp${ins.offset}]`); + case "store_reg": + this.writeIns( + `mov QWORD ${this.relative(ins.offset)}, ${r(ins.reg)}`, + ); + return; + case "store_imm": + this.writeIns( + `mov QWORD ${this.relative(ins.offset)}, ${ins.val}`, + ); return; case "call_reg": - this.generateCall(r(ins.reg), ins.args); + this.writeIns(`call ${r(ins.reg)}`); + this.writeIns(`push rax`); return; - case "call_fn": - this.generateCall(ins.fn.label, ins.args); + case "call_imm": + this.writeIns(`call ${ins.fn.label}`); + this.writeIns(`push rax`); return; case "jmp": this.writeIns(`jmp .L${ins.target}`); @@ -153,27 +185,7 @@ export class AsmGen { this.kill(ins.reg); return; } - } - - private generateCall(value: string, args: number) { - const argRegs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - const returnReg = "rax"; - if (args > argRegs.length) { - throw new Error( - `arg count (${args}) > ${argRegs.length} not supported`, - ); - } - this.writeIns(`push ${returnReg}`); - for (const reg of argRegs.slice(0, args + 1)) { - this.writeIns(`push ${reg}`); - } - this.writeIns(`call ${value}`); - this.writeIns(`mov r12, rax`); - for (const reg of argRegs.slice(0, args + 1).toReversed()) { - this.writeIns(`push ${reg}`); - } - this.writeIns(`pop ${returnReg}`); - this.writeIns(`push r12`); + const _: never = ins; } private reg(reg: lir.Reg): string { @@ -198,7 +210,6 @@ export class AsmGen { private allocReg(reg: lir.Reg) { if (!this.liveRegs.has(reg)) { - this.liveRegs.add(reg); const regSel = [ "rax", "rdi", @@ -213,10 +224,15 @@ export class AsmGen { if (!regSel) { throw new Error("ran out of registers"); } + this.liveRegs.add(reg); this.regSelect.set(reg, regSel); } } + private relative(offset: number): string { + return `[rbp${offset >= 0 ? `+${offset}` : `${offset}`}]`; + } + private kill(reg: lir.Reg) { this.liveRegs.delete(reg); this.regSelect.delete(reg); diff --git a/backup-compiler/entry.c b/backup-compiler/entry.c new file mode 100644 index 0000000..ddc6bc3 --- /dev/null +++ b/backup-compiler/entry.c @@ -0,0 +1,8 @@ +#include + +extern int32_t sbc__main(void); + +int main(void) +{ + sbc__main(); +} diff --git a/backup-compiler/lib.c b/backup-compiler/lib.c new file mode 100644 index 0000000..5262dda --- /dev/null +++ b/backup-compiler/lib.c @@ -0,0 +1,8 @@ +#include +#include + +int64_t print_int(int64_t value) +{ + printf("%ld\n", value); + return 0; +} diff --git a/backup-compiler/lir.ts b/backup-compiler/lir.ts index 38bb9a3..0f447b4 100644 --- a/backup-compiler/lir.ts +++ b/backup-compiler/lir.ts @@ -11,6 +11,7 @@ export type Fn = { mir: mir.Fn; lines: Line[]; frameSize: number; + localOffsets: Map; }; export type Line = { @@ -27,9 +28,10 @@ export type Ins = | { tag: "push"; reg: Reg } | { tag: "pop"; reg: Reg } | { tag: "load"; reg: Reg; offset: number } - | { tag: "store"; offset: number; reg: Reg } + | { tag: "store_reg"; offset: number; reg: Reg } + | { tag: "store_imm"; offset: number; val: number } | { tag: "call_reg"; reg: Reg; args: number } - | { tag: "call_fn"; fn: Fn; args: number } + | { tag: "call_imm"; fn: Fn; args: number } | { tag: "jmp"; target: Label } | { tag: "jnz_reg"; reg: Reg; target: Label } | { tag: "ret" } @@ -52,7 +54,7 @@ export class ProgramStringifyer { .map((label) => `${ label.labels - .map((label) => `.${label}:\n`) + .map((label) => `.L${label}:\n`) .join() } ${this.ins(label.ins)}\n` ) @@ -80,11 +82,13 @@ export class ProgramStringifyer { return `pop %${ins.reg}`; case "load": return `load %${ins.reg}, ${ins.offset}`; - case "store": - return `store ${ins.offset}, %${ins.reg}`; + case "store_reg": + return `store_reg ${ins.offset}, %${ins.reg}`; + case "store_imm": + return `store_val ${ins.offset}, ${ins.val}`; case "call_reg": return `call_reg %${ins.reg}, ${ins.args}`; - case "call_fn": + case "call_imm": return `call_fn ${ins.fn.label}, ${ins.args}`; case "jmp": return `jmp .b${ins.target}`; diff --git a/backup-compiler/lir_gen.ts b/backup-compiler/lir_gen.ts index e5fa2ff..c9c7f69 100644 --- a/backup-compiler/lir_gen.ts +++ b/backup-compiler/lir_gen.ts @@ -31,7 +31,14 @@ export class LirGen { const mir = this.mirGen.fnMir(stmt, stmt.kind); const id = this.fnIds++; const label = `sbc__${stmt.kind.ident}`; - const fn: Fn = { id, label, mir, lines: [], frameSize: 0 }; + const fn: Fn = { + id, + label, + mir, + lines: [], + frameSize: 0, + localOffsets: new Map(), + }; this.fns.set(id, fn); this.stmtFns.set(stmt.id, fn); } @@ -69,8 +76,6 @@ class FnGen { private currentLabels: Label[] = []; - private nextOffset = -8; - private frameSize = 0; private localOffsets = new Map(); public constructor( @@ -84,12 +89,33 @@ class FnGen { const label = this.labelIds++; this.blockLabels.set(block.id, label); } - for (const local of this.fn.mir.locals) { - this.localOffsets.set(local.id, this.nextOffset); - this.nextOffset -= 8; - this.frameSize += 8; + + let currentOffset = 8 + this.fn.mir.paramLocals.size * 8; + let frameSize = 0; + + for (const local of this.fn.mir.paramLocals.values()) { + this.localOffsets.set(local.id, currentOffset); + currentOffset -= 8; } - this.fn.frameSize = this.frameSize; + // return address + currentOffset -= 8; + // old rbp + currentOffset -= 8; + // return value + this.localOffsets.set(this.fn.mir.returnLocal.id, currentOffset); + currentOffset -= 8; + frameSize += 8; + for (const local of this.fn.mir.locals) { + if (this.localOffsets.has(local.id)) { + continue; + } + this.localOffsets.set(local.id, currentOffset); + currentOffset -= 8; + frameSize += 8; + } + this.fn.frameSize = frameSize; + this.fn.localOffsets = this.localOffsets; + for (const block of this.fn.mir.blocks) { this.currentLabels.push(this.blockLabels.get(block.id)!); for (const stmt of block.stmts) { @@ -158,7 +184,7 @@ class FnGen { const reg = this.reg(); const offset = this.localOffsets.get(k.local.id)!; this.pushIns({ tag: "pop", reg }); - this.pushIns({ tag: "store", offset, reg }); + this.pushIns({ tag: "store_reg", offset, reg }); this.pushIns({ tag: "kill", reg }); return; } @@ -175,8 +201,8 @@ class FnGen { case "mul": { const dst = this.reg(); const src = this.reg(); - this.pushIns({ tag: "pop", reg: src }); this.pushIns({ tag: "pop", reg: dst }); + this.pushIns({ tag: "pop", reg: src }); this.pushIns({ tag: k.tag, dst, src }); this.pushIns({ tag: "push", reg: dst }); this.pushIns({ tag: "kill", reg: src }); diff --git a/backup-compiler/lir_optimize.ts b/backup-compiler/lir_optimize.ts new file mode 100644 index 0000000..e4d0a11 --- /dev/null +++ b/backup-compiler/lir_optimize.ts @@ -0,0 +1,185 @@ +import { + Fn, + Ins, + Label, + Line, + Program, + ProgramStringifyer, + Reg, +} from "./lir.ts"; + +export function lirOptimize(program: Program) { + console.log("=== BEFORE OPTIMIZATION ==="); + console.log(new ProgramStringifyer(program).stringify()); + for (const fn of program.fns) { + eliminatePushPop(fn); + eliminateMovFnCall(fn); + eliminateMovIntStoreReg(fn); + } + console.log("=== AFTER OPTIMIZATION ==="); + console.log(new ProgramStringifyer(program).stringify()); +} + +function eliminatePushPop(fn: Fn) { + const candidates: number[] = []; + + for (let i = 0; i < fn.lines.length - 2; ++i) { + const [push, kill, pop] = fn.lines.slice(i); + if ( + push.ins.tag === "push" && + kill.ins.tag === "kill" && + pop.ins.tag === "pop" && + kill.labels.length === 0 && + pop.labels.length === 0 && + push.ins.reg === kill.ins.reg + ) { + candidates.push(i); + } + } + + for (const i of candidates.toReversed()) { + if (i + 3 >= fn.lines.length) { + fn.lines[i + 3].labels.push(...fn.lines[i].labels); + } + const [push, kill, pop] = fn.lines.slice(i); + if ( + !( + push.ins.tag === "push" && + kill.ins.tag === "kill" && + pop.ins.tag === "pop" + ) + ) { + throw new Error(); + } + const toRemove = pop.ins.reg; + const replacement = push.ins.reg; + fn.lines.splice(i, 3); + replaceReg(fn, toRemove, replacement); + } +} + +function eliminateMovFnCall(fn: Fn) { + const candidates: number[] = []; + + for (let i = 0; i < fn.lines.length - 2; ++i) { + const [movFn, callReg, kill] = fn.lines.slice(i); + if ( + movFn.ins.tag === "mov_fn" && + callReg.ins.tag === "call_reg" && + kill.ins.tag === "kill" && + callReg.labels.length === 0 && + kill.labels.length === 0 && + movFn.ins.reg === callReg.ins.reg && + movFn.ins.reg === kill.ins.reg + ) { + candidates.push(i); + } + } + + for (const i of candidates.toReversed()) { + const [movFn, callReg, kill] = fn.lines.slice(i); + if ( + !( + movFn.ins.tag === "mov_fn" && + callReg.ins.tag === "call_reg" && + kill.ins.tag === "kill" + ) + ) { + throw new Error(); + } + const fnVal = movFn.ins.fn; + const args = callReg.ins.args; + fn.lines.splice(i + 1, 2); + fn.lines[i].ins = { tag: "call_imm", fn: fnVal, args }; + } +} + +function eliminateMovIntStoreReg(fn: Fn) { + const candidates: number[] = []; + + for (let i = 0; i < fn.lines.length - 2; ++i) { + const [movInt, storeReg, kill] = fn.lines.slice(i); + if ( + movInt.ins.tag === "mov_int" && + storeReg.ins.tag === "store_reg" && + kill.ins.tag === "kill" && + storeReg.labels.length === 0 && + kill.labels.length === 0 && + movInt.ins.reg === storeReg.ins.reg && + movInt.ins.reg === kill.ins.reg + ) { + candidates.push(i); + } + } + + for (const i of candidates.toReversed()) { + const [movInt, storeReg, kill] = fn.lines.slice(i); + if ( + !( + movInt.ins.tag === "mov_int" && + storeReg.ins.tag === "store_reg" && + kill.ins.tag === "kill" + ) + ) { + throw new Error(); + } + const offset = storeReg.ins.offset; + const val = movInt.ins.val; + fn.lines.splice(i + 1, 2); + fn.lines[i].ins = { tag: "store_imm", offset, val }; + } +} + +function replaceReg(fn: Fn, cand: Reg, replacement: Reg) { + const r = (reg: Reg): Reg => reg === cand ? replacement : reg; + + for (const { ins } of fn.lines) { + switch (ins.tag) { + case "error": + break; + case "nop": + break; + case "mov_int": + case "mov_string": + case "mov_fn": + ins.reg = r(ins.reg); + break; + case "push": + case "pop": + ins.reg = r(ins.reg); + break; + case "load": + case "store_reg": + ins.reg = r(ins.reg); + break; + case "store_imm": + break; + case "call_reg": + ins.reg = r(ins.reg); + break; + case "call_imm": + break; + case "jmp": + break; + case "jnz_reg": + ins.reg = r(ins.reg); + break; + case "ret": + break; + case "lt": + case "eq": + case "add": + case "mul": + ins.dst = r(ins.dst); + ins.src = r(ins.src); + break; + case "kill": + ins.reg = r(ins.reg); + break; + default: { + const _: never = ins; + } + } + } +} + diff --git a/backup-compiler/main.ts b/backup-compiler/main.ts index 8cf6525..61d4dd4 100644 --- a/backup-compiler/main.ts +++ b/backup-compiler/main.ts @@ -5,35 +5,40 @@ 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"; async function main() { const text = await Deno.readTextFile(Deno.args[0]); const ast = new Parser(text).parse(); - console.log("=== AST ==="); - console.log(yaml.stringify(ast)); + // console.log("=== AST ==="); + // console.log(yaml.stringify(ast)); const re = new Resolver(ast).resolve(); const ch = new Checker(re); const mirGen = new MirGen(re, ch); - console.log("=== MIR ==="); - for (const stmt of ast) { - if (stmt.kind.tag !== "fn") { - throw new Error("only functions can compile top level"); - } - const fnMir = mirGen.fnMir(stmt, stmt.kind); - console.log(new FnStringifyer(fnMir).stringify()); - } + // console.log("=== MIR ==="); + // for (const stmt of ast) { + // if (stmt.kind.tag !== "fn") { + // throw new Error("only functions can compile top level"); + // } + // const fnMir = mirGen.fnMir(stmt, stmt.kind); + // console.log(new FnStringifyer(fnMir).stringify()); + // } const lir = new LirGen(ast, mirGen).generate(); - console.log("=== LIR ==="); - console.log(new ProgramStringifyer(lir).stringify()); + // console.log("=== LIR ==="); + // console.log(new ProgramStringifyer(lir).stringify()); + + lirOptimize(lir); const asm = new AsmGen(lir).generate(); - console.log("=== ASM ==="); - console.log(asm); + // console.log("=== ASM ==="); + // console.log(asm); + + await Deno.writeTextFile("out.nasm", asm); } main(); diff --git a/backup-compiler/mir_gen.ts b/backup-compiler/mir_gen.ts index ccc3d98..ce30931 100644 --- a/backup-compiler/mir_gen.ts +++ b/backup-compiler/mir_gen.ts @@ -51,11 +51,12 @@ export class FnMirGen { const entry = this.block(); const exit = this.block(); + this.returnBlock = exit; this.currentBlock = entry; this.lowerBlock(this.stmtKind.body); - entry.ter = Ter({ tag: "goto", target: exit }); + this.currentBlock.ter = Ter({ tag: "goto", target: exit }); exit.ter = Ter({ tag: "return" }); return { stmt: this.stmt, @@ -251,6 +252,8 @@ export class FnMirGen { return; } case "binary": { + this.lowerExpr(k.left); + this.lowerExpr(k.right); switch (k.op) { case "<": this.pushStmt({ tag: "lt" }); @@ -290,4 +293,3 @@ export class FnMirGen { this.currentBlock.stmts.push(Stmt(kind)); } } - diff --git a/backup-compiler/program.sbl b/backup-compiler/program.sbl index df28955..1c526a5 100644 --- a/backup-compiler/program.sbl +++ b/backup-compiler/program.sbl @@ -2,13 +2,15 @@ #[c_function("print_int")] fn print_int(value) {} -// #[c_function("println")] -// fn println(value) {} +fn inner(value) { + print_int(value); + return 0; +} fn main() { - // println("hello\ world"); let a = 4; - print_int(a + 2); + inner(a + 2); + return a; } // vim: syntax=rust commentstring=//\ %s