ethos/src/mir_interpreter.ts
sfja 856618d113
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 8s
added unary ops
2026-03-11 19:21:22 +01:00

223 lines
7.1 KiB
TypeScript

import * as mir from "./middle.ts";
export class FnInterpreter {
private regs = new Map<mir.Inst, Val>();
private locals: (Val | null)[] = [];
private localMap = new Map<mir.Inst, number>();
private bb: mir.BasicBlock;
private instIdx = 0;
constructor(
private fn: mir.Fn,
private args: Val[],
) {
this.bb = this.fn.bbs[0];
}
eval(): Val {
while (this.instIdx < this.bb.insts.length) {
const inst = this.bb.insts[this.instIdx];
this.instIdx += 1;
const k = inst.kind;
switch (k.tag) {
case "Error":
throw new Error();
case "Void":
case "Int":
case "Bool":
case "Fn":
this.regs.set(inst, new Val(k));
break;
case "Param":
this.regs.set(inst, this.args[k.idx]);
break;
case "Call": {
const fn = this.regs.get(k.callee);
if (!fn || fn.kind.tag !== "Fn") {
throw new Error();
}
const args = k.args.map((arg) => this.regs.get(arg)!);
const val = new FnInterpreter(fn.kind.fn, args).eval();
this.regs.set(inst, val);
break;
}
case "AllocLocal":
this.localMap.set(inst, this.locals.length);
this.locals.push(null);
break;
case "LocalLoad":
if (!this.localMap.has(k.source)) {
throw new Error();
}
if (this.locals[this.localMap.get(k.source)!] === null) {
throw new Error();
}
this.regs.set(
inst,
this.locals[this.localMap.get(k.source)!]!,
);
break;
case "LocalStore":
if (!this.localMap.has(k.target)) {
throw new Error();
}
this.locals[this.localMap.get(k.target)!] = this.regs.get(
k.source,
)!;
break;
case "Jump": {
this.bb = k.target;
this.instIdx = 0;
break;
}
case "Branch": {
const cond = this.regs.get(k.cond)!;
if (cond.kind.tag !== "Bool") {
throw new Error();
}
this.bb = cond.kind.value ? k.truthy : k.falsy;
this.instIdx = 0;
break;
}
case "Return":
return this.regs.get(k.source)!;
case "Not": {
const source = this.regs.get(k.source)!;
if (source.kind.tag !== "Bool") {
throw new Error();
}
this.regs.set(
inst,
new Val({ tag: "Bool", value: !source.kind.value }),
);
break;
}
case "Negate": {
const source = this.regs.get(k.source)!;
if (source.kind.tag !== "Int") {
throw new Error();
}
this.regs.set(
inst,
new Val({ tag: "Int", value: -source.kind.value }),
);
break;
}
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":
this.evalBinaryOp(inst, k);
break;
case "DebugPrint":
console.log(
k.args
.map((a) => this.regs.get(a)!.pretty())
.join(", "),
);
break;
default:
k satisfies never;
}
}
return Val.Void;
}
private evalBinaryOp(
inst: mir.Inst,
k: mir.InstKind & { tag: mir.BinaryOp },
) {
const left = this.regs.get(k.left)!;
const right = this.regs.get(k.right)!;
if (left.kind.tag === "Int" && right.kind.tag === "Int") {
const l = left.kind.value;
const r = right.kind.value;
const value = (() => {
const Int = (value: number) => new Val({ tag: "Int", value });
const Bool = (value: boolean) =>
new Val({ tag: "Bool", value });
switch (k.tag) {
case "Eq":
return Bool(l === r);
case "Ne":
return Bool(l !== r);
case "Lt":
return Bool(l < r);
case "Gt":
return Bool(l > r);
case "Lte":
return Bool(l <= r);
case "Gte":
return Bool(l >= r);
case "BitOr":
case "BitXor":
case "BitAnd":
case "Shl":
case "Shr":
break;
case "Add":
return Int(l + r);
case "Sub":
return Int(l - r);
case "Mul":
return Int(l * r);
case "Div":
return Int(Math.floor(l / r));
case "Rem":
return Int(l % r);
}
throw new Error(`'${k.tag}' not handled`);
})();
this.regs.set(inst, value);
return;
}
throw new Error(`'${k.tag}' not handled`);
}
}
class Val {
constructor(
public kind: ValKind,
) {}
static Void = new Val({ tag: "Void" });
pretty() {
const k = this.kind;
switch (k.tag) {
case "Void":
return "void";
case "Int":
case "Bool":
return `${k.value}`;
case "Fn":
return `<${k.fn.ty.pretty()}>`;
default:
k satisfies never;
}
}
}
type ValKind =
| { tag: "Void" }
| { tag: "Int"; value: number }
| { tag: "Bool"; value: boolean }
| { tag: "Fn"; fn: mir.Fn };