compiler: operators and while loop

This commit is contained in:
SimonFJ20 2025-03-27 13:56:24 +01:00
parent cb0406e180
commit 15d0892efd
10 changed files with 268 additions and 41 deletions

View File

@ -27,10 +27,25 @@ export class AsmGen {
this.writeIns(`db "${escaped}"`); this.writeIns(`db "${escaped}"`);
} }
this.writeln(`section .text`); this.writeln(`section .text`);
for (const fn of this.lir.fns) { for (const fn of this.lir.fns) {
this.generateFn(fn); this.generateFn(fn);
} }
this.writeln(`sbc__div:`);
this.writeIns(`mov rdx, 0`);
this.writeIns(`mov rax, [rsp+16]`);
this.writeIns(`mov rdi, [rsp+8]`);
this.writeIns(`div rdi`);
this.writeIns(`ret`);
this.writeln(`sbc__mod:`);
this.writeIns(`mov rdx, 0`);
this.writeIns(`mov rax, [rsp+16]`);
this.writeIns(`mov rdi, [rsp+8]`);
this.writeIns(`div rdi`);
this.writeIns(`mov rax, rdx`);
this.writeIns(`ret`);
this.writeln(`; vim: syntax=nasm commentstring=;\\ %s`); this.writeln(`; vim: syntax=nasm commentstring=;\\ %s`);
this.writeln(""); this.writeln("");
return this.writer.finalize(); return this.writer.finalize();
@ -241,16 +256,47 @@ export class AsmGen {
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`setl ${this.reg8(ins.dst)}`); this.writeIns(`setl ${this.reg8(ins.dst)}`);
return; return;
case "gt":
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`setg ${this.reg8(ins.dst)}`);
return;
case "le":
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`setle ${this.reg8(ins.dst)}`);
return;
case "ge":
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`setge ${this.reg8(ins.dst)}`);
return;
case "eq": case "eq":
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`sete ${this.reg8(ins.dst)}`); this.writeIns(`sete ${this.reg8(ins.dst)}`);
return; return;
case "ne":
this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`);
this.writeIns(`setne ${this.reg8(ins.dst)}`);
return;
case "add": case "add":
this.writeIns(`add ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`add ${r(ins.dst)}, ${r(ins.src)}`);
return; return;
case "sub":
this.writeIns(`sub ${r(ins.dst)}, ${r(ins.src)}`);
return;
case "mul": case "mul":
this.writeIns(`imul ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`imul ${r(ins.dst)}, ${r(ins.src)}`);
return; return;
case "div":
this.writeIns(`push ${r(ins.dst)}`);
this.writeIns(`push ${r(ins.src)}`);
this.writeIns(`call sbc__div`);
this.writeIns(`mov ${r(ins.dst)}, rax`);
return;
case "mod":
this.writeIns(`push ${r(ins.dst)}`);
this.writeIns(`push ${r(ins.src)}`);
this.writeIns(`call sbc__mod`);
this.writeIns(`mov ${r(ins.dst)}, rax`);
return;
case "kill": case "kill":
this.kill(ins.reg); this.kill(ins.reg);
return; return;

View File

@ -16,6 +16,7 @@ export type StmtKind =
| { tag: "fn" } & FnStmt | { tag: "fn" } & FnStmt
| { tag: "let" } & LetStmt | { tag: "let" } & LetStmt
| { tag: "loop"; body: Block } | { tag: "loop"; body: Block }
| { tag: "while"; expr: Expr; body: Block }
| { tag: "if"; expr: Expr; truthy: Block; falsy?: Block } | { tag: "if"; expr: Expr; truthy: Block; falsy?: Block }
| { tag: "return"; expr?: Expr } | { tag: "return"; expr?: Expr }
| { tag: "break" } | { tag: "break" }
@ -54,9 +55,25 @@ export type ExprKind =
| { tag: "int"; val: number } | { tag: "int"; val: number }
| { tag: "str"; val: string } | { tag: "str"; val: string }
| { tag: "call"; expr: Expr; args: Expr[] } | { tag: "call"; expr: Expr; args: Expr[] }
| { tag: "not"; expr: Expr }
| { tag: "negate"; expr: Expr }
| { tag: "binary"; op: BinaryOp; left: Expr; right: Expr }; | { tag: "binary"; op: BinaryOp; left: Expr; right: Expr };
export type BinaryOp = "<" | "==" | "+" | "*"; export type BinaryOp =
| "or"
| "xor"
| "and"
| "<"
| ">"
| "<="
| ">="
| "=="
| "!="
| "+"
| "-"
| "*"
| "/"
| "%";
export type Ty = { export type Ty = {
id: number; id: number;

View File

@ -140,24 +140,27 @@ export class Checker {
} }
return callee.returnTy; return callee.returnTy;
} }
case "not":
case "negate":
throw new Error("todo");
case "binary": { case "binary": {
const left = this.exprTy(k.left); const left = this.exprTy(k.left);
const right = this.exprTy(k.right); const right = this.exprTy(k.right);
const cfg = (op: BinaryOp, l: Ty, r: Ty = l) => const cfg = (ops: BinaryOp[], l: Ty, r: Ty = l) =>
k.op === op && this.assignable(left, l) && ops.includes(k.op) && this.assignable(left, l) &&
this.assignable(right, r); this.assignable(right, r);
if (cfg("<", { tag: "int" })) { if (cfg(["<", ">", "<=", ">="], { tag: "int" })) {
return { tag: "int" }; return { tag: "int" };
} }
if (cfg("==", { tag: "int" })) { if (cfg(["==", "!="], { tag: "int" })) {
return { tag: "int" }; return { tag: "int" };
} }
if (cfg("+", { tag: "int" })) { if (cfg(["+", "-"], { tag: "int" })) {
return { tag: "int" }; return { tag: "int" };
} }
if (cfg("*", { tag: "int" })) { if (cfg(["*", "/", "%"], { tag: "int" })) {
return { tag: "int" }; return { tag: "int" };
} }
@ -436,6 +439,12 @@ export class Resolver {
this.resolveBlock(k.body); this.resolveBlock(k.body);
this.loopStack.pop(); this.loopStack.pop();
return; return;
case "while":
this.resolveExpr(k.expr);
this.loopStack.push(stmt);
this.resolveBlock(k.body);
this.loopStack.pop();
return;
case "if": case "if":
this.resolveExpr(k.expr); this.resolveExpr(k.expr);
this.resolveBlock(k.truthy); this.resolveBlock(k.truthy);
@ -487,6 +496,9 @@ export class Resolver {
this.resolveExpr(arg); this.resolveExpr(arg);
} }
return; return;
case "not":
case "negate":
throw new Error("todo");
case "binary": case "binary":
this.resolveExpr(k.left); this.resolveExpr(k.left);
this.resolveExpr(k.right); this.resolveExpr(k.right);
@ -560,6 +572,8 @@ export class Parser {
return this.parseLetStmt(); return this.parseLetStmt();
} else if (this.test("loop")) { } else if (this.test("loop")) {
return this.parseLoopStmt(); return this.parseLoopStmt();
} else if (this.test("while")) {
return this.parseWhileStmt();
} else if (this.test("if")) { } else if (this.test("if")) {
return this.parseIfStmt(); return this.parseIfStmt();
} else if (this.test("return")) { } else if (this.test("return")) {
@ -732,6 +746,18 @@ export class Parser {
return this.stmt({ tag: "loop", body }, line); return this.stmt({ tag: "loop", body }, line);
} }
private parseWhileStmt(): Stmt {
const line = this.curr().line;
this.step();
const expr = this.parseExpr();
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ tag: "error" }, line);
}
const body = this.parseBlock();
return this.stmt({ tag: "while", expr, body }, line);
}
private parseIfStmt(): Stmt { private parseIfStmt(): Stmt {
const line = this.curr().line; const line = this.curr().line;
this.step(); this.step();
@ -780,15 +806,25 @@ export class Parser {
return this.parseBinaryExpr(); return this.parseBinaryExpr();
} }
private parseBinaryExpr(prec = 4): Expr { private parseBinaryExpr(prec = 7): Expr {
if (prec == 0) { if (prec == 0) {
return this.parsePostfixExpr(); return this.parsePrefixExpr();
} }
const ops: [BinaryOp, number][] = [ const ops: [BinaryOp, number][] = [
["or", 7],
["xor", 6],
["and", 5],
["<", 4], ["<", 4],
[">", 4],
["<=", 4],
[">=", 4],
["==", 3], ["==", 3],
["!=", 3],
["+", 2], ["+", 2],
["-", 2],
["*", 1], ["*", 1],
["/", 1],
["%", 1],
]; ];
let left = this.parseBinaryExpr(prec - 1); let left = this.parseBinaryExpr(prec - 1);
@ -811,6 +847,18 @@ export class Parser {
return left; return left;
} }
private parsePrefixExpr(): Expr {
if (this.eat("not")) {
const expr = this.parsePrefixExpr();
return this.expr({ tag: "not", expr }, this.eaten!.line);
} else if (this.eat("-")) {
const expr = this.parsePrefixExpr();
return this.expr({ tag: "negate", expr }, this.eaten!.line);
} else {
return this.parsePostfixExpr();
}
}
private parsePostfixExpr(): Expr { private parsePostfixExpr(): Expr {
let expr = this.parseOperandExpr(); let expr = this.parseOperandExpr();
while (true) { while (true) {
@ -933,8 +981,21 @@ export type Tok = {
}; };
export function lex(text: string): Tok[] { export function lex(text: string): Tok[] {
const ops = "(){}[]<>+-*=:,;#\n"; const ops = "(){}[]<>+-*/%=!:,;#\n";
const kws = ["let", "fn", "return", "if", "else", "loop", "break"]; const kws = [
"let",
"fn",
"return",
"if",
"else",
"loop",
"while",
"break",
"not",
"or",
"xor",
"and",
];
return ops return ops
.split("") .split("")
@ -943,6 +1004,8 @@ export function lex(text: string): Tok[] {
.replaceAll(/\/\/.*?$/mg, "") .replaceAll(/\/\/.*?$/mg, "")
.replaceAll(op, ` ${op} `) .replaceAll(op, ` ${op} `)
.replaceAll(" - > ", " -> ") .replaceAll(" - > ", " -> ")
.replaceAll(" < = ", " <= ")
.replaceAll(" > = ", " >= ")
.replaceAll(" = = ", " == ") .replaceAll(" = = ", " == ")
.replaceAll(/\\ /g, "\\SPACE"), text) .replaceAll(/\\ /g, "\\SPACE"), text)
.split(/[ \t\r]/) .split(/[ \t\r]/)

View File

@ -37,9 +37,22 @@ export type Ins =
| { tag: "jmp"; target: Label } | { tag: "jmp"; target: Label }
| { tag: "jnz_reg"; reg: Reg; target: Label } | { tag: "jnz_reg"; reg: Reg; target: Label }
| { tag: "ret" } | { tag: "ret" }
| { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg } | { tag: BinaryOp; dst: Reg; src: Reg }
| { tag: "kill"; reg: Reg }; | { tag: "kill"; reg: Reg };
export type BinaryOp =
| "lt"
| "gt"
| "le"
| "ge"
| "eq"
| "ne"
| "add"
| "sub"
| "mul"
| "div"
| "mod";
export type Reg = number; export type Reg = number;
export type Label = number; export type Label = number;
@ -103,9 +116,16 @@ export class ProgramStringifyer {
case "ret": case "ret":
return "ret"; return "ret";
case "lt": case "lt":
case "gt":
case "le":
case "ge":
case "eq": case "eq":
case "ne":
case "add": case "add":
case "sub":
case "div":
case "mul": case "mul":
case "mod":
return `${ins.tag} %${ins.dst}, %${ins.src}`; return `${ins.tag} %${ins.dst}, %${ins.src}`;
case "kill": case "kill":
return `kill %${ins.reg}`; return `kill %${ins.reg}`;

View File

@ -192,9 +192,16 @@ class FnGen {
return; return;
} }
case "lt": case "lt":
case "gt":
case "le":
case "ge":
case "ne":
case "eq": case "eq":
case "add": case "add":
case "mul": { case "sub":
case "mul":
case "div":
case "mod": {
const src = this.reg(); const src = this.reg();
const dst = this.reg(); const dst = this.reg();
this.pushIns({ tag: "pop", reg: src }); this.pushIns({ tag: "pop", reg: src });

View File

@ -9,8 +9,8 @@ import {
} from "./lir.ts"; } from "./lir.ts";
export function optimizeLir(program: Program) { export function optimizeLir(program: Program) {
console.log("=== BEFORE OPTIMIZATION ==="); // console.log("=== BEFORE OPTIMIZATION ===");
console.log(new ProgramStringifyer(program).stringify()); // console.log(new ProgramStringifyer(program).stringify());
let sizeBefore = program.fns let sizeBefore = program.fns
.reduce((acc, fn) => acc + fn.lines.length, 0); .reduce((acc, fn) => acc + fn.lines.length, 0);
@ -34,8 +34,8 @@ export function optimizeLir(program: Program) {
sizeHistory.add(sizeBefore); sizeHistory.add(sizeBefore);
} }
console.log("=== AFTER OPTIMIZATION ==="); // console.log("=== AFTER OPTIMIZATION ===");
console.log(new ProgramStringifyer(program).stringify()); // console.log(new ProgramStringifyer(program).stringify());
} }
function eliminatePushPop(fn: Fn) { function eliminatePushPop(fn: Fn) {
@ -237,9 +237,16 @@ function replaceReg(fn: Fn, cand: Reg, replacement: Reg) {
case "ret": case "ret":
break; break;
case "lt": case "lt":
case "gt":
case "le":
case "ge":
case "eq": case "eq":
case "ne":
case "add": case "add":
case "sub":
case "mul": case "mul":
case "div":
case "mod":
ins.dst = r(ins.dst); ins.dst = r(ins.dst);
ins.src = r(ins.src); ins.src = r(ins.src);
break; break;
@ -278,9 +285,16 @@ function pollutesStack(ins: Ins): boolean {
case "ret": case "ret":
return false; return false;
case "lt": case "lt":
case "gt":
case "le":
case "ge":
case "eq": case "eq":
case "ne":
case "add": case "add":
case "sub":
case "mul": case "mul":
case "div":
case "mod":
return true; return true;
case "kill": case "kill":
return false; return false;

View File

@ -38,7 +38,20 @@ export type StmtKind =
| { tag: "load"; local: Local } | { tag: "load"; local: Local }
| { tag: "store"; local: Local } | { tag: "store"; local: Local }
| { tag: "call"; args: number } | { tag: "call"; args: number }
| { tag: "lt" | "eq" | "add" | "mul" }; | { tag: BinaryOp };
export type BinaryOp =
| "lt"
| "gt"
| "le"
| "ge"
| "eq"
| "ne"
| "add"
| "sub"
| "mul"
| "div"
| "mod";
export type Ter = { export type Ter = {
kind: TerKind; kind: TerKind;
@ -108,9 +121,16 @@ export class FnStringifyer {
case "call": case "call":
return `call ${k.args}`; return `call ${k.args}`;
case "lt": case "lt":
case "gt":
case "le":
case "ge":
case "eq": case "eq":
case "ne":
case "add": case "add":
case "sub":
case "mul": case "mul":
case "div":
case "mod":
return k.tag; return k.tag;
} }
} }

View File

@ -94,8 +94,8 @@ export class FnMirGen {
} }
case "loop": { case "loop": {
const entry = this.currentBlock; const entry = this.currentBlock;
const exit = this.block();
const loop = this.block(); const loop = this.block();
const exit = this.block();
entry.ter = Ter({ tag: "goto", target: loop }); entry.ter = Ter({ tag: "goto", target: loop });
@ -108,6 +108,31 @@ export class FnMirGen {
this.currentBlock = exit; this.currentBlock = exit;
return; return;
} }
case "while": {
const entry = this.currentBlock;
const cond = this.block();
const loop = this.block();
const exit = this.block();
entry.ter = Ter({ tag: "goto", target: cond });
this.currentBlock = cond;
this.lowerExpr(k.expr);
this.currentBlock.ter = Ter({
tag: "if",
truthy: loop,
falsy: exit,
});
this.loopExitBlocks.set(stmt.id, exit);
this.currentBlock = loop;
this.lowerBlock(k.body);
this.currentBlock.ter = Ter({ tag: "goto", target: cond });
this.currentBlock = exit;
return;
}
case "if": { case "if": {
this.lowerExpr(k.expr); this.lowerExpr(k.expr);
const entry = this.currentBlock; const entry = this.currentBlock;
@ -250,22 +275,50 @@ export class FnMirGen {
}); });
return; return;
} }
case "not":
case "negate":
throw new Error("todo");
case "binary": { case "binary": {
this.lowerExpr(k.left); this.lowerExpr(k.left);
this.lowerExpr(k.right); this.lowerExpr(k.right);
switch (k.op) { switch (k.op) {
case "or":
case "xor":
case "and":
throw new Error("todo");
case "<": case "<":
this.pushStmt({ tag: "lt" }); this.pushStmt({ tag: "lt" });
return; return;
case ">":
this.pushStmt({ tag: "gt" });
return;
case "<=":
this.pushStmt({ tag: "le" });
return;
case ">=":
this.pushStmt({ tag: "ge" });
return;
case "==": case "==":
this.pushStmt({ tag: "eq" }); this.pushStmt({ tag: "eq" });
return; return;
case "!=":
this.pushStmt({ tag: "ne" });
return;
case "+": case "+":
this.pushStmt({ tag: "add" }); this.pushStmt({ tag: "add" });
return; return;
case "-":
this.pushStmt({ tag: "sub" });
return;
case "*": case "*":
this.pushStmt({ tag: "mul" }); this.pushStmt({ tag: "mul" });
return; return;
case "/":
this.pushStmt({ tag: "div" });
return;
case "%":
this.pushStmt({ tag: "mod" });
return;
} }
const __: never = k.op; const __: never = k.op;
return; return;

View File

@ -2,9 +2,9 @@ import * as ast from "./ast.ts";
import { Block, Fn, FnStringifyer } from "./mir.ts"; import { Block, Fn, FnStringifyer } from "./mir.ts";
export function optimizeMirFn(fn: Fn) { export function optimizeMirFn(fn: Fn) {
//console.log(`=== OPTIMIZING ${(fn.stmt.kind as ast.FnStmt).ident} ===`); console.log(`=== OPTIMIZING ${(fn.stmt.kind as ast.FnStmt).ident} ===`);
//console.log("=== BEFORE OPTIMIZATION ==="); console.log("=== BEFORE OPTIMIZATION ===");
//console.log(new FnStringifyer(fn).stringify()); console.log(new FnStringifyer(fn).stringify());
const blockSize = fn.blocks const blockSize = fn.blocks
.map((block) => block.stmts.length) .map((block) => block.stmts.length)
@ -27,8 +27,8 @@ export function optimizeMirFn(fn: Fn) {
sizeHistory.add(sizeBefore); sizeHistory.add(sizeBefore);
} }
//console.log("=== AFTER OPTIMIZATION ==="); console.log("=== AFTER OPTIMIZATION ===");
//console.log(new FnStringifyer(fn).stringify()); console.log(new FnStringifyer(fn).stringify());
} }
function fnSize(fn: Fn, blockSize: number): number { function fnSize(fn: Fn, blockSize: number): number {

View File

@ -1,29 +1,16 @@
#[c_function("notice")]
fn notice() -> int {}
#[c_function("print_int")]
fn print_int(value: int) -> int {}
#[c_function("println")] #[c_function("println")]
fn println(value: *str) -> int {} fn println(value: *str) -> int {}
#[c_function("print_int")]
fn add(a: int, b: int) -> int { return a + b; } fn print_int(value: int) -> int {}
fn compute() -> int { return 0; }
fn consume(value: int) -> int { return 0; }
#[c_export("sbc_main")] #[c_export("sbc_main")]
fn main() -> int { fn main() -> int {
let i = 0; let i = 0;
loop { while i < 10 {
if 10 < i + 1 {
break;
}
println("hello\ world"); println("Hello\ world");
i = i + 1; i = i + 1;
} }