diff --git a/backup-compiler/asm_gen.ts b/backup-compiler/asm_gen.ts new file mode 100644 index 0000000..381a5b3 --- /dev/null +++ b/backup-compiler/asm_gen.ts @@ -0,0 +1,244 @@ +import * as lir from "./lir.ts"; + +export class AsmGen { + private writer = new AsmWriter(); + + private liveRegs = new Set(); + private regSelect = new Map(); + + public constructor( + private lir: lir.Program, + ) {} + + public generate(): string { + this.writeln(`bits 64`); + this.writeln(`section .data`); + for (const [id, val] of this.lir.strings) { + this.writeln(`align 8`); + this.writeln(`sbc__string_${id}:`); + this.writeIns(`dq ${val.length}`); + const escaped = val + .replaceAll('"', '\\"') + .replaceAll("\n", "\\n") + .replaceAll("\t", "\\t"); + this.writeIns(`db "${escaped}"`); + } + 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); + } + + return this.writer.finalize(); + } + + private generateFnBody(fn: lir.Fn) { + const stmtKind = fn.mir.stmt.kind; + if (stmtKind.tag !== "fn") { + throw new Error(); + } + if (stmtKind.attrs.at(0)?.ident === "c_function") { + const arg = stmtKind.attrs.at(0)!.args.at(0); + 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; + } + + this.writeIns(`push r12`); + this.writeIns(`push rbp`); + this.writeIns(`mov rbp, rsp`); + this.writeIns(`sub rsp, ${fn.frameSize}`); + this.writeIns(`jmp .L${fn.mir.entry.id}`); + + for (const line of fn.lines) { + for (const label of line.labels) { + this.writeln(`.L${label}:`); + } + this.generateIns(line.ins); + } + + this.writeln(`.exit:`); + 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`); + } + + private generateIns(ins: lir.Ins) { + const r = (reg: lir.Reg) => this.reg(reg); + + switch (ins.tag) { + case "error": + throw new Error(); + case "nop": + this.writeIns(`nop`); + return; + case "mov_int": + this.writeIns(`mov ${r(ins.reg)}, ${ins.val}`); + return; + case "mov_string": + this.writeIns(`mov ${r(ins.reg)}, sbc__string_${ins.stringId}`); + return; + case "mov_fn": + this.writeIns(`mov ${r(ins.reg)}, ${ins.fn.label}`); + return; + case "push": + this.writeIns(`push ${r(ins.reg)}`); + return; + case "pop": + this.writeIns(`pop ${r(ins.reg)}`); + return; + case "load": + this.writeIns(`mov QWORD [rbp${ins.offset}], ${r(ins.reg)}`); + return; + case "store": + this.writeIns(`mov ${r(ins.reg)}, QWORD [rbp${ins.offset}]`); + return; + case "call_reg": + this.generateCall(r(ins.reg), ins.args); + return; + case "call_fn": + this.generateCall(ins.fn.label, ins.args); + return; + case "jmp": + this.writeIns(`jmp .L${ins.target}`); + return; + case "jnz_reg": + this.writeIns(`jnz ${r(ins.reg)}, .L${ins.target}`); + return; + case "ret": + this.writeIns(`jmp .exit`); + return; + case "lt": + this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); + this.writeIns(`setle ${this.reg8(ins.dst)}`); + return; + case "eq": + this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); + this.writeIns(`sete ${this.reg8(ins.dst)}`); + return; + case "add": + this.writeIns(`add ${r(ins.dst)}, ${r(ins.src)}`); + return; + case "mul": + this.writeIns(`imul ${r(ins.dst)}, ${r(ins.src)}`); + return; + case "kill": + 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`); + } + + private reg(reg: lir.Reg): string { + this.allocReg(reg); + return this.regSelect.get(reg)!; + } + + private reg8(reg: lir.Reg): string { + this.allocReg(reg); + return { + "rax": "al", + "rdi": "dil", + "rsi": "sil", + "rdx": "dl", + "rcx": "cl", + "r8": "r8b", + "r9": "r9b", + "r10": "r10b", + "r11": "r11b", + }[this.regSelect.get(reg)!]!; + } + + private allocReg(reg: lir.Reg) { + if (!this.liveRegs.has(reg)) { + this.liveRegs.add(reg); + const regSel = [ + "rax", + "rdi", + "rsi", + "rdx", + "rcx", + "r8", + "r9", + "r10", + "r11", + ].at(this.liveRegs.size); + if (!regSel) { + throw new Error("ran out of registers"); + } + this.regSelect.set(reg, regSel); + } + } + + private kill(reg: lir.Reg) { + this.liveRegs.delete(reg); + this.regSelect.delete(reg); + } + + private writeIns(ins: string) { + this.writer.writeln(` ${ins}`); + } + + private writeln(line: string) { + this.writer.writeln(line); + } +} + +class AsmWriter { + private result = ""; + + public writeln(line: string) { + this.result += `${line}\n`; + } + + public finalize(): string { + return this.result; + } +} diff --git a/backup-compiler/lir.ts b/backup-compiler/lir.ts index 4c0b2dd..38bb9a3 100644 --- a/backup-compiler/lir.ts +++ b/backup-compiler/lir.ts @@ -10,6 +10,7 @@ export type Fn = { label: string; mir: mir.Fn; lines: Line[]; + frameSize: number; }; export type Line = { @@ -27,12 +28,13 @@ export type Ins = | { tag: "pop"; reg: Reg } | { tag: "load"; reg: Reg; offset: number } | { tag: "store"; offset: number; reg: Reg } - | { tag: "call_reg"; reg: Reg } - | { tag: "call_fn"; fn: Fn } + | { tag: "call_reg"; reg: Reg; args: number } + | { tag: "call_fn"; fn: Fn; args: number } | { tag: "jmp"; target: Label } | { tag: "jnz_reg"; reg: Reg; target: Label } | { tag: "ret" } - | { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg }; + | { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg } + | { tag: "kill"; reg: Reg }; export type Reg = number; export type Label = number; @@ -81,9 +83,9 @@ export class ProgramStringifyer { case "store": return `store ${ins.offset}, %${ins.reg}`; case "call_reg": - return `call_reg %${ins.reg}`; + return `call_reg %${ins.reg}, ${ins.args}`; case "call_fn": - return `call_fn ${ins.fn.label}`; + return `call_fn ${ins.fn.label}, ${ins.args}`; case "jmp": return `jmp .b${ins.target}`; case "jnz_reg": @@ -95,6 +97,8 @@ export class ProgramStringifyer { case "add": case "mul": return `${ins.tag} %${ins.dst}, %${ins.src}`; + case "kill": + return `kill %${ins.reg}`; } const _: never = ins; } diff --git a/backup-compiler/lir_gen.ts b/backup-compiler/lir_gen.ts index 73a8f0a..e5fa2ff 100644 --- a/backup-compiler/lir_gen.ts +++ b/backup-compiler/lir_gen.ts @@ -31,7 +31,7 @@ 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: [] }; + const fn: Fn = { id, label, mir, lines: [], frameSize: 0 }; this.fns.set(id, fn); this.stmtFns.set(stmt.id, fn); } @@ -43,15 +43,10 @@ export class LirGen { throw new Error(); } - // if (stmtKind.attrs.at(0)?.ident === "c_function") { - // const arg = stmtKind.attrs.at(0)!.args.at(0); - // if (!arg || arg.kind.tag !== "string") { - // throw new Error("incorrect args for attribute"); - // } - // const label = arg.kind.val; - // new CFunctionGen(fn, label).generate(); - // continue; - // } + if (stmtKind.attrs.at(0)?.ident === "c_function") { + // No need to generate lir. + continue; + } new FnGen( fn, @@ -75,6 +70,7 @@ class FnGen { private currentLabels: Label[] = []; private nextOffset = -8; + private frameSize = 0; private localOffsets = new Map(); public constructor( @@ -91,7 +87,9 @@ class FnGen { for (const local of this.fn.mir.locals) { this.localOffsets.set(local.id, this.nextOffset); this.nextOffset -= 8; + this.frameSize += 8; } + this.fn.frameSize = this.frameSize; for (const block of this.fn.mir.blocks) { this.currentLabels.push(this.blockLabels.get(block.id)!); for (const stmt of block.stmts) { @@ -117,12 +115,14 @@ class FnGen { const stringId = this.strings.intern(k.val.val); this.pushIns({ tag: "mov_string", reg, stringId }); this.pushIns({ tag: "push", reg }); + this.pushIns({ tag: "kill", reg }); return; } case "int": { const reg = this.reg(); this.pushIns({ tag: "mov_int", reg, val: k.val.val }); this.pushIns({ tag: "push", reg }); + this.pushIns({ tag: "kill", reg }); return; } case "fn": { @@ -133,6 +133,7 @@ class FnGen { fn: this.stmtFns.get(k.val.stmt.id)!, }); this.pushIns({ tag: "push", reg }); + this.pushIns({ tag: "kill", reg }); return; } } @@ -142,6 +143,7 @@ class FnGen { case "pop": { const reg = this.reg(); this.pushIns({ tag: "pop", reg }); + this.pushIns({ tag: "kill", reg }); return; } case "load": { @@ -149,6 +151,7 @@ class FnGen { const offset = this.localOffsets.get(k.local.id)!; this.pushIns({ tag: "load", reg, offset }); this.pushIns({ tag: "push", reg }); + this.pushIns({ tag: "kill", reg }); return; } case "store": { @@ -156,12 +159,14 @@ class FnGen { const offset = this.localOffsets.get(k.local.id)!; this.pushIns({ tag: "pop", reg }); this.pushIns({ tag: "store", offset, reg }); + this.pushIns({ tag: "kill", reg }); return; } case "call": { const reg = this.reg(); this.pushIns({ tag: "pop", reg }); - this.pushIns({ tag: "call_reg", reg }); + this.pushIns({ tag: "call_reg", reg, args: k.args }); + this.pushIns({ tag: "kill", reg }); return; } case "lt": @@ -174,6 +179,8 @@ class FnGen { this.pushIns({ tag: "pop", reg: dst }); this.pushIns({ tag: k.tag, dst, src }); this.pushIns({ tag: "push", reg: dst }); + this.pushIns({ tag: "kill", reg: src }); + this.pushIns({ tag: "kill", reg: dst }); return; } } @@ -206,6 +213,7 @@ class FnGen { reg, target: this.blockLabels.get(k.falsy.id)!, }); + this.pushIns({ tag: "kill", reg }); this.pushIns({ tag: "jmp", target: this.blockLabels.get(k.falsy.id)!, @@ -227,16 +235,6 @@ class FnGen { } } -class CFunctionGen { - public constructor( - private fn: Fn, - private label: string, - ) {} - - public generate() { - } -} - class StringIntern { private ids = 0; private strings = new Map(); diff --git a/backup-compiler/main.ts b/backup-compiler/main.ts index 565da74..8cf6525 100644 --- a/backup-compiler/main.ts +++ b/backup-compiler/main.ts @@ -4,6 +4,7 @@ import { MirGen } from "./mir_gen.ts"; import { FnStringifyer } from "./mir.ts"; import { LirGen } from "./lir_gen.ts"; import { ProgramStringifyer } from "./lir.ts"; +import { AsmGen } from "./asm_gen.ts"; async function main() { const text = await Deno.readTextFile(Deno.args[0]); @@ -29,6 +30,10 @@ async function main() { const lir = new LirGen(ast, mirGen).generate(); console.log("=== LIR ==="); console.log(new ProgramStringifyer(lir).stringify()); + + const asm = new AsmGen(lir).generate(); + console.log("=== ASM ==="); + console.log(asm); } main();