import * as mir from "../../mir.ts"; import { Block, Fn, Inst, Reg, Regs } from "./module.ts"; export function selectFnInstructions(fn: mir.Fn): Fn { return new InstructionSelector(fn).generateFn(); } class InstructionSelector { private localsSize = 0; private locals = new Map(); private exitBlock!: Block; constructor( private fn: mir.Fn, ) {} generateFn(): Fn { this.generateFrameLayout(); const entry = new Block(); const exit = new Block(); entry.push(new Inst({ tag: "PushR", src: Reg.reg(Regs.bp) })); entry.push( new Inst({ tag: "MovRR", dst: Reg.reg(Regs.bp), src: Reg.reg(Regs.sp), }), ); entry.push( new Inst({ tag: "SubRI", dst: Reg.reg(Regs.sp), imm: this.localsSize, }), ); this.exitBlock = exit; const blocks: Block[] = []; for (const bb of this.fn.bbs) { blocks.push(this.generateBasicBlock(bb)); } entry.push(new Inst({ tag: "Jmp", bb: blocks[0] ?? exit })); exit.push( new Inst({ tag: "MovRR", dst: Reg.reg(Regs.sp), src: Reg.reg(Regs.bp), }), ); exit.push(new Inst({ tag: "PopR", dst: Reg.reg(Regs.bp) })); exit.push(new Inst({ tag: "Ret" })); return new Fn(this.fn, [entry, ...blocks, exit]); } private generateFrameLayout() { let offset = 0; const align = (alignment: number) => { if (offset % alignment != 0) { offset -= alignment + offset % alignment; } }; const pushLocal = (inst: mir.Inst, size: number, alignment: number) => { align(alignment); this.locals.set(inst, offset); offset -= size; }; this.fn.visit({ visitInst(inst) { if (inst.kind.tag === "Alloca") { const ty = inst.ty.as("PtrMut").kind.ty; switch (ty.kind.tag) { case "Int": { switch (ty.kind.intTy) { case "i32": pushLocal(inst, 4, 4); break; case "i64": pushLocal(inst, 8, 8); break; default: throw new Error( `not implemented (${ty.kind.intTy})`, ); } break; } default: throw new Error( `not implemented (${ty.kind.tag})`, ); } } }, }); // c abi requires 16 bit stack alignment, // might aswell do it here. align(16); this.localsSize = -offset; } private generateBasicBlock(bb: mir.BasicBlock): Block { const regs = new Map(); const block = new Block(); for (const inst of bb.insts) { const save = (r: Reg): Reg => { regs.set(inst, r); return r; }; const k = inst.kind; switch (k.tag) { case "Alloca": save(Reg.temp()); break; case "Void": block.push( new Inst({ tag: "MovRI", dst: save(Reg.temp()), imm: 0, }), ); break; case "Int": block.push( new Inst({ tag: "MovRI", dst: save(Reg.temp()), imm: k.value, }), ); break; case "Store": block.push( new Inst({ tag: "MovMRR", dst: Reg.reg(Regs.bp), offset: this.locals.get(k.target)!, src: regs.get(k.source)!, }), ); break; case "Load": block.push( new Inst({ tag: "MovRMR", dst: save(Reg.temp()), offset: this.locals.get(k.source)!, src: Reg.reg(Regs.bp), }), ); break; case "Add": block.push( new Inst({ tag: "AddR", dst_op1: save(regs.get(k.left)!), op2: regs.get(k.right)!, }), ); break; case "Return": block.push( new Inst({ tag: "MovRR", dst: Reg.reg(Regs.a), src: regs.get(k.source)!, }), ); block.push( new Inst({ tag: "Jmp", bb: this.exitBlock, }), ); break; default: console.error(`not implemented (${k.tag})`); // throw new Error( // `not implemented (${k.tag})`, // ); } } return block; } }