From a7349890d031e1988d6c84c0bc0ec13f30387292 Mon Sep 17 00:00:00 2001 From: sfja Date: Fri, 3 Jan 2025 01:15:05 +0100 Subject: [PATCH] add optimizations --- compiler/checker.ts | 21 +- compiler/compiler.ts | 32 ++- compiler/info.ts | 7 +- compiler/middle/cfg.ts | 103 ++++++++ compiler/middle/elim_blocks.ts | 59 +++++ compiler/middle/elim_transient_locals.ts | 111 ++++++++ compiler/middle/elim_transient_vals.ts | 99 ++++++++ compiler/middle/elim_unused_local.ts | 81 ++++++ compiler/middle/lower_ast.ts | 106 +++++--- compiler/middle/mir.ts | 307 ++++++++++++++++++----- compiler/vtype.ts | 1 + examples/transient_variable.slg | 22 ++ examples/unused_variable.slg | 6 + 13 files changed, 839 insertions(+), 116 deletions(-) create mode 100644 compiler/middle/cfg.ts create mode 100644 compiler/middle/elim_blocks.ts create mode 100644 compiler/middle/elim_transient_locals.ts create mode 100644 compiler/middle/elim_transient_vals.ts create mode 100644 compiler/middle/elim_unused_local.ts create mode 100644 examples/transient_variable.slg create mode 100644 examples/unused_variable.slg diff --git a/compiler/checker.ts b/compiler/checker.ts index 88b1c10..703644d 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -51,7 +51,7 @@ export class Checker { } const vtype = this.checkEType(param.etype!); param.vtype = vtype; - params.push({ ident: param.ident, vtype }); + params.push({ ident: param.ident, mut: true, vtype }); } const returnType: VType = stmt.kind.returnType ? this.checkEType(stmt.kind.returnType) @@ -426,7 +426,11 @@ export class Checker { throw new Error(); } const fields: VTypeParam[] = expr.kind.fields - .map(({ ident, expr }) => ({ ident, vtype: this.checkExpr(expr) })); + .map(({ ident, expr }): VTypeParam => ({ + ident, + mut: true, + vtype: this.checkExpr(expr), + })); return { type: "struct", fields }; } @@ -489,6 +493,9 @@ export class Checker { ); } const args = expr.kind.args.map((arg) => this.checkExpr(arg)); + if (args.some((arg) => arg.type === "error")) { + return { type: "error" }; + } if (subject.genericParams === undefined) { return this.checkCallExprNoGenericsTail( expr, @@ -1023,10 +1030,12 @@ export class Checker { this.report(`field ${declaredTwiceTest[2]} defined twice`, pos); return { type: "error" }; } - const fields = etype.kind.fields.map((param): VTypeParam => ({ - ident: param.ident, - vtype: this.checkEType(param.etype!), - })); + const fields = etype.kind.fields + .map((param): VTypeParam => ({ + ident: param.ident, + mut: true, + vtype: this.checkEType(param.etype!), + })); return { type: "struct", fields }; } if (etype.kind.type === "type_of") { diff --git a/compiler/compiler.ts b/compiler/compiler.ts index d2714b0..22f1783 100644 --- a/compiler/compiler.ts +++ b/compiler/compiler.ts @@ -12,10 +12,16 @@ import { Resolver } from "./resolver.ts"; import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts"; import { Pos } from "./token.ts"; import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts"; +import { mirOpCount, printMir } from "./middle/mir.ts"; import * as path from "jsr:@std/path"; -import { AstLowerer } from "./middle/lower_ast.ts"; -import { printMir } from "./middle/mir.ts"; +import { lowerAst } from "./middle/lower_ast.ts"; +import { eliminateUnusedLocals } from "./middle/elim_unused_local.ts"; +import { + eliminateOnlyChildsBlocks, + eliminateUnreachableBlocks, +} from "./middle/elim_blocks.ts"; +import { eliminateTransientVals } from "./middle/elim_transient_vals.ts"; export type CompileResult = { program: number[]; @@ -47,8 +53,28 @@ export class Compiler { new Checker(this.reporter).check(ast); - const mir = new AstLowerer(ast).lower(); + const mir = lowerAst(ast); + console.log("Before optimizations:"); + printMir(mir); + + const mirHistory = [mirOpCount(mir)]; + for (let i = 0; i < 1; ++i) { + eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1); + eliminateOnlyChildsBlocks(mir); + eliminateUnreachableBlocks(mir); + eliminateTransientVals(mir); + + const opCount = mirOpCount(mir); + const histOccurence = mirHistory + .filter((v) => v === opCount).length; + if (histOccurence >= 2) { + break; + } + mirHistory.push(opCount); + } + + console.log("After optimizations:"); printMir(mir); if (this.reporter.errorOccured()) { diff --git a/compiler/info.ts b/compiler/info.ts index b67bbac..bc6d7ff 100644 --- a/compiler/info.ts +++ b/compiler/info.ts @@ -1,7 +1,7 @@ import { Pos } from "./token.ts"; export type Report = { - type: "error" | "note"; + type: "error" | "warning" | "note"; reporter: string; pos?: Pos; msg: string; @@ -23,6 +23,11 @@ export class Reporter { this.errorSet = true; } + public reportWarning(report: Omit) { + this.reports.push({ ...report, type: "warning" }); + this.printReport({ ...report, type: "warning" }); + } + private printReport({ reporter, type, pos, msg }: Report) { console.error( `${reporter} ${type}: ${msg}${ diff --git a/compiler/middle/cfg.ts b/compiler/middle/cfg.ts new file mode 100644 index 0000000..377b3bc --- /dev/null +++ b/compiler/middle/cfg.ts @@ -0,0 +1,103 @@ +import { Block, BlockId, Fn, Mir } from "./mir.ts"; + +export function createCfg(fn: Fn): Cfg { + return new CfgBuilder(fn).build(); +} + +export class Cfg { + public constructor( + private graph: Map, + private entry: BlockId, + private exit: BlockId, + ) {} + + public parents(block: Block): Block[] { + return this.graph + .get(block.id)!.parents + .map((id) => this.graph.get(id)!.block); + } + + public children(block: Block): Block[] { + return this.graph + .get(block.id)!.children + .map((id) => this.graph.get(id)!.block); + } + + public index(block: Block): number { + return this.graph.get(block.id)!.index; + } + + public print() { + for (const [id, node] of this.graph.entries()) { + const l = (v: T[]) => v.map((v) => `${v}`).join(", "); + + console.log(`graph[${id}] = {`); + console.log(` id: ${node.block.id},`); + console.log(` index: ${node.index},`); + console.log(` parents: [${l(node.parents)}],`); + console.log(` children: [${l(node.children)}],`); + console.log(`}`); + } + } +} + +type CfgNode = { + block: Block; + index: number; + parents: BlockId[]; + children: BlockId[]; +}; + +class CfgBuilder { + private nodes: [Block, number][] = []; + private edges: [BlockId, BlockId][] = []; + + public constructor(private fn: Fn) {} + + public build(): Cfg { + for ( + const [block, index] of this.fn.blocks + .map((v, i) => [v, i] as const) + ) { + this.addNode(block, index); + + const tk = block.ter.kind; + switch (tk.type) { + case "error": + break; + case "return": + break; + case "jump": + this.addEdge(block.id, tk.target); + break; + case "if": + this.addEdge(block.id, tk.truthy); + this.addEdge(block.id, tk.falsy); + break; + } + } + + const graph = new Map(); + for (const [block, index] of this.nodes) { + const parents = this.edges + .filter(([_from, to]) => to === block.id) + .map(([from, _to]) => from); + + const children = this.edges + .filter(([from, _to]) => from === block.id) + .map(([_from, to]) => to); + + graph.set(block.id, { block, index, parents, children }); + } + + return new Cfg(graph, this.fn.entry, this.fn.exit); + } + + private addNode(block: Block, index: number) { + this.nodes.push([block, index]); + } + + private addEdge(from: BlockId, to: BlockId) { + this.edges.push([from, to]); + } +} diff --git a/compiler/middle/elim_blocks.ts b/compiler/middle/elim_blocks.ts new file mode 100644 index 0000000..0e47e76 --- /dev/null +++ b/compiler/middle/elim_blocks.ts @@ -0,0 +1,59 @@ +import { createCfg } from "./cfg.ts"; +import { Block, Mir } from "./mir.ts"; + +export function eliminateOnlyChildsBlocks(mir: Mir) { + for (const fn of mir.fns) { + const cfg = createCfg(fn); + + const candidates: { parent: Block; child: Block }[] = []; + + for (const block of fn.blocks) { + const children = cfg.children(block); + if (children.length !== 1) { + continue; + } + if (cfg.parents(children[0]).length !== 1) { + continue; + } + candidates.push({ parent: block, child: children[0] }); + } + + const elimIndices: number[] = []; + + for (const { parent, child } of candidates) { + parent.ops.push(...child.ops); + parent.ter = child.ter; + elimIndices.push(cfg.index(child)); + } + + for (const i of elimIndices.toReversed()) { + fn.blocks.splice(i, 1); + } + } +} + +export function eliminateUnreachableBlocks(mir: Mir) { + for (const fn of mir.fns) { + const cfg = createCfg(fn); + + const candidates: Block[] = []; + + for (const block of fn.blocks) { + if (block.id === fn.entry) { + continue; + } + if (cfg.parents(block).length !== 0) { + continue; + } + candidates.push(block); + } + + for ( + const i of candidates + .map((block) => cfg.index(block)) + .toReversed() + ) { + fn.blocks.splice(i, 1); + } + } +} diff --git a/compiler/middle/elim_transient_locals.ts b/compiler/middle/elim_transient_locals.ts new file mode 100644 index 0000000..d62c3da --- /dev/null +++ b/compiler/middle/elim_transient_locals.ts @@ -0,0 +1,111 @@ +import { FnStmtKind } from "../ast.ts"; +import { + Block, + Fn, + Local, + LocalId, + Mir, + replaceBlockSrcs, + RValue, +} from "./mir.ts"; + +export function eliminateTransientLocals(mir: Mir) { + for (const fn of mir.fns) { + const otherLocals = fn.locals + .slice(1 + (fn.stmt.kind as FnStmtKind).params.length) + .map((local) => local.id); + + for (const block of fn.blocks) { + new EliminateTransientLocalsBlockPass(block, otherLocals) + .pass(); + } + } +} + +type Candidate = { + dst: LocalId; + src: LocalId; +}; + +class EliminateTransientLocalsBlockPass { + private candidates: Candidate[] = []; + + public constructor( + private block: Block, + private readonly locals: LocalId[], + ) { + } + + public pass() { + this.findCandidatesInBlock(this.block); + + this.candidates = this.candidates + .filter((cand) => this.locals.includes(cand.dst)); + + this.eliminateCandidatesInBlock(this.block); + } + + private eliminateCandidatesInBlock(block: Block) { + replaceBlockSrcs(block, (src) => this.replaceMaybe(src)); + } + + private replaceMaybe(src: RValue): RValue { + if (src.type !== "local") { + return src; + } + const candidate = this.candidates + .find((cand) => cand.dst === src.id)?.src; + return candidate !== undefined ? { type: "local", id: candidate } : src; + } + + private findCandidatesInBlock(block: Block) { + for (const op of block.ops) { + const ok = op.kind; + switch (ok.type) { + case "error": + break; + case "assign": + this.markDst(ok.dst, ok.src); + break; + case "assign_error": + case "assign_null": + case "assign_bool": + case "assign_int": + case "assign_string": + case "assign_fn": + case "field": + case "assign_field": + case "index": + case "assign_index": + case "call_val": + case "binary": + break; + default: + throw new Error(); + } + } + const tk = block.ter.kind; + switch (tk.type) { + case "error": + break; + case "return": + break; + case "jump": + break; + case "if": + break; + } + } + + private markDst(dst: LocalId, src: RValue) { + if (src.type !== "local") { + return; + } + + this.candidates = this.candidates + .filter((cand) => cand.dst !== dst); + this.candidates = this.candidates + .filter((cand) => cand.src !== dst); + this.candidates.push({ dst, src: src.id }); + } +} diff --git a/compiler/middle/elim_transient_vals.ts b/compiler/middle/elim_transient_vals.ts new file mode 100644 index 0000000..30c9eeb --- /dev/null +++ b/compiler/middle/elim_transient_vals.ts @@ -0,0 +1,99 @@ +import { Mir, Op, RValue, visitBlockSrcs } from "./mir.ts"; + +export function eliminateTransientVals(mir: Mir) { + for (const fn of mir.fns) { + for (const block of fn.blocks) { + const cands: { src: RValue; consumer: Op; definition: Op }[] = []; + + visitBlockSrcs(block, (src, op, i) => { + if (src.type !== "local") { + return; + } + const found = block.ops.find((op, fi) => + op.kind.type === "assign" && + op.kind.dst === src.id && + fi < i! + ); + if (!found) { + return; + } + cands.push({ src, consumer: op!, definition: found! }); + }); + + //console.log(cands); + + for (const { src: oldsrc, consumer, definition } of cands) { + if (oldsrc.type !== "local") { + throw new Error(); + } + if (definition.kind.type !== "assign") { + throw new Error(); + } + const src = definition.kind.src; + + const k = consumer.kind; + switch (k.type) { + case "error": + break; + case "assign": + k.src = src; + break; + case "field": + if (same(k.subject, oldsrc)) { + k.subject = src; + } + break; + case "assign_field": + if (same(k.subject, oldsrc)) { + k.subject = src; + } + if (same(k.src, oldsrc)) { + k.src = src; + } + break; + case "index": + if (same(k.subject, oldsrc)) { + k.subject = src; + } + if (same(k.index, oldsrc)) { + k.index = src; + } + break; + case "assign_index": + if (same(k.subject, oldsrc)) { + k.subject = src; + } + if (same(k.index, oldsrc)) { + k.index = src; + } + if (same(k.src, oldsrc)) { + k.src = src; + } + break; + case "call_val": + if (same(k.subject, oldsrc)) { + k.subject = src; + } + for (let i = 0; i < k.args.length; ++i) { + if (same(k.args[i], oldsrc)) { + k.args[i] = src; + } + } + break; + case "binary": + if (same(k.left, oldsrc)) { + k.left = src; + } + if (same(k.right, oldsrc)) { + k.right = src; + } + break; + } + } + } + } +} + +function same(a: RValue, b: RValue): boolean { + return a.type === "local" && a.type === b.type && a.id === b.id; +} diff --git a/compiler/middle/elim_unused_local.ts b/compiler/middle/elim_unused_local.ts new file mode 100644 index 0000000..67b551a --- /dev/null +++ b/compiler/middle/elim_unused_local.ts @@ -0,0 +1,81 @@ +import { FnStmtKind } from "../ast.ts"; +import { Reporter } from "../info.ts"; +import { + Block, + Fn, + LocalId, + Mir, + RValue, + visitBlockDsts, + visitBlockSrcs, +} from "./mir.ts"; + +export function eliminateUnusedLocals( + mir: Mir, + reporter: Reporter, + isPassOne: boolean, +) { + for (const fn of mir.fns) { + new EliminateUnusedLocalsFnPass(fn, reporter, isPassOne).pass(); + } +} + +class EliminateUnusedLocalsFnPass { + private locals: LocalId[]; + + public constructor( + private fn: Fn, + private reporter: Reporter, + private isPassOne: boolean, + ) { + this.locals = this.fn.locals + .slice(1 + (fn.stmt.kind as FnStmtKind).params.length) + .map((local) => local.id); + } + + public pass() { + for (const block of this.fn.blocks) { + this.markLocalsInBlock(block); + } + for (const local of this.locals) { + for (const block of this.fn.blocks) { + this.eliminateLocalInBlock(block, local); + } + } + for (const id of this.locals) { + const local = this.fn.locals.find((local) => local.id === id)!; + if (local.sym?.type === "let" && this.isPassOne) { + this.reporter.reportWarning({ + reporter: "analysis mf'er", + msg: `unused let symbol '${local.sym.ident}'`, + pos: local.sym.pos, + }); + } + } + this.fn.locals = this.fn.locals + .filter((local) => !this.locals.includes(local.id)); + } + + private eliminateLocalInBlock(block: Block, local: LocalId) { + const elimIndices: number[] = []; + visitBlockDsts(block, (dst, i) => { + if (dst === local) { + elimIndices.push(i); + } + }); + for (const i of elimIndices.toReversed()) { + block.ops.splice(i, 1); + } + } + + private markLocalsInBlock(block: Block) { + visitBlockSrcs(block, (src) => this.markUsed(src)); + } + + private markUsed(local: RValue) { + if (local.type !== "local") { + return; + } + this.locals = this.locals.filter((lid) => lid !== local.id); + } +} diff --git a/compiler/middle/lower_ast.ts b/compiler/middle/lower_ast.ts index b38513f..edbe27b 100644 --- a/compiler/middle/lower_ast.ts +++ b/compiler/middle/lower_ast.ts @@ -8,13 +8,17 @@ import { Local, LocalId, Mir, - Op, OpKind, + RValue, Ter, TerKind, } from "./mir.ts"; -export class AstLowerer { +export function lowerAst(ast: Ast.Stmt[]): Mir { + return new AstLowerer(ast).lower(); +} + +class AstLowerer { public constructor(private ast: Ast.Stmt[]) {} public lower(): Mir { @@ -29,9 +33,15 @@ export class AstLowerer { class LocalAllocator { private locals: Local[] = []; - public alloc(vtype: VType): LocalId { + public alloc(vtype: VType, sym?: Ast.Sym): LocalId { const id = this.locals.length; - this.locals.push({ id, vtype }); + this.locals.push({ id, mut: false, vtype, sym }); + return id; + } + + public allocMut(vtype: VType, sym?: Ast.Sym): LocalId { + const id = this.locals.length; + this.locals.push({ id, mut: true, vtype, sym }); return id; } @@ -63,19 +73,21 @@ class FnAstLowerer { throw new Error(); } - this.locals.alloc(stmt.kind.vtype!); + const rLoc = this.locals.alloc(vtype.returnType); for (const param of stmt.kind.params) { - const id = this.locals.alloc(param.vtype!); + const id = this.locals.allocMut(param.vtype!); this.fnParamIndexLocals.set(param.index!, id); } - this.pushBlock("entry"); - this.lowerBlockExpr(stmt.kind.body); - this.pushBlock("exit"); + const entry = this.pushBlock(); + const rVal = this.lowerBlockExpr(stmt.kind.body); + this.addOp({ type: "assign", dst: rLoc, src: local(rVal) }); + this.setTer({ type: "return" }); + const exit = this.currentBlock(); const locals = this.locals.finish(); const blocks = this.blocks.values().toArray(); - return { stmt, locals, blocks }; + return { stmt, locals, blocks, entry, exit }; } private lowerStmt(stmt: Ast.Stmt) { @@ -86,12 +98,12 @@ class FnAstLowerer { case "mod": break; case "break": { - const { local, block } = this.breakStack.at(-1)!; + const { local: dst, block } = this.breakStack.at(-1)!; if (stmt.kind.expr) { const val = this.lowerExpr(stmt.kind.expr); - this.addOp({ type: "assign", dst: local, src: val }); + this.addOp({ type: "assign", dst, src: local(val) }); } else { - this.addOp({ type: "assign_null", dst: local }); + this.addOp({ type: "assign_null", dst }); } this.setTer({ type: "jump", target: block }); this.pushBlock(); @@ -110,8 +122,7 @@ class FnAstLowerer { case "assign": return this.lowerAssign(stmt); case "expr": { - const val = this.lowerExpr(stmt.kind.expr); - this.addOp({ type: "drop", val }); + this.lowerExpr(stmt.kind.expr); return; } } @@ -125,19 +136,19 @@ class FnAstLowerer { if (stmt.kind.assignType !== "=") { throw new Error("incomplete desugar"); } - const src = this.lowerExpr(stmt.kind.value); + const src = local(this.lowerExpr(stmt.kind.value)); const s = stmt.kind.subject; switch (s.kind.type) { case "field": { - const subject = this.lowerExpr(s.kind.subject); + const subject = local(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 }); + const subject = local(this.lowerExpr(s.kind.subject)); + const index = local(this.lowerExpr(s.kind.value)); + this.addOp({ type: "assign_index", subject, index, src }); return; } case "sym": { @@ -167,9 +178,12 @@ class FnAstLowerer { 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 }); + const srcId = this.lowerExpr(stmt.kind.value); + const dst = this.locals.allocMut( + stmt.kind.param.vtype!, + stmt.kind.param.sym!, + ); + this.addOp({ type: "assign", dst, src: local(srcId) }); this.letStmtIdLocals.set(stmt.id, dst); } @@ -177,30 +191,34 @@ class FnAstLowerer { switch (expr.kind.type) { case "error": { const dst = this.locals.alloc({ type: "error" }); - this.addOp({ type: "assign_error", dst }); + this.addOp({ type: "assign", dst, src: { type: "error" } }); return dst; } case "null": { const dst = this.locals.alloc({ type: "null" }); - this.addOp({ type: "assign_null", dst }); + this.addOp({ type: "assign", dst, src: { type: "null" } }); return dst; } case "bool": { const val = expr.kind.value; const dst = this.locals.alloc({ type: "bool" }); - this.addOp({ type: "assign_bool", dst, val }); + this.addOp({ type: "assign", dst, src: { type: "bool", val } }); return dst; } case "int": { const val = expr.kind.value; const dst = this.locals.alloc({ type: "int" }); - this.addOp({ type: "assign_int", dst, val }); + this.addOp({ type: "assign", dst, src: { type: "int", val } }); return dst; } case "string": { const val = expr.kind.value; const dst = this.locals.alloc({ type: "string" }); - this.addOp({ type: "assign_string", dst, val }); + this.addOp({ + type: "assign", + dst, + src: { type: "string", val }, + }); return dst; } case "ident": @@ -274,7 +292,7 @@ class FnAstLowerer { throw new Error(); } const ident = expr.kind.ident; - const subject = this.lowerExpr(expr.kind.subject); + const subject = local(this.lowerExpr(expr.kind.subject)); const subjectVType = expr.kind.subject.vtype!; if (subjectVType.type !== "struct") { @@ -296,8 +314,8 @@ class FnAstLowerer { if (expr.kind.type !== "index") { throw new Error(); } - const subject = this.lowerExpr(expr.kind.subject); - const index = this.lowerExpr(expr.kind.value); + const subject = local(this.lowerExpr(expr.kind.subject)); + const index = local(this.lowerExpr(expr.kind.value)); const dstVType = ((): VType => { const outer = expr.kind.subject.vtype!; @@ -320,9 +338,9 @@ class FnAstLowerer { throw new Error(); } - const args = expr.kind.args.map((arg) => this.lowerExpr(arg)); + const args = expr.kind.args.map((arg) => local(this.lowerExpr(arg))); - const subject = this.lowerExpr(expr.kind.subject); + const subject = local(this.lowerExpr(expr.kind.subject)); const subjectVType = expr.kind.subject.vtype!; if (subjectVType.type !== "fn") { @@ -346,8 +364,8 @@ class FnAstLowerer { //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 left = local(this.lowerExpr(expr.kind.left)); + const right = local(this.lowerExpr(expr.kind.right)); const dst = this.locals.alloc(expr.vtype!); @@ -364,19 +382,19 @@ class FnAstLowerer { throw new Error(); } const condBlock = this.currentBlock(); - const cond = this.lowerExpr(expr.kind.cond); + const cond = local(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); + const truthyVal = local(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); + const falsyVal = local(this.lowerExpr(expr.kind.falsy)); this.addOp({ type: "assign", dst: val, src: falsyVal }); this.setTer({ type: "jump", target: end }); @@ -385,6 +403,8 @@ class FnAstLowerer { this.setTerOn(condBlock, { type: "if", cond, truthy, falsy: end }); } + this.pushBlockWithId(end); + return val; } @@ -397,8 +417,9 @@ class FnAstLowerer { const breakBlock = this.reserveBlock(); this.breakStack.push({ local: val, block: breakBlock }); + const before = this.currentBlock(); const body = this.pushBlock(); - this.setTer({ type: "jump", target: body }); + this.setTerOn(before, { type: "jump", target: body }); this.lowerExpr(expr.kind.body); this.setTer({ type: "jump", target: body }); @@ -457,13 +478,18 @@ class FnAstLowerer { this.blockIdCounter += 1; const ter: Ter = { kind: { type: "error" } }; this.blocks.set(id, { id, ops: [], ter, label }); + this.currentBlockId = id; return id; } private pushBlockWithId(id: BlockId): BlockId { - this.blockIdCounter += 1; const ter: Ter = { kind: { type: "error" } }; this.blocks.set(id, { id, ops: [], ter }); + this.currentBlockId = id; return id; } } + +function local(id: LocalId): RValue { + return { type: "local", id }; +} diff --git a/compiler/middle/mir.ts b/compiler/middle/mir.ts index 725e3bd..d2f674a 100644 --- a/compiler/middle/mir.ts +++ b/compiler/middle/mir.ts @@ -1,4 +1,4 @@ -import { BinaryType, Stmt } from "../ast.ts"; +import { BinaryType, Stmt, Sym } from "../ast.ts"; import { VType, vtypeToString } from "../vtype.ts"; export type Mir = { @@ -9,13 +9,17 @@ export type Fn = { stmt: Stmt; locals: Local[]; blocks: Block[]; + entry: BlockId; + exit: BlockId; }; export type LocalId = number; export type Local = { id: LocalId; + mut: boolean; vtype: VType; + sym?: Sym; }; export type BlockId = number; @@ -32,24 +36,17 @@ export type Op = { }; type L = LocalId; +type R = RValue; 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 }; + | { type: "assign"; dst: L; src: R } + | { type: "field"; dst: L; subject: R; ident: string } + | { type: "assign_field"; subject: R; ident: string; src: R } + | { type: "index"; dst: L; subject: R; index: R } + | { type: "assign_index"; subject: R; index: R; src: R } + | { type: "call_val"; dst: L; subject: R; args: R[] } + | { type: "binary"; binaryType: BinaryType; dst: L; left: R; right: R }; export type Ter = { kind: TerKind; @@ -57,8 +54,158 @@ export type Ter = { export type TerKind = | { type: "error" } + | { type: "return" } | { type: "jump"; target: BlockId } - | { type: "if"; cond: L; truthy: BlockId; falsy: BlockId }; + | { type: "if"; cond: R; truthy: BlockId; falsy: BlockId }; + +export type RValue = + | { type: "error" } + | { type: "local"; id: BlockId } + | { type: "null" } + | { type: "bool"; val: boolean } + | { type: "int"; val: number } + | { type: "string"; val: string } + | { type: "fn"; stmt: Stmt }; + +export function visitBlockDsts( + block: Block, + visit: (local: LocalId, index: number) => void, +) { + for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) { + const ok = op.kind; + switch (ok.type) { + case "error": + break; + case "assign": + case "field": + case "index": + case "call_val": + case "binary": + visit(ok.dst, i); + break; + case "assign_field": + case "assign_index": + break; + default: + throw new Error(); + } + } +} + +export function replaceBlockSrcs( + block: Block, + replace: (src: RValue) => RValue, +) { + for (const op of block.ops) { + const ok = op.kind; + switch (ok.type) { + case "error": + break; + case "assign": + ok.src = replace(ok.src); + break; + case "field": + ok.subject = replace(ok.subject); + break; + case "assign_field": + ok.subject = replace(ok.subject); + ok.src = replace(ok.src); + break; + case "index": + ok.subject = replace(ok.subject); + ok.index = replace(ok.index); + break; + case "assign_index": + ok.subject = replace(ok.subject); + ok.index = replace(ok.index); + ok.src = replace(ok.src); + break; + case "call_val": + ok.subject = replace(ok.subject); + ok.args = ok.args.map((arg) => replace(arg)); + break; + case "binary": + ok.left = replace(ok.left); + ok.right = replace(ok.right); + break; + default: + throw new Error(); + } + } + const tk = block.ter.kind; + switch (tk.type) { + case "error": + break; + case "return": + break; + case "jump": + break; + case "if": + tk.cond = replace(tk.cond); + break; + } +} + +export function visitBlockSrcs( + block: Block, + visitor: (src: RValue, op?: Op, index?: number, ter?: Ter) => void, +) { + for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) { + const ok = op.kind; + switch (ok.type) { + case "error": + break; + case "assign": + visitor(ok.src, op, i); + break; + case "field": + visitor(ok.subject, op, i); + break; + case "assign_field": + visitor(ok.subject, op, i); + visitor(ok.src, op, i); + break; + case "index": + visitor(ok.subject, op, i); + visitor(ok.index, op, i); + break; + case "assign_index": + visitor(ok.subject, op, i); + visitor(ok.index, op, i); + visitor(ok.src, op, i); + break; + case "call_val": + visitor(ok.subject, op, i); + ok.args.map((arg) => visitor(arg, op, i)); + break; + case "binary": + visitor(ok.left, op, i); + visitor(ok.right, op, i); + break; + default: + throw new Error(); + } + } + const tk = block.ter.kind; + switch (tk.type) { + case "error": + break; + case "return": + break; + case "jump": + break; + case "if": + visitor(tk.cond, undefined, undefined, block.ter); + break; + } +} + +export function mirOpCount(mir: Mir): number { + return mir.fns + .reduce((acc, fn) => + acc + fn.blocks + .reduce((acc, block) => acc + block.ops.length + 1, 0), 0); +} export function printMir(mir: Mir) { for (const fn of mir.fns) { @@ -66,7 +213,7 @@ export function printMir(mir: Mir) { if (stmt.kind.type !== "fn") { throw new Error(); } - const name = stmt.kind.ident; + const name = stmt.kind.sym!.fullPath; const vtype = stmt.kind.vtype; if (vtype?.type !== "fn") { @@ -75,78 +222,106 @@ export function printMir(mir: Mir) { const generics = vtype.genericParams ?.map(({ ident }) => `${ident}`).join(", ") ?? ""; const params = vtype.params - .map(({ vtype }, i) => - `_${fn.locals[i + 1].id}: ${vtypeToString(vtype)}` + .map(({ mut, vtype }, i) => + `${mut && "mut" || ""} _${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)};`); + console.log(`${name}${generics}(${params}) -> ${returnType} {`); + + const paramIndices = vtype.params.map((_v, i) => i + 1); + for ( + const { id, vtype, mut } of fn.locals + .filter((_v, i) => !paramIndices.includes(i)) + ) { + const m = mut ? "mut" : ""; + const v = vtypeToString(vtype); + console.log(` let ${m} _${id}: ${v};`); } for (const block of fn.blocks) { - console.log(`.${block.label ?? block.id}:`); + const l = (msg: string) => console.log(` ${msg}`); + const r = rvalueToString; + + console.log(` ${block.label ?? "bb" + 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};`); + l(`;`); break; case "assign": - console.log(` _${k.dst} = _${k.src};`); + l(`_${k.dst} = ${r(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};`, - ); + l(`_${k.dst} = ${r(k.subject)}.${k.ident};`); + break; + case "assign_field": + l(`${r(k.subject)}.${k.ident} = ${r(k.src)};`); break; case "index": - console.log( - ` _${k.dst} = _${k.subject}[_${k.index}];`, - ); + l(`_${k.dst} = ${r(k.subject)}[${r(k.index)}];`); + break; + case "assign_index": + l(`${r(k.subject)}[${r(k.index)}] = ${r(k.src)};`); break; case "call_val": { - const args = k.args.map((arg) => `_${arg}`).join(", "); - console.log(` _${k.dst} = _${k.subject}(${args});`); + const args = k.args.map((arg) => r(arg)).join(", "); + l(`_${k.dst} = call ${r(k.subject)}(${args});`); break; } case "binary": { - console.log( - ` _${k.dst} = _${k.left} ${k.binaryType} _${k.right};`, - ); + l(`_${k.dst} = ${r(k.left)} ${k.binaryType} ${ + r(k.right) + };`); break; } } } + const tk = block.ter.kind; + switch (tk.type) { + case "error": + l(`;`); + break; + case "return": + l(`return;`); + break; + case "jump": + l(`jump bb${tk.target};`); + break; + case "if": + l(`if ${ + r(tk.cond) + }, true: bb${tk.truthy}, false: bb${tk.falsy};`); + break; + } + console.log(" }"); + } + console.log("}"); + } +} + +export function rvalueToString(rvalue: RValue): string { + switch (rvalue.type) { + case "error": + return ``; + case "local": + return `_${rvalue.id}`; + case "null": + return "null"; + case "bool": + return `${rvalue.val}`; + case "int": + return `${rvalue.val}`; + case "string": + return `"${rvalue.val}"`; + case "fn": { + const stmt = rvalue.stmt; + if (stmt.kind.type !== "fn") { + throw new Error(); + } + return stmt.kind.sym!.fullPath; } } } diff --git a/compiler/vtype.ts b/compiler/vtype.ts index c0bbe1b..48a8d8e 100644 --- a/compiler/vtype.ts +++ b/compiler/vtype.ts @@ -23,6 +23,7 @@ export type VType = export type VTypeParam = { ident: string; + mut: boolean; vtype: VType; }; diff --git a/examples/transient_variable.slg b/examples/transient_variable.slg new file mode 100644 index 0000000..85e5009 --- /dev/null +++ b/examples/transient_variable.slg @@ -0,0 +1,22 @@ +// mod std; + +fn black_box(v: int) { } + +fn add(a: int, b: int) -> int { + let s = a + b; + if false {} + s +} + +fn main() { + let a = 5; + + loop { + a = 3; + } + + let b = a; + let c = b; + + black_box(b); +} diff --git a/examples/unused_variable.slg b/examples/unused_variable.slg new file mode 100644 index 0000000..8030720 --- /dev/null +++ b/examples/unused_variable.slg @@ -0,0 +1,6 @@ +// mod std; + +fn main() { + let a = 5; + let b = a; +}