import { Expr } from "./parsed"; export type Register = "acc" | "op"; export type Value = number; export type Location = number; export type Instruction = ({ type: "push" | "pop", register: Register, } | { type: "load", register: Register, value: Value, } | { type: "add" | "mul" | "sub" | "div", left: Register, right: Register, dest: Register, } | { type: "negate", src: Register, dest: Register, } | { type: "jump_zero", register: Register, location: Location, } | { type: "jump", location: Location, }) & ({ jumpedTo: true, label: number } | { jumpedTo?: false }); export class Compiler { public result: Instruction[] = []; public compileExpr(expr: Expr) { switch (expr.exprType) { case "int": { this.result.push({ type: "load", register: "acc", value: expr.value }) break; } case "unary": { this.compileExpr(expr.subject); switch (expr.unaryType) { case "plus": { break; } case "negate": { this.result.push({ type: "negate", src: "acc", dest: "acc" }) break; } } break; } case "binary": { this.compileExpr(expr.right); this.result.push({ type: "push", register: "acc" }) this.compileExpr(expr.left); this.result.push({ type: "pop", register: "op" }); let binaryType: "add" | "sub" | "mul" | "div"; switch (expr.binaryType) { case "add": { binaryType = "add"; break; } case "subtract": { binaryType = "sub"; break; } case "multiply": { binaryType = "mul"; break; } case "divide": { binaryType = "div"; break; } } this.result.push({ type: binaryType, left: "acc", right: "op", dest: "acc" }); break; } case "if": { this.compileExpr(expr.condition); const jumpToFalsyIndex = this.result.length; this.result.push({ type: "jump_zero", register: "acc", location: 0 }); this.compileExpr(expr.truthy); const skipFalsyIndex = this.result.length; this.result.push({ type: "jump", location: 0 }); let jumpToFalsyRef = this.result[jumpToFalsyIndex]; if (jumpToFalsyRef.type !== "jump_zero") throw new Error("unreachable"); jumpToFalsyRef.location = this.result.length; this.compileExpr(expr.falsy); let skipFalsyRef = this.result[skipFalsyIndex]; if (skipFalsyRef.type !== "jump") throw new Error("unreachable"); skipFalsyRef.location = this.result.length; break; } case "block": { this.compileExpr(expr.expr); break; } default: { const exhaustiveCheck: never = expr; throw new Error(`Unhandled color case: ${exhaustiveCheck}`); } } } } export function locateAndSetJumpedToInstructions(instructions: Instruction[]) { let nextLabel = 0; for (const ins of instructions) { if (ins.type === "jump_zero" || ins.type === "jump") { instructions[ins.location] = { ...instructions[ins.location], jumpedTo: true, label: nextLabel, }; nextLabel += 1; } } } export class X86Generator { public generate(instructions: Instruction[]): string { return this.asmWithHeaders(instructions.map((ins) => this.generateInstruction(ins)).join("")); } private generateInstruction(ins: Instruction): string { let result = ""; if (ins.jumpedTo) { } switch (ins.type) { case "load": { result += ` ; load` result += ` mov ${this.reg64(ins.register)}, ${this.value(ins.value)}\n`; break; } case "push": { return ` ; push push ${this.reg64(ins.register)} `; } case "pop": { return ` ; pop pop ${this.reg64(ins.register)} `; } case "negate": { return ` ; neg mov ${this.reg64(ins.dest)}, ${this.reg64(ins.src)} neg ${this.reg64(ins.dest)} `; } case "add": { return ` ; add mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)} add ${this.reg64(ins.dest)}, ${this.reg64(ins.right)} `; } case "sub": { return ` ; sub mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)} sub ${this.reg64(ins.dest)}, ${this.reg64(ins.right)} `; } case "mul": { return ` ; mul mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)} imul ${this.reg64(ins.dest)}, ${this.reg64(ins.right)} `; } case "div": { return ` ; div mov rdi, ${this.reg64(ins.right)} mov rax, ${this.reg64(ins.left)} xor rdx, rdx cqo idiv rdi mov ${this.reg64(ins.dest)}, rax `; } case "jump_zero": { return ` ; jump_zero ` } } } private asmWithHeaders(asm: string) { return ` bits 64 global _start _start: ${asm} exit: mov rdi, rax mov rax, 60 syscall `; } private reg64(reg: Register): string { switch (reg) { case "acc": return "rax"; case "op": return "rdx"; } } private reg32(reg: Register): string { switch (reg) { case "acc": return "eax"; case "op": return "edx"; } } private value(value: Value): string { return value.toString(); } }