sfja ec84b385d3
Some checks failed
Check / Explore-Gitea-Actions (push) Failing after 8s
more codegen
2026-04-17 16:47:04 +02:00

200 lines
6.1 KiB
TypeScript

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<mir.Inst, number>();
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<mir.Inst, Reg>();
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;
}
}