From 15d0892efd86530754cdb7dfaf6b7a00a22ec5c7 Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Thu, 27 Mar 2025 13:56:24 +0100 Subject: [PATCH] compiler: operators and while loop --- sbc/asm_gen.ts | 46 +++++++++++++++++++++++++ sbc/ast.ts | 19 ++++++++++- sbc/front.ts | 83 +++++++++++++++++++++++++++++++++++++++------ sbc/lir.ts | 22 +++++++++++- sbc/lir_gen.ts | 9 ++++- sbc/lir_optimize.ts | 22 +++++++++--- sbc/mir.ts | 22 +++++++++++- sbc/mir_gen.ts | 55 +++++++++++++++++++++++++++++- sbc/mir_optimize.ts | 10 +++--- sbc/program.sbl | 21 +++--------- 10 files changed, 268 insertions(+), 41 deletions(-) diff --git a/sbc/asm_gen.ts b/sbc/asm_gen.ts index 27063a7..d53e23c 100644 --- a/sbc/asm_gen.ts +++ b/sbc/asm_gen.ts @@ -27,10 +27,25 @@ export class AsmGen { this.writeIns(`db "${escaped}"`); } this.writeln(`section .text`); + for (const fn of this.lir.fns) { 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(""); return this.writer.finalize(); @@ -241,16 +256,47 @@ export class AsmGen { this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`setl ${this.reg8(ins.dst)}`); 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": this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); this.writeIns(`sete ${this.reg8(ins.dst)}`); return; + case "ne": + this.writeIns(`cmp ${r(ins.dst)}, ${r(ins.src)}`); + this.writeIns(`setne ${this.reg8(ins.dst)}`); + return; case "add": this.writeIns(`add ${r(ins.dst)}, ${r(ins.src)}`); return; + case "sub": + this.writeIns(`sub ${r(ins.dst)}, ${r(ins.src)}`); + return; case "mul": this.writeIns(`imul ${r(ins.dst)}, ${r(ins.src)}`); 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": this.kill(ins.reg); return; diff --git a/sbc/ast.ts b/sbc/ast.ts index 4e68000..6550c55 100644 --- a/sbc/ast.ts +++ b/sbc/ast.ts @@ -16,6 +16,7 @@ export type StmtKind = | { tag: "fn" } & FnStmt | { tag: "let" } & LetStmt | { tag: "loop"; body: Block } + | { tag: "while"; expr: Expr; body: Block } | { tag: "if"; expr: Expr; truthy: Block; falsy?: Block } | { tag: "return"; expr?: Expr } | { tag: "break" } @@ -54,9 +55,25 @@ export type ExprKind = | { tag: "int"; val: number } | { tag: "str"; val: string } | { tag: "call"; expr: Expr; args: Expr[] } + | { tag: "not"; expr: Expr } + | { tag: "negate"; expr: Expr } | { tag: "binary"; op: BinaryOp; left: Expr; right: Expr }; -export type BinaryOp = "<" | "==" | "+" | "*"; +export type BinaryOp = + | "or" + | "xor" + | "and" + | "<" + | ">" + | "<=" + | ">=" + | "==" + | "!=" + | "+" + | "-" + | "*" + | "/" + | "%"; export type Ty = { id: number; diff --git a/sbc/front.ts b/sbc/front.ts index 51005c0..603827d 100644 --- a/sbc/front.ts +++ b/sbc/front.ts @@ -140,24 +140,27 @@ export class Checker { } return callee.returnTy; } + case "not": + case "negate": + throw new Error("todo"); case "binary": { const left = this.exprTy(k.left); const right = this.exprTy(k.right); - const cfg = (op: BinaryOp, l: Ty, r: Ty = l) => - k.op === op && this.assignable(left, l) && + const cfg = (ops: BinaryOp[], l: Ty, r: Ty = l) => + ops.includes(k.op) && this.assignable(left, l) && this.assignable(right, r); - if (cfg("<", { tag: "int" })) { + if (cfg(["<", ">", "<=", ">="], { tag: "int" })) { return { tag: "int" }; } - if (cfg("==", { tag: "int" })) { + if (cfg(["==", "!="], { tag: "int" })) { return { tag: "int" }; } - if (cfg("+", { tag: "int" })) { + if (cfg(["+", "-"], { tag: "int" })) { return { tag: "int" }; } - if (cfg("*", { tag: "int" })) { + if (cfg(["*", "/", "%"], { tag: "int" })) { return { tag: "int" }; } @@ -436,6 +439,12 @@ export class Resolver { this.resolveBlock(k.body); this.loopStack.pop(); return; + case "while": + this.resolveExpr(k.expr); + this.loopStack.push(stmt); + this.resolveBlock(k.body); + this.loopStack.pop(); + return; case "if": this.resolveExpr(k.expr); this.resolveBlock(k.truthy); @@ -487,6 +496,9 @@ export class Resolver { this.resolveExpr(arg); } return; + case "not": + case "negate": + throw new Error("todo"); case "binary": this.resolveExpr(k.left); this.resolveExpr(k.right); @@ -560,6 +572,8 @@ export class Parser { return this.parseLetStmt(); } else if (this.test("loop")) { return this.parseLoopStmt(); + } else if (this.test("while")) { + return this.parseWhileStmt(); } else if (this.test("if")) { return this.parseIfStmt(); } else if (this.test("return")) { @@ -732,6 +746,18 @@ export class Parser { 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 { const line = this.curr().line; this.step(); @@ -780,15 +806,25 @@ export class Parser { return this.parseBinaryExpr(); } - private parseBinaryExpr(prec = 4): Expr { + private parseBinaryExpr(prec = 7): Expr { if (prec == 0) { - return this.parsePostfixExpr(); + return this.parsePrefixExpr(); } const ops: [BinaryOp, number][] = [ + ["or", 7], + ["xor", 6], + ["and", 5], ["<", 4], + [">", 4], + ["<=", 4], + [">=", 4], ["==", 3], + ["!=", 3], ["+", 2], + ["-", 2], ["*", 1], + ["/", 1], + ["%", 1], ]; let left = this.parseBinaryExpr(prec - 1); @@ -811,6 +847,18 @@ export class Parser { 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 { let expr = this.parseOperandExpr(); while (true) { @@ -933,8 +981,21 @@ export type Tok = { }; export function lex(text: string): Tok[] { - const ops = "(){}[]<>+-*=:,;#\n"; - const kws = ["let", "fn", "return", "if", "else", "loop", "break"]; + const ops = "(){}[]<>+-*/%=!:,;#\n"; + const kws = [ + "let", + "fn", + "return", + "if", + "else", + "loop", + "while", + "break", + "not", + "or", + "xor", + "and", + ]; return ops .split("") @@ -943,6 +1004,8 @@ export function lex(text: string): Tok[] { .replaceAll(/\/\/.*?$/mg, "") .replaceAll(op, ` ${op} `) .replaceAll(" - > ", " -> ") + .replaceAll(" < = ", " <= ") + .replaceAll(" > = ", " >= ") .replaceAll(" = = ", " == ") .replaceAll(/\\ /g, "\\SPACE"), text) .split(/[ \t\r]/) diff --git a/sbc/lir.ts b/sbc/lir.ts index e141a9c..ecccd8e 100644 --- a/sbc/lir.ts +++ b/sbc/lir.ts @@ -37,9 +37,22 @@ export type Ins = | { tag: "jmp"; target: Label } | { tag: "jnz_reg"; reg: Reg; target: Label } | { tag: "ret" } - | { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg } + | { tag: BinaryOp; dst: Reg; src: 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 Label = number; @@ -103,9 +116,16 @@ export class ProgramStringifyer { case "ret": return "ret"; case "lt": + case "gt": + case "le": + case "ge": case "eq": + case "ne": case "add": + case "sub": + case "div": case "mul": + case "mod": return `${ins.tag} %${ins.dst}, %${ins.src}`; case "kill": return `kill %${ins.reg}`; diff --git a/sbc/lir_gen.ts b/sbc/lir_gen.ts index 49952b3..a159777 100644 --- a/sbc/lir_gen.ts +++ b/sbc/lir_gen.ts @@ -192,9 +192,16 @@ class FnGen { return; } case "lt": + case "gt": + case "le": + case "ge": + case "ne": case "eq": case "add": - case "mul": { + case "sub": + case "mul": + case "div": + case "mod": { const src = this.reg(); const dst = this.reg(); this.pushIns({ tag: "pop", reg: src }); diff --git a/sbc/lir_optimize.ts b/sbc/lir_optimize.ts index 175209b..e441654 100644 --- a/sbc/lir_optimize.ts +++ b/sbc/lir_optimize.ts @@ -9,8 +9,8 @@ import { } from "./lir.ts"; export function optimizeLir(program: Program) { - console.log("=== BEFORE OPTIMIZATION ==="); - console.log(new ProgramStringifyer(program).stringify()); + // console.log("=== BEFORE OPTIMIZATION ==="); + // console.log(new ProgramStringifyer(program).stringify()); let sizeBefore = program.fns .reduce((acc, fn) => acc + fn.lines.length, 0); @@ -34,8 +34,8 @@ export function optimizeLir(program: Program) { sizeHistory.add(sizeBefore); } - console.log("=== AFTER OPTIMIZATION ==="); - console.log(new ProgramStringifyer(program).stringify()); + // console.log("=== AFTER OPTIMIZATION ==="); + // console.log(new ProgramStringifyer(program).stringify()); } function eliminatePushPop(fn: Fn) { @@ -237,9 +237,16 @@ function replaceReg(fn: Fn, cand: Reg, replacement: Reg) { case "ret": break; case "lt": + case "gt": + case "le": + case "ge": case "eq": + case "ne": case "add": + case "sub": case "mul": + case "div": + case "mod": ins.dst = r(ins.dst); ins.src = r(ins.src); break; @@ -278,9 +285,16 @@ function pollutesStack(ins: Ins): boolean { case "ret": return false; case "lt": + case "gt": + case "le": + case "ge": case "eq": + case "ne": case "add": + case "sub": case "mul": + case "div": + case "mod": return true; case "kill": return false; diff --git a/sbc/mir.ts b/sbc/mir.ts index 1fd221f..93bcbdb 100644 --- a/sbc/mir.ts +++ b/sbc/mir.ts @@ -38,7 +38,20 @@ export type StmtKind = | { tag: "load"; local: Local } | { tag: "store"; local: Local } | { 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 = { kind: TerKind; @@ -108,9 +121,16 @@ export class FnStringifyer { case "call": return `call ${k.args}`; case "lt": + case "gt": + case "le": + case "ge": case "eq": + case "ne": case "add": + case "sub": case "mul": + case "div": + case "mod": return k.tag; } } diff --git a/sbc/mir_gen.ts b/sbc/mir_gen.ts index 618fa0e..0ba4476 100644 --- a/sbc/mir_gen.ts +++ b/sbc/mir_gen.ts @@ -94,8 +94,8 @@ export class FnMirGen { } case "loop": { const entry = this.currentBlock; - const exit = this.block(); const loop = this.block(); + const exit = this.block(); entry.ter = Ter({ tag: "goto", target: loop }); @@ -108,6 +108,31 @@ export class FnMirGen { this.currentBlock = exit; 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": { this.lowerExpr(k.expr); const entry = this.currentBlock; @@ -250,22 +275,50 @@ export class FnMirGen { }); return; } + case "not": + case "negate": + throw new Error("todo"); case "binary": { this.lowerExpr(k.left); this.lowerExpr(k.right); switch (k.op) { + case "or": + case "xor": + case "and": + throw new Error("todo"); case "<": this.pushStmt({ tag: "lt" }); return; + case ">": + this.pushStmt({ tag: "gt" }); + return; + case "<=": + this.pushStmt({ tag: "le" }); + return; + case ">=": + this.pushStmt({ tag: "ge" }); + return; case "==": this.pushStmt({ tag: "eq" }); return; + case "!=": + this.pushStmt({ tag: "ne" }); + return; case "+": this.pushStmt({ tag: "add" }); return; + case "-": + this.pushStmt({ tag: "sub" }); + return; case "*": this.pushStmt({ tag: "mul" }); return; + case "/": + this.pushStmt({ tag: "div" }); + return; + case "%": + this.pushStmt({ tag: "mod" }); + return; } const __: never = k.op; return; diff --git a/sbc/mir_optimize.ts b/sbc/mir_optimize.ts index 4a6def5..edf855b 100644 --- a/sbc/mir_optimize.ts +++ b/sbc/mir_optimize.ts @@ -2,9 +2,9 @@ import * as ast from "./ast.ts"; import { Block, Fn, FnStringifyer } from "./mir.ts"; export function optimizeMirFn(fn: Fn) { - //console.log(`=== OPTIMIZING ${(fn.stmt.kind as ast.FnStmt).ident} ===`); - //console.log("=== BEFORE OPTIMIZATION ==="); - //console.log(new FnStringifyer(fn).stringify()); + console.log(`=== OPTIMIZING ${(fn.stmt.kind as ast.FnStmt).ident} ===`); + console.log("=== BEFORE OPTIMIZATION ==="); + console.log(new FnStringifyer(fn).stringify()); const blockSize = fn.blocks .map((block) => block.stmts.length) @@ -27,8 +27,8 @@ export function optimizeMirFn(fn: Fn) { sizeHistory.add(sizeBefore); } - //console.log("=== AFTER OPTIMIZATION ==="); - //console.log(new FnStringifyer(fn).stringify()); + console.log("=== AFTER OPTIMIZATION ==="); + console.log(new FnStringifyer(fn).stringify()); } function fnSize(fn: Fn, blockSize: number): number { diff --git a/sbc/program.sbl b/sbc/program.sbl index c725eef..dee4023 100644 --- a/sbc/program.sbl +++ b/sbc/program.sbl @@ -1,29 +1,16 @@ -#[c_function("notice")] -fn notice() -> int {} - -#[c_function("print_int")] -fn print_int(value: int) -> int {} - - #[c_function("println")] fn println(value: *str) -> int {} - -fn add(a: int, b: int) -> int { return a + b; } -fn compute() -> int { return 0; } -fn consume(value: int) -> int { return 0; } - +#[c_function("print_int")] +fn print_int(value: int) -> int {} #[c_export("sbc_main")] fn main() -> int { let i = 0; - loop { - if 10 < i + 1 { - break; - } + while i < 10 { - println("hello\ world"); + println("Hello\ world"); i = i + 1; }