Compare commits

..

No commits in common. "c5c09eb10ee6bc0ba02279a924c8895001a43fbc" and "696bfc55f40008c9f1e822879d1d6d5463aaf3e6" have entirely different histories.

3 changed files with 173 additions and 229 deletions

View File

@ -1,6 +1,5 @@
import * as ast from "./ast.ts"; import * as ast from "./ast.ts";
import { Ty } from "./ty.ts"; import { Ty } from "./ty.ts";
import * as stringify from "./stringify.ts";
export interface Visitor { export interface Visitor {
visitFn?(fn: Fn): void; visitFn?(fn: Fn): void;
@ -23,7 +22,46 @@ export class Fn {
} }
pretty(): string { pretty(): string {
return stringify.mirFnPretty(this); const fnTy = this.ty.is("FnStmt") && this.ty.kind.ty.is("Fn")
? this.ty.kind.ty
: null;
if (!fnTy) {
throw new Error();
}
const cx = new PrettyCx();
return `fn ${this.stmt.kind.ident}(${
fnTy.kind.params
.map((ty, idx) => `${idx}: ${ty.pretty()}`)
.join(", ")
}) -> ${fnTy.kind.retTy.pretty()}\n{\n${
this.bbs
.map((bb) => bb.pretty(cx))
.join("\n")
}\n}`;
}
}
class IdMap<T> {
private map = new Map<T, number>();
private counter = 0;
id(val: T): number {
if (!this.map.has(val)) {
this.map.set(val, this.counter++);
}
return this.map.get(val)!;
}
}
export class PrettyCx {
private bbIds = new IdMap<BasicBlock>();
private regIds = new IdMap<Inst>();
bbId(bb: BasicBlock): number {
return this.bbIds.id(bb);
}
regId(reg: Inst): number {
return this.regIds.id(reg);
} }
} }
@ -36,6 +74,18 @@ export class BasicBlock {
inst.visit(v); inst.visit(v);
} }
} }
pretty(cx: PrettyCx): string {
const consts = ["Void", "Int", "Bool", "Array"];
return `bb${cx.bbId(this)}:\n${
this.insts
.filter((inst) => !consts.includes(inst.kind.tag))
.map((inst) => inst.pretty(cx))
.map((line) => ` ${line}`)
.join("\n")
}`;
}
} }
export class Inst { export class Inst {
@ -47,6 +97,88 @@ export class Inst {
visit(v: Visitor) { visit(v: Visitor) {
v.visitInst?.(this); v.visitInst?.(this);
} }
pretty(cx: PrettyCx): string {
const valueless = ["Store", "Jump", "Branch", "Return", "DebugPrint"];
const valueType = `%${cx.regId(this)} (${this.ty.pretty()}) = `;
return `${
!valueless.includes(this.kind.tag) ? valueType : ""
}${this.kind.tag} ${this.prettyArgs(cx)}`;
}
private prettyArgs(cx: PrettyCx): string {
const consts = ["Void", "Int", "Bool", "Array"];
const r = (v: Inst) =>
consts.includes(v.kind.tag) ? v.prettyArgs(cx) : `%${cx.regId(v)}`;
const k = this.kind;
switch (k.tag) {
case "Error":
return "";
case "Void":
return "";
case "Int":
return `${k.value}${k.intTy}`;
case "Bool":
return `${k.value}`;
case "Str":
return `${JSON.stringify(k.value)}`;
case "Array":
return `[${k.values.map(r).join(", ")}]`;
case "Fn":
return `${k.fn.stmt.kind.ident}`;
case "Param":
return `${k.idx}`;
case "GetElemPtr":
return `&[ptr ${r(k.base)}][${r(k.offset)}]`;
case "Slice":
return `&[ptr ${r(k.value)}][${k.begin ? r(k.begin) : ""}..${
k.end ? r(k.end) : ""
}]`;
case "Call":
return `${r(k.callee)} (${k.args.map(r).join(", ")})`;
case "Alloca":
return "";
case "Load":
return `[ptr ${r(k.source)}]`;
case "Store":
return `[ptr ${r(k.target)}] = ${r(k.source)}`;
case "Jump":
return `bb${cx.bbId(k.target)}`;
case "Branch":
return `if ${r(k.cond)}: bb${cx.bbId(k.truthy)}, else: bb${
cx.bbId(k.falsy)
}`;
case "Return":
return `${r(k.source)}`;
case "Not":
case "Negate":
return `${r(k.source)}`;
case "Eq":
case "Ne":
case "Lt":
case "Gt":
case "Lte":
case "Gte":
case "BitOr":
case "BitXor":
case "BitAnd":
case "Shl":
case "Shr":
case "Add":
case "Sub":
case "Mul":
case "Div":
case "Rem":
return `${r(k.left)} ${r(k.right)}`;
case "Len":
return `${r(k.source)}`;
case "DebugPrint":
return `${k.args.map(r).join(", ")}`;
}
k satisfies never;
}
} }
export type InstKind = export type InstKind =

View File

@ -1,225 +0,0 @@
import * as ty from "./ty.ts";
import * as mir from "./mir.ts";
export function tyPretty(ty: ty.Ty): string {
switch (ty.kind.tag) {
case "Error":
return "<error>";
case "Void":
return "void";
case "IntLiteral":
return "{integer}";
case "Int":
return `${ty.kind.intTy}`;
case "Bool":
return "bool";
case "Ptr":
return `*${ty.kind.ty.pretty()}`;
case "PtrMut":
return `*mut ${ty.kind.ty.pretty()}`;
case "Array":
return `[${ty.kind.ty.pretty()}; ${ty.kind.length}]`;
case "Slice":
return `[${ty.kind.ty.pretty()}]`;
case "Range":
return `Range`;
case "Fn":
return `fn (${
ty.kind.params.map((param) => param.pretty()).join(", ")
}) -> ${ty.kind.retTy.pretty()}`;
case "FnStmt":
if (!ty.kind.ty.is("Fn")) {
throw new Error();
}
return `fn ${ty.kind.stmt.kind.ident}(${
ty.kind.ty.kind.params.map((param) => param.pretty())
.join(
", ",
)
}) -> ${ty.kind.ty.kind.retTy.pretty()}`;
}
return "<unhandled>";
}
export function mirFnPretty(fn: mir.Fn): string {
return new MirFnPrettyStringifier(fn).stringify();
}
class MirFnPrettyStringifier {
private bbIds = new Map<mir.BasicBlock, number>();
private instIds = new Map<mir.Inst, number>();
private result = "";
constructor(
private fn: mir.Fn,
) {}
stringify(): string {
const fnTy = this.fn.ty.is("FnStmt") && this.fn.ty.kind.ty.is("Fn")
? this.fn.ty.kind.ty
: null;
if (!fnTy) {
throw new Error();
}
const ident = this.fn.stmt.kind.ident;
const params = fnTy.kind.params
.map((ty, idx) => `${idx}: ${ty.pretty()}`)
.join(", ");
const retTy = fnTy.kind.retTy.pretty();
this.result += `fn ${ident}(${params}) -> ${retTy} {\n`;
for (const bb of this.fn.bbs) {
this.basicBlock(bb);
}
return this.result;
}
private basicBlock(bb: mir.BasicBlock) {
this.result += `bb${this.bbId(bb)}:\n`;
for (const inst of bb.insts) {
this.inst(inst);
}
}
private instsPrinted = new Set<mir.Inst>();
private inst(inst: mir.Inst) {
if (this.instsPrinted.has(inst)) {
return;
}
// inst is a value
const expr = (tail: string) => {
this.result += ` %${
this.instId(inst)
} (${inst.ty.pretty()}) = ${inst.kind.tag}${tail}\n`;
};
// inst is a statement
const stmt = (tail: string) => {
this.result += ` ${inst.kind.tag}${tail}\n`;
};
this.instTail(inst, expr, stmt);
this.instsPrinted.add(inst);
}
private instArg(inst: mir.Inst): string {
const inline: mir.InstKind["tag"][] = ["Void", "Int", "Bool", "Array"];
if (
inline.includes(inst.kind.tag) &&
!this.instsPrinted.has(inst)
) {
let str = `${inst.kind.tag}`;
this.instTail(inst, (s) => {
str += s;
}, () => {});
return str;
}
this.inst(inst);
return `%${this.instId(inst)}`;
}
private instTail(
inst: mir.Inst,
expr: (str: string) => void,
stmt: (str: string) => void,
) {
const r = (inst: mir.Inst) => this.instArg(inst);
const k = inst.kind;
switch (k.tag) {
case "Error":
return expr(``);
case "Void":
return expr(``);
case "Int":
return expr(` ${k.value}${k.intTy}`);
case "Bool":
return expr(` ${k.value}`);
case "Str":
return expr(` ${JSON.stringify(k.value)}`);
case "Array":
return expr(` [${k.values.map(r).join(", ")}]`);
case "Fn":
return expr(` ${k.fn.stmt.kind.ident}`);
case "Param":
return expr(` ${k.idx}`);
case "GetElemPtr":
return expr(` &(ptr ${r(k.base)})[${r(k.offset)}]`);
case "Slice":
return expr(
` &[ptr ${r(k.value)}][${k.begin ? r(k.begin) : ""}..${
k.end ? r(k.end) : ""
}]`,
);
case "Call":
return expr(` ${r(k.callee)} (${k.args.map(r).join(", ")})`);
case "Alloca":
return expr(``);
case "Load":
return expr(` [ptr ${r(k.source)}]`);
case "Store":
return stmt(` [ptr ${r(k.target)}] = ${r(k.source)}`);
case "Jump":
return stmt(` bb${this.bbId(k.target)}`);
case "Branch":
return stmt(
` if ${r(k.cond)}: bb${this.bbId(k.truthy)}, else: bb${
this.bbId(k.falsy)
}`,
);
case "Return":
return stmt(` ${r(k.source)}`);
case "Not":
case "Negate":
return expr(`${r(k.source)}`);
case "Eq":
case "Ne":
case "Lt":
case "Gt":
case "Lte":
case "Gte":
case "BitOr":
case "BitXor":
case "BitAnd":
case "Shl":
case "Shr":
case "Add":
case "Sub":
case "Mul":
case "Div":
case "Rem":
return expr(` ${r(k.left)}, ${r(k.right)}`);
case "Len":
return expr(` ${r(k.source)}`);
case "DebugPrint":
return stmt(` ${k.args.map(r).join(", ")}`);
}
}
private bbId(bb: mir.BasicBlock) {
const found = this.bbIds.get(bb);
if (found !== undefined) {
return found;
}
const id = this.bbIds.size;
this.bbIds.set(bb, id);
return id;
}
private instId(inst: mir.Inst) {
const found = this.instIds.get(inst);
if (found !== undefined) {
return found;
}
const id = this.instIds.size;
this.instIds.set(inst, id);
return id;
}
}

View File

@ -1,5 +1,4 @@
import * as ast from "./ast.ts"; import * as ast from "./ast.ts";
import * as stringify from "./stringify.ts";
export class Ty { export class Ty {
private static idCounter = 0; private static idCounter = 0;
@ -146,7 +145,45 @@ export class Ty {
} }
pretty(): string { pretty(): string {
return stringify.tyPretty(this); switch (this.kind.tag) {
case "Error":
return "<error>";
case "Void":
return "void";
case "IntLiteral":
return "{integer}";
case "Int":
return `${this.kind.intTy}`;
case "Bool":
return "bool";
case "Ptr":
return `*${this.kind.ty.pretty()}`;
case "PtrMut":
return `*mut ${this.kind.ty.pretty()}`;
case "Array":
return `[${this.kind.ty.pretty()}; ${this.kind.length}]`;
case "Slice":
return `[${this.kind.ty.pretty()}]`;
case "Range":
return `Range`;
case "Fn":
return `fn (${
this.kind.params.map((param) => param.pretty()).join(", ")
}) -> ${this.kind.retTy.pretty()}`;
case "FnStmt":
if (!this.kind.ty.is("Fn")) {
throw new Error();
}
return `fn ${this.kind.stmt.kind.ident}(${
this.kind.ty.kind.params.map((param) => param.pretty())
.join(
", ",
)
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
default:
this.kind satisfies never;
}
throw new Error("unhandled");
} }
} }