add optimizations

This commit is contained in:
sfja 2025-01-03 01:15:05 +01:00
parent 3babacd58d
commit a7349890d0
13 changed files with 839 additions and 116 deletions

View File

@ -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") {

View File

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

View File

@ -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<Report, "type">) {
this.reports.push({ ...report, type: "warning" });
this.printReport({ ...report, type: "warning" });
}
private printReport({ reporter, type, pos, msg }: Report) {
console.error(
`${reporter} ${type}: ${msg}${

103
compiler/middle/cfg.ts Normal file
View File

@ -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<BlockId, CfgNode>,
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 = <T>(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<BlockId, CfgNode>();
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]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(` <error>;`);
break;
case "return":
console.log(` return;`);
break;
case "drop":
console.log(` drop _${k.val};`);
l(`<error>;`);
break;
case "assign":
console.log(` _${k.dst} = _${k.src};`);
l(`_${k.dst} = ${r(k.src)};`);
break;
case "assign_error":
console.log(` _${k.dst} = <error>;`);
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(`<error>;`);
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 `<error>`;
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;
}
}
}

View File

@ -23,6 +23,7 @@ export type VType =
export type VTypeParam = {
ident: string;
mut: boolean;
vtype: VType;
};

View File

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

View File

@ -0,0 +1,6 @@
// mod std;
fn main() {
let a = 5;
let b = a;
}