works, but segfaults

This commit is contained in:
SimonFJ20 2025-03-25 15:45:24 +01:00
parent 2025a450f6
commit fd2f6243a2
12 changed files with 388 additions and 96 deletions

View File

@ -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

4
backup-compiler/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
out.nasm
*.o
out

18
backup-compiler/Makefile Normal file
View File

@ -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

View File

@ -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.generateFn(fn);
}
this.writeln(`; vim: syntax=nasm commentstring=;\\ %s`);
this.writeln("");
return this.writer.finalize();
}
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);
}
return this.writer.finalize();
}
private generateFnBody(fn: lir.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 };
}
this.writeIns(`push r12`);
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`);
}
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);

8
backup-compiler/entry.c Normal file
View File

@ -0,0 +1,8 @@
#include <stdint.h>
extern int32_t sbc__main(void);
int main(void)
{
sbc__main();
}

8
backup-compiler/lib.c Normal file
View File

@ -0,0 +1,8 @@
#include <stdint.h>
#include <stdio.h>
int64_t print_int(int64_t value)
{
printf("%ld\n", value);
return 0;
}

View File

@ -11,6 +11,7 @@ export type Fn = {
mir: mir.Fn;
lines: Line[];
frameSize: number;
localOffsets: Map<number, number>;
};
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}`;

View File

@ -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<number, number>();
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 });

View File

@ -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;
}
}
}
}

View File

@ -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();

View File

@ -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));
}
}

View File

@ -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