diff --git a/compiler/compiler.ts b/compiler/compiler.ts index 6d85a1b..d2714b0 100644 --- a/compiler/compiler.ts +++ b/compiler/compiler.ts @@ -10,11 +10,13 @@ import { FnNamesMap, Lowerer } from "./lowerer.ts"; import { Parser } from "./parser.ts"; import { Resolver } from "./resolver.ts"; import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts"; - -import * as path from "jsr:@std/path"; import { Pos } from "./token.ts"; import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts"; +import * as path from "jsr:@std/path"; +import { AstLowerer } from "./middle/lower_ast.ts"; +import { printMir } from "./middle/mir.ts"; + export type CompileResult = { program: number[]; fnNames: FnNamesMap; @@ -29,28 +31,32 @@ export class Compiler { } public async compile(): Promise { - const mod = new ModTree( + const { ast } = new ModTree( this.startFilePath, this.astCreator, this.reporter, ).resolve(); - new SpecialLoopDesugarer(this.astCreator).desugar(mod.ast); - new ArrayLiteralDesugarer(this.astCreator).desugar(mod.ast); - new StructLiteralDesugarer(this.astCreator).desugar(mod.ast); + new SpecialLoopDesugarer(this.astCreator).desugar(ast); + new ArrayLiteralDesugarer(this.astCreator).desugar(ast); + new StructLiteralDesugarer(this.astCreator).desugar(ast); - new Resolver(this.reporter).resolve(mod.ast); + new Resolver(this.reporter).resolve(ast); - new CompoundAssignDesugarer(this.astCreator).desugar(mod.ast); + new CompoundAssignDesugarer(this.astCreator).desugar(ast); - new Checker(this.reporter).check(mod.ast); + new Checker(this.reporter).check(ast); + + const mir = new AstLowerer(ast).lower(); + + printMir(mir); if (this.reporter.errorOccured()) { console.error("Errors occurred, stopping compilation."); Deno.exit(1); } - const { monoFns, callMap } = new Monomorphizer(mod.ast).monomorphize(); + const { monoFns, callMap } = new Monomorphizer(ast).monomorphize(); const lastPos = await lastPosInTextFile(this.startFilePath); diff --git a/compiler/middle/lower_ast.ts b/compiler/middle/lower_ast.ts new file mode 100644 index 0000000..b38513f --- /dev/null +++ b/compiler/middle/lower_ast.ts @@ -0,0 +1,469 @@ +import * as Ast from "../ast.ts"; +import { AllFnsCollector } from "../mono.ts"; +import { VType, vtypesEqual } from "../vtype.ts"; +import { + Block, + BlockId, + Fn, + Local, + LocalId, + Mir, + Op, + OpKind, + Ter, + TerKind, +} from "./mir.ts"; + +export class AstLowerer { + public constructor(private ast: Ast.Stmt[]) {} + + public lower(): Mir { + const fnAsts = new AllFnsCollector().collect(this.ast).values(); + const fns = fnAsts + .map((fnAst) => new FnAstLowerer(fnAst).lower()) + .toArray(); + return { fns }; + } +} + +class LocalAllocator { + private locals: Local[] = []; + + public alloc(vtype: VType): LocalId { + const id = this.locals.length; + this.locals.push({ id, vtype }); + return id; + } + + public finish(): Local[] { + return this.locals; + } +} + +class FnAstLowerer { + private locals = new LocalAllocator(); + private blockIdCounter = 0; + private currentBlockId = 0; + private blocks = new Map(); + + private fnParamIndexLocals = new Map(); + private letStmtIdLocals = new Map(); + + private breakStack: { local: LocalId; block: BlockId }[] = []; + + public constructor(private ast: Ast.Stmt) {} + + public lower(): Fn { + const stmt = this.ast; + if (stmt.kind.type !== "fn") { + throw new Error(); + } + const vtype = stmt.kind.vtype; + if (vtype?.type !== "fn") { + throw new Error(); + } + + this.locals.alloc(stmt.kind.vtype!); + for (const param of stmt.kind.params) { + const id = this.locals.alloc(param.vtype!); + this.fnParamIndexLocals.set(param.index!, id); + } + + this.pushBlock("entry"); + this.lowerBlockExpr(stmt.kind.body); + this.pushBlock("exit"); + + const locals = this.locals.finish(); + const blocks = this.blocks.values().toArray(); + return { stmt, locals, blocks }; + } + + private lowerStmt(stmt: Ast.Stmt) { + switch (stmt.kind.type) { + case "error": + case "mod_block": + case "mod_file": + case "mod": + break; + case "break": { + const { local, block } = this.breakStack.at(-1)!; + if (stmt.kind.expr) { + const val = this.lowerExpr(stmt.kind.expr); + this.addOp({ type: "assign", dst: local, src: val }); + } else { + this.addOp({ type: "assign_null", dst: local }); + } + this.setTer({ type: "jump", target: block }); + this.pushBlock(); + return; + } + case "return": + break; + case "fn": + // nothing + return; + case "let": + this.lowerLetStmt(stmt); + return; + case "type_alias": + break; + case "assign": + return this.lowerAssign(stmt); + case "expr": { + const val = this.lowerExpr(stmt.kind.expr); + this.addOp({ type: "drop", val }); + return; + } + } + throw new Error(`statement type '${stmt.kind.type}' not covered`); + } + + private lowerAssign(stmt: Ast.Stmt) { + if (stmt.kind.type !== "assign") { + throw new Error(); + } + if (stmt.kind.assignType !== "=") { + throw new Error("incomplete desugar"); + } + const src = this.lowerExpr(stmt.kind.value); + const s = stmt.kind.subject; + switch (s.kind.type) { + case "field": { + const subject = this.lowerExpr(s.kind.subject); + const ident = s.kind.ident; + this.addOp({ type: "assign_field", subject, ident, src }); + return; + } + case "index": { + const subject = this.lowerExpr(s.kind.subject); + const index = this.lowerExpr(s.kind.value); + this.addOp({ type: "assign_field", subject, index, src }); + return; + } + case "sym": { + const sym = s.kind.sym; + switch (sym.type) { + case "let": { + const dst = this.letStmtIdLocals.get(sym.stmt.id)!; + this.addOp({ type: "assign", dst, src }); + return; + } + case "fn_param": { + const dst = this.fnParamIndexLocals.get( + sym.param.index!, + )!; + this.addOp({ type: "assign", dst, src }); + return; + } + } + throw new Error(`symbol type '${sym.type}' not covered`); + } + default: + throw new Error(); + } + } + + private lowerLetStmt(stmt: Ast.Stmt) { + if (stmt.kind.type !== "let") { + throw new Error(); + } + const src = this.lowerExpr(stmt.kind.value); + const dst = this.locals.alloc(stmt.kind.param.vtype!); + this.addOp({ type: "assign", dst, src }); + this.letStmtIdLocals.set(stmt.id, dst); + } + + private lowerExpr(expr: Ast.Expr): LocalId { + switch (expr.kind.type) { + case "error": { + const dst = this.locals.alloc({ type: "error" }); + this.addOp({ type: "assign_error", dst }); + return dst; + } + case "null": { + const dst = this.locals.alloc({ type: "null" }); + this.addOp({ type: "assign_null", dst }); + return dst; + } + case "bool": { + const val = expr.kind.value; + const dst = this.locals.alloc({ type: "bool" }); + this.addOp({ type: "assign_bool", dst, val }); + return dst; + } + case "int": { + const val = expr.kind.value; + const dst = this.locals.alloc({ type: "int" }); + this.addOp({ type: "assign_int", dst, val }); + return dst; + } + case "string": { + const val = expr.kind.value; + const dst = this.locals.alloc({ type: "string" }); + this.addOp({ type: "assign_string", dst, val }); + return dst; + } + case "ident": + throw new Error("should've been resolved"); + case "sym": + return this.lowerSymExpr(expr); + case "group": + return this.lowerExpr(expr.kind.expr); + case "array": + throw new Error("incomplete desugar"); + case "struct": + throw new Error("incomplete desugar"); + case "field": + return this.lowerFieldExpr(expr); + case "index": + return this.lowerIndexExpr(expr); + case "call": + return this.lowerCallExpr(expr); + case "path": + case "etype_args": + case "unary": + break; + case "binary": + return this.lowerBinaryExpr(expr); + case "if": + return this.lowerIfExpr(expr); + case "loop": + return this.lowerLoopExpr(expr); + case "block": + return this.lowerBlockExpr(expr); + case "while": + case "for_in": + case "for": + throw new Error("incomplete desugar"); + } + throw new Error(`expression type '${expr.kind.type}' not covered`); + } + + private lowerSymExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "sym") { + throw new Error(); + } + const sym = expr.kind.sym; + switch (sym.type) { + case "let": + return this.letStmtIdLocals.get(sym.stmt.id)!; + case "let_static": + case "type_alias": + break; + case "fn": { + const stmt = sym.stmt; + if (sym.stmt.kind.type !== "fn") { + throw new Error(); + } + const dst = this.locals.alloc(sym.stmt.kind.vtype!); + this.addOp({ type: "assign_fn", dst, stmt }); + return dst; + } + case "fn_param": { + return this.fnParamIndexLocals.get(sym.param.index!)!; + } + case "closure": + case "generic": + case "mod": + } + throw new Error(`symbol type '${sym.type}' not covered`); + } + + private lowerFieldExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "field") { + throw new Error(); + } + const ident = expr.kind.ident; + const subject = this.lowerExpr(expr.kind.subject); + + const subjectVType = expr.kind.subject.vtype!; + if (subjectVType.type !== "struct") { + throw new Error(); + } + const fieldVType = subjectVType.fields.find((field) => + field.ident === ident + ); + if (fieldVType === undefined) { + throw new Error(); + } + + const dst = this.locals.alloc(fieldVType.vtype); + this.addOp({ type: "field", dst, subject, ident }); + return dst; + } + + private lowerIndexExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "index") { + throw new Error(); + } + const subject = this.lowerExpr(expr.kind.subject); + const index = this.lowerExpr(expr.kind.value); + + const dstVType = ((): VType => { + const outer = expr.kind.subject.vtype!; + if (outer.type === "array") { + return outer.inner; + } + if (outer.type === "string") { + return { type: "int" }; + } + throw new Error(); + })(); + + const dst = this.locals.alloc(dstVType); + this.addOp({ type: "index", dst, subject, index }); + return dst; + } + + private lowerCallExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "call") { + throw new Error(); + } + + const args = expr.kind.args.map((arg) => this.lowerExpr(arg)); + + const subject = this.lowerExpr(expr.kind.subject); + + const subjectVType = expr.kind.subject.vtype!; + if (subjectVType.type !== "fn") { + throw new Error(); + } + + const dst = this.locals.alloc(subjectVType.returnType); + this.addOp({ type: "call_val", dst, subject, args }); + return dst; + } + + private lowerBinaryExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "binary") { + throw new Error(); + } + const leftVType = expr.kind.left.vtype!; + const rightVType = expr.kind.right.vtype!; + if (!vtypesEqual(leftVType, rightVType)) { + throw new Error(); + } + //const vtype = leftVType.type === "error" && rightVType || leftVType; + + const binaryType = expr.kind.binaryType; + const left = this.lowerExpr(expr.kind.left); + const right = this.lowerExpr(expr.kind.right); + + const dst = this.locals.alloc(expr.vtype!); + + this.addOp({ type: "binary", binaryType, dst, left, right }); + return dst; + + //throw new Error( + // `binary vtype '${vtypeToString(leftVType)}' not covered`, + //); + } + + private lowerIfExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "if") { + throw new Error(); + } + const condBlock = this.currentBlock(); + const cond = this.lowerExpr(expr.kind.cond); + const end = this.reserveBlock(); + + const val = this.locals.alloc(expr.vtype!); + + const truthy = this.pushBlock(); + const truthyVal = this.lowerExpr(expr.kind.truthy); + this.addOp({ type: "assign", dst: val, src: truthyVal }); + this.setTer({ type: "jump", target: end }); + + if (expr.kind.falsy) { + const falsy = this.pushBlock(); + const falsyVal = this.lowerExpr(expr.kind.falsy); + this.addOp({ type: "assign", dst: val, src: falsyVal }); + this.setTer({ type: "jump", target: end }); + + this.setTerOn(condBlock, { type: "if", cond, truthy, falsy }); + } else { + this.setTerOn(condBlock, { type: "if", cond, truthy, falsy: end }); + } + + return val; + } + + private lowerLoopExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "loop") { + throw new Error(); + } + + const val = this.locals.alloc(expr.vtype!); + const breakBlock = this.reserveBlock(); + this.breakStack.push({ local: val, block: breakBlock }); + + const body = this.pushBlock(); + this.setTer({ type: "jump", target: body }); + + this.lowerExpr(expr.kind.body); + this.setTer({ type: "jump", target: body }); + + this.breakStack.pop(); + + this.pushBlockWithId(breakBlock); + return val; + } + + private lowerBlockExpr(expr: Ast.Expr): LocalId { + if (expr.kind.type !== "block") { + throw new Error(); + } + + for (const stmt of expr.kind.stmts) { + this.lowerStmt(stmt); + } + if (expr.kind.expr) { + return this.lowerExpr(expr.kind.expr); + } else { + const local = this.locals.alloc({ type: "null" }); + this.addOp({ type: "assign_null", dst: local }); + return local; + } + } + + private addOp(kind: OpKind) { + this.blocks.get(this.currentBlockId)!.ops.push({ kind }); + } + + private addOpOn(blockId: BlockId, kind: OpKind) { + this.blocks.get(blockId)!.ops.push({ kind }); + } + + private setTer(kind: TerKind) { + this.blocks.get(this.currentBlockId)!.ter = { kind }; + } + + private setTerOn(blockId: BlockId, kind: TerKind) { + this.blocks.get(blockId)!.ter = { kind }; + } + + private currentBlock(): BlockId { + return this.currentBlockId; + } + + private reserveBlock(): BlockId { + const id = this.blockIdCounter; + this.blockIdCounter += 1; + return id; + } + + private pushBlock(label?: string): BlockId { + const id = this.blockIdCounter; + this.blockIdCounter += 1; + const ter: Ter = { kind: { type: "error" } }; + this.blocks.set(id, { id, ops: [], ter, label }); + return id; + } + + private pushBlockWithId(id: BlockId): BlockId { + this.blockIdCounter += 1; + const ter: Ter = { kind: { type: "error" } }; + this.blocks.set(id, { id, ops: [], ter }); + return id; + } +} diff --git a/compiler/middle/mir.ts b/compiler/middle/mir.ts new file mode 100644 index 0000000..725e3bd --- /dev/null +++ b/compiler/middle/mir.ts @@ -0,0 +1,152 @@ +import { BinaryType, Stmt } from "../ast.ts"; +import { VType, vtypeToString } from "../vtype.ts"; + +export type Mir = { + fns: Fn[]; +}; + +export type Fn = { + stmt: Stmt; + locals: Local[]; + blocks: Block[]; +}; + +export type LocalId = number; + +export type Local = { + id: LocalId; + vtype: VType; +}; + +export type BlockId = number; + +export type Block = { + id: BlockId; + ops: Op[]; + ter: Ter; + label?: string; +}; + +export type Op = { + kind: OpKind; +}; + +type L = LocalId; + +export type OpKind = + | { type: "error" } + | { type: "return" } + | { type: "drop"; val: L } + | { type: "assign"; dst: L; src: L } + | { type: "assign_error"; dst: L } + | { type: "assign_null"; dst: L } + | { type: "assign_bool"; dst: L; val: boolean } + | { type: "assign_int"; dst: L; val: number } + | { type: "assign_string"; dst: L; val: string } + | { type: "assign_fn"; dst: L; stmt: Stmt } + | { type: "field"; dst: L; subject: L; ident: string } + | { type: "assign_field"; subject: L; ident: string; src: L } + | { type: "index"; dst: L; subject: L; index: number } + | { type: "assign_field"; subject: L; index: L; src: L } + | { type: "call_val"; dst: L; subject: L; args: L[] } + | { type: "binary"; binaryType: BinaryType; dst: L; left: L; right: L }; + +export type Ter = { + kind: TerKind; +}; + +export type TerKind = + | { type: "error" } + | { type: "jump"; target: BlockId } + | { type: "if"; cond: L; truthy: BlockId; falsy: BlockId }; + +export function printMir(mir: Mir) { + for (const fn of mir.fns) { + const stmt = fn.stmt; + if (stmt.kind.type !== "fn") { + throw new Error(); + } + const name = stmt.kind.ident; + + const vtype = stmt.kind.vtype; + if (vtype?.type !== "fn") { + throw new Error(); + } + const generics = vtype.genericParams + ?.map(({ ident }) => `${ident}`).join(", ") ?? ""; + const params = vtype.params + .map(({ vtype }, i) => + `_${fn.locals[i + 1].id}: ${vtypeToString(vtype)}` + ) + .join(", "); + const returnType = vtypeToString(vtype.returnType); + console.log(`${name}${generics}(${params}) -> ${returnType}:`); + for (const { id, vtype } of fn.locals) { + console.log(` let _${id}: ${vtypeToString(vtype)};`); + } + for (const block of fn.blocks) { + console.log(`.${block.label ?? block.id}:`); + for (const op of block.ops) { + const k = op.kind; + switch (k.type) { + case "error": + console.log(` ;`); + break; + case "return": + console.log(` return;`); + break; + case "drop": + console.log(` drop _${k.val};`); + break; + case "assign": + console.log(` _${k.dst} = _${k.src};`); + break; + case "assign_error": + console.log(` _${k.dst} = ;`); + break; + case "assign_null": + console.log(` _${k.dst} = null;`); + break; + case "assign_bool": + console.log(` _${k.dst} = ${k.val};`); + break; + case "assign_int": + console.log(` _${k.dst} = ${k.val};`); + break; + case "assign_string": + console.log(` _${k.dst} = "${k.val}";`); + break; + case "assign_fn": { + const stmt = k.stmt; + if (stmt.kind.type !== "fn") { + throw new Error(); + } + console.log(` _${k.dst} = ${stmt.kind.ident};`); + break; + } + case "field": + console.log( + ` _${k.dst} = _${k.subject}.${k.ident};`, + ); + break; + case "index": + console.log( + ` _${k.dst} = _${k.subject}[_${k.index}];`, + ); + break; + case "call_val": { + const args = k.args.map((arg) => `_${arg}`).join(", "); + console.log(` _${k.dst} = _${k.subject}(${args});`); + break; + } + case "binary": { + console.log( + ` _${k.dst} = _${k.left} ${k.binaryType} _${k.right};`, + ); + break; + } + } + } + } + } +} diff --git a/compiler/mono.ts b/compiler/mono.ts index 9ccc45b..45a1cb3 100644 --- a/compiler/mono.ts +++ b/compiler/mono.ts @@ -181,7 +181,7 @@ function vtypeNameGenPart(vtype: VType): string { } } -class AllFnsCollector implements AstVisitor { +export class AllFnsCollector implements AstVisitor { private allFns = new Map(); public collect(ast: Stmt[]): Map { diff --git a/compiler/vtype.ts b/compiler/vtype.ts index bf432d1..c0bbe1b 100644 --- a/compiler/vtype.ts +++ b/compiler/vtype.ts @@ -130,7 +130,7 @@ export function vtypeToString(vtype: VType): string { `${param.ident}: ${vtypeToString(param.vtype)}` ) .join(", "); - return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`; + return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`; } if (vtype.type === "generic") { return `generic`;