Author | SHA1 | Message | Date |
---|---|---|---|
Forest Belton | d7060781ae | Reorganize codebase and implement liveness check | 3 years ago |
Forest Belton | b7b5514247 | Add basic unit tests | 3 years ago |
Forest Belton | f2d54fde09 | Improve IR format | 3 years ago |
@ -1,240 +0,0 @@ | |||||
import type { SSA, SSABinary, SSACopy, SSAUnary } from "./ir" | |||||
const reg8 = ["B", "C", "D", "E"] as const | |||||
type R8 = typeof reg8[number] | |||||
class ASMState { | |||||
regs: { [r in R8]: string | null } | |||||
syms: { [s: string]: R8 } | |||||
stack_offsets: { [s: string]: number } | |||||
cur_offset: number | |||||
insns: Array<string> | |||||
constructor() { | |||||
this.regs = { "B": null, "C": null, "D": null, "E": null } | |||||
this.syms = {} | |||||
this.stack_offsets = {} | |||||
this.cur_offset = 0 | |||||
this.insns = [] | |||||
} | |||||
// TODO: Generalize for u16 | |||||
get_sym_loc(sym: string): R8 | number | null { | |||||
// return register if already allocated | |||||
if (typeof this.syms[sym] !== "undefined") { | |||||
return this.syms[sym] | |||||
} | |||||
// return stack offset if already allocated | |||||
if (typeof this.stack_offsets[sym] !== "undefined") { | |||||
return this.cur_offset - this.stack_offsets[sym] | |||||
} | |||||
return null | |||||
} | |||||
alloc_sym_loc(sym: string): R8 | number { | |||||
const existing_loc = this.get_sym_loc(sym) | |||||
if (existing_loc !== null) { | |||||
return existing_loc | |||||
} | |||||
// look for free register | |||||
for (const r8 of reg8) { | |||||
if (this.regs[r8] === null) { | |||||
this.regs[r8] = sym | |||||
this.syms[sym] = r8 | |||||
return r8 | |||||
} | |||||
} | |||||
// allocate space on stack | |||||
this.cur_offset++ | |||||
this.stack_offsets[sym] = this.cur_offset | |||||
this.insns.push("ADD SP, -1") | |||||
return this.stack_offsets[sym] | |||||
} | |||||
} | |||||
export const convertASM = (ir: Array<SSA>): Array<string> => { | |||||
const state = new ASMState() | |||||
ir.forEach(ssa => convertASM_SSA(state, ssa)) | |||||
for (let sym of Object.keys(state.syms)) { | |||||
if (sym.startsWith("temp")) { | |||||
continue | |||||
} | |||||
state.insns.push( | |||||
`LD A, ${state.syms[sym]}`, | |||||
`LD (${sym}), A` | |||||
) | |||||
} | |||||
for (let sym of Object.keys(state.stack_offsets)) { | |||||
if (sym.startsWith("temp")) { | |||||
continue | |||||
} | |||||
state.insns.push( | |||||
`LD HL, SP + (${state.cur_offset - state.stack_offsets[sym]})`, | |||||
`LD A, (HL)`, | |||||
`LD (${sym}), A` | |||||
) | |||||
} | |||||
if (state.cur_offset !== 0) { | |||||
state.insns.push(`ADD SP, ${state.cur_offset}`) | |||||
} | |||||
return state.insns | |||||
} | |||||
// TODO: Track liveness | |||||
const convertASM_SSA = (state: ASMState, ssa: SSA): void => { | |||||
if ("source1" in ssa) { // Binary | |||||
convertASM_SSA_Binary(state, ssa) | |||||
} else if ("op" in ssa) { // Unary | |||||
convertASM_SSA_Unary(state, ssa) | |||||
} else { // Copy | |||||
convertASM_SSA_Copy(state, ssa) | |||||
} | |||||
} | |||||
export const convertASM_SSA_Binary = (state: ASMState, ssa: SSABinary): void => { | |||||
const dest = state.alloc_sym_loc(ssa.dest) | |||||
if (typeof ssa.source === "number") { | |||||
state.insns.push(`LD A, ${ssa.source}`) | |||||
} else { | |||||
const loc = state.get_sym_loc(ssa.source) | |||||
if (loc === null) { | |||||
state.insns.push(`LD A, (${ssa.source})`) | |||||
} else if (typeof loc === "string") { | |||||
state.insns.push(`LD A, ${loc}`) | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${loc})`, | |||||
`LD A, (HL)` | |||||
) | |||||
} | |||||
} | |||||
switch (ssa.op) { | |||||
case "add": | |||||
if (typeof ssa.source1 === "number") { | |||||
state.insns.push(`ADD ${ssa.source1}`) | |||||
} else { | |||||
const loc = state.get_sym_loc(ssa.source1) | |||||
if (loc === null) { | |||||
throw new Error('fuck') | |||||
} else if (typeof loc === "string") { | |||||
state.insns.push(`ADD ${loc}`) | |||||
} else { | |||||
throw new Error('fuck') | |||||
state.insns.push( | |||||
`LD HL, SP + (${loc})` | |||||
) | |||||
} | |||||
} | |||||
break | |||||
default: | |||||
throw new Error(`unsupported binary op \`${ssa.op}'`) | |||||
} | |||||
if (typeof dest === "string") { | |||||
state.insns.push(`LD ${dest}, A`) | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${dest})`, | |||||
`LD (HL), A` | |||||
) | |||||
} | |||||
} | |||||
export const convertASM_SSA_Unary = (state: ASMState, ssa: SSAUnary): void => { | |||||
const dest = state.alloc_sym_loc(ssa.dest) | |||||
if (typeof ssa.source == "number") { | |||||
state.insns.push(`LD A, ${ssa.source}`) | |||||
} else { | |||||
const loc = state.get_sym_loc(ssa.source) | |||||
if (loc === null) { | |||||
state.insns.push(`LD A, (${ssa.source})`) | |||||
} else if (typeof loc === "string") { | |||||
state.insns.push(`LD A, ${loc}`) | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${loc})`, | |||||
`LD A, (HL)` | |||||
) | |||||
} | |||||
} | |||||
switch (ssa.op) { | |||||
case 'arith_negate': | |||||
state.insns.push( | |||||
`CPL`, | |||||
`INC A`, | |||||
) | |||||
break | |||||
case 'bit_negate': | |||||
state.insns.push( | |||||
`CPL` | |||||
) | |||||
break | |||||
default: | |||||
throw new Error(`unsupported unary op \`'${ssa.op}`) | |||||
} | |||||
if (typeof dest === "string") { | |||||
state.insns.push(`LD ${dest}, A`) | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${dest})`, | |||||
`LD (HL), A` | |||||
) | |||||
} | |||||
} | |||||
export const convertASM_SSA_Copy = (state: ASMState, ssa: SSACopy): void => { | |||||
const dest = state.alloc_sym_loc(ssa.dest) | |||||
let source = "" | |||||
if (typeof ssa.source == "number") { | |||||
source = ssa.source.toString() | |||||
} else { | |||||
const loc = state.get_sym_loc(ssa.source) | |||||
if (loc === null) { | |||||
state.insns.push(`LD A, (${ssa.source})`) | |||||
source = "A" | |||||
} else if (typeof loc === "string") { | |||||
source = loc | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${loc})`, | |||||
`LD A, (HL)` | |||||
) | |||||
source = "A" | |||||
} | |||||
} | |||||
if (typeof dest === "string") { | |||||
state.insns.push(`LD ${dest}, ${source}`) | |||||
} else { | |||||
state.insns.push( | |||||
`LD HL, SP + (${dest})`, | |||||
`LD (HL), ${source}` | |||||
) | |||||
} | |||||
} |
@ -0,0 +1,25 @@ | |||||
export const intersect = <A>(xs: Set<A>, ys: Set<A>): Set<A> => { | |||||
const out: Set<A> = new Set() | |||||
xs.forEach(x => { | |||||
if (ys.has(x)) { | |||||
out.add(x) | |||||
} | |||||
}) | |||||
return out | |||||
} | |||||
export const setEquals = <A>(xs: Set<A>, ys: Set<A>): boolean => { | |||||
if (xs.size != ys.size) { | |||||
return false | |||||
} | |||||
for (const x of xs) { | |||||
if (!ys.has(x)) { | |||||
return false | |||||
} | |||||
} | |||||
return true | |||||
} |
@ -0,0 +1,43 @@ | |||||
export enum LocType { | |||||
REGISTER = "register", | |||||
TEMPORARY = "temporary", | |||||
VARIABLE = "variable", | |||||
} | |||||
export class Loc { | |||||
type: LocType | |||||
name: string | |||||
constructor(type: LocType, name: string) { | |||||
this.type = type | |||||
this.name = name | |||||
} | |||||
toString(): string { | |||||
let sigil = '' | |||||
switch (this.type) { | |||||
case 'register': | |||||
sigil = '%' | |||||
break | |||||
case 'temporary': | |||||
sigil = '#' | |||||
break | |||||
} | |||||
return sigil + this.name | |||||
} | |||||
static vari(name: string): Loc { | |||||
return new Loc(LocType.VARIABLE, name) | |||||
} | |||||
static temp(name: string): Loc { | |||||
return new Loc(LocType.TEMPORARY, name) | |||||
} | |||||
static reg(name: string): Loc { | |||||
return new Loc(LocType.REGISTER, name) | |||||
} | |||||
} |
@ -0,0 +1,45 @@ | |||||
import type { Loc } from "./loc" | |||||
import type { Operand, SSA } from "./ssa" | |||||
export const prettyPrintBlock = (block: Array<SSA>): string => { | |||||
return block.map(prettyPrintSSA).join("\n") | |||||
} | |||||
export const prettyPrintSSA = (ssa: SSA): string => { | |||||
let output = "" | |||||
if ("source1" in ssa) { | |||||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)}, ${ppOp(ssa.source1)})` | |||||
} else if ("op" in ssa) { | |||||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)})` | |||||
} else { | |||||
output = `${ppLoc(ssa.dest)} = ${ppOp(ssa.source)}` | |||||
} | |||||
return output | |||||
} | |||||
const ppOp = (op: Operand): string => | |||||
typeof op === "number" | |||||
? op.toString() | |||||
: ppLoc(op) | |||||
const ppLoc = (loc: Loc): string => { | |||||
let output = '' | |||||
switch (loc.type) { | |||||
case "register": | |||||
output = `%${loc.name}` | |||||
break | |||||
case "temporary": | |||||
output = `#${loc.name}` | |||||
break | |||||
case "variable": | |||||
output = loc.name | |||||
break | |||||
} | |||||
return output | |||||
} |
@ -0,0 +1,17 @@ | |||||
import type { BinaryOp, UnaryOp } from "../ast" | |||||
import type { Loc } from "./loc" | |||||
export type Operand = Loc | number | |||||
type ASSA<Data> = { dest: Loc, source: Operand } & Data | |||||
// dest = source | |||||
export type SSACopy = ASSA<{}> | |||||
// dest = op(source) | |||||
export type SSAUnary = ASSA<{ op: UnaryOp }> | |||||
// dest = op(source, source1) | |||||
export type SSABinary = ASSA<{ op: BinaryOp, source1: Operand }> | |||||
export type SSA = SSACopy | SSAUnary | SSABinary |
@ -0,0 +1,33 @@ | |||||
import type { Loc } from "./ir/loc" | |||||
import type { SSA } from "./ir/ssa" | |||||
export type LivenessInfo = Array<Set<Loc>> | |||||
export const liveness = (block: Array<SSA>): LivenessInfo => { | |||||
const info: LivenessInfo = [] | |||||
for (let i = block.length - 1; i >= 0; --i) { | |||||
const insn = block[i] | |||||
const last = info[i + 1] || new Set() | |||||
info[i] = new Set() | |||||
if (typeof insn.source !== "number") { | |||||
info[i].add(insn.source) | |||||
} | |||||
if ("source1" in insn && typeof insn.source1 !== "number") { | |||||
info[i].add(insn.source1) | |||||
} | |||||
last.forEach(loc => { | |||||
if (loc === insn.dest) { | |||||
return | |||||
} | |||||
info[i].add(loc) | |||||
}) | |||||
} | |||||
return info | |||||
} |
@ -0,0 +1,40 @@ | |||||
import { expect } from "chai" | |||||
import { createGraph, neighbors, vertices } from "../../lib/data/graph" | |||||
const g = createGraph((v, e) => { | |||||
v("a", true) | |||||
v("b", true) | |||||
v("c", true) | |||||
v("d", true) | |||||
e("a", "b") | |||||
e("a", "c") | |||||
}) | |||||
const empty = createGraph((v, e) => { }) | |||||
describe("graph", () => { | |||||
describe("neighbors", () => { | |||||
it("returns neighbors of vertex", () => { | |||||
const ns = neighbors(g, "a") | |||||
expect(ns).to.deep.equal(new Set(["b", "c"])) | |||||
}) | |||||
it("returns empty set when vertex has no neighbors", () => { | |||||
const ns = neighbors(g, "d") | |||||
expect(ns).to.deep.equal(new Set()) | |||||
}) | |||||
it("throw on invalid vertex", () => { | |||||
expect(() => neighbors(empty, "x")).to.throw() | |||||
}) | |||||
}) | |||||
describe("vertices", () => { | |||||
it("return all vertices for a graph", () => { | |||||
const vs = vertices(g) | |||||
expect(vs).to.deep.equal(new Set(["a", "b", "c", "d"])) | |||||
}) | |||||
}) | |||||
}) |
@ -0,0 +1,133 @@ | |||||
import { expect } from "chai" | |||||
import { convertAssignStmt } from "../../lib/ir/convert" | |||||
import { Loc } from "../../lib/ir/loc" | |||||
describe("ir", () => { | |||||
describe("convertExpr", () => { | |||||
it("load with immediate", () => { | |||||
const state = { nextID: 0, ssa_stmts: [] } | |||||
const ir = convertAssignStmt(state, { | |||||
type: "stmt", | |||||
stmt_type: "assign", | |||||
args: { | |||||
name: "x", | |||||
expr: 42, | |||||
}, | |||||
}) | |||||
expect(ir).to.deep.equal([ | |||||
{ | |||||
dest: Loc.vari("x"), | |||||
source: 42, | |||||
}, | |||||
]) | |||||
}) | |||||
it("load with variable", () => { | |||||
const state = { nextID: 0, ssa_stmts: [] } | |||||
const ir = convertAssignStmt(state, { | |||||
type: "stmt", | |||||
stmt_type: "assign", | |||||
args: { | |||||
name: "x", | |||||
expr: "y", | |||||
}, | |||||
}) | |||||
expect(ir).to.deep.equal([ | |||||
{ | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
}, | |||||
]) | |||||
}) | |||||
it("unary op", () => { | |||||
const state = { nextID: 0, ssa_stmts: [] } | |||||
const ir = convertAssignStmt(state, { | |||||
type: "stmt", | |||||
stmt_type: "assign", | |||||
args: { | |||||
name: "x", | |||||
expr: { | |||||
type: "unary", | |||||
op: "arith_negate", | |||||
arg: "y" | |||||
}, | |||||
}, | |||||
}) | |||||
expect(ir).to.deep.equal([ | |||||
{ | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
op: "arith_negate", | |||||
}, | |||||
]) | |||||
}) | |||||
it("binary op", () => { | |||||
const state = { nextID: 0, ssa_stmts: [] } | |||||
const ir = convertAssignStmt(state, { | |||||
type: "stmt", | |||||
stmt_type: "assign", | |||||
args: { | |||||
name: "x", | |||||
expr: { | |||||
type: "binary", | |||||
op: "add", | |||||
left: "y", | |||||
right: 42, | |||||
}, | |||||
}, | |||||
}) | |||||
expect(ir).to.deep.equal([ | |||||
{ | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
source1: 42, | |||||
op: "add", | |||||
}, | |||||
]) | |||||
}) | |||||
it("nested binary op", () => { | |||||
const state = { nextID: 0, ssa_stmts: [] } | |||||
const ir = convertAssignStmt(state, { | |||||
type: "stmt", | |||||
stmt_type: "assign", | |||||
args: { | |||||
name: "x", | |||||
expr: { | |||||
type: "binary", | |||||
op: "add", | |||||
left: "y", | |||||
right: { | |||||
type: "binary", | |||||
op: "subtract", | |||||
left: "z", | |||||
right: 42, | |||||
}, | |||||
}, | |||||
}, | |||||
}) | |||||
expect(ir).to.deep.equal([ | |||||
{ | |||||
dest: Loc.temp("t0"), | |||||
source: Loc.vari("z"), | |||||
op: "subtract", | |||||
source1: 42, | |||||
}, | |||||
{ | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
op: "add", | |||||
source1: Loc.temp("t0"), | |||||
}, | |||||
]) | |||||
}) | |||||
}) | |||||
}) |
@ -0,0 +1,66 @@ | |||||
import { expect } from "chai" | |||||
import { Loc } from "../../lib/ir/loc" | |||||
import { prettyPrintSSA } from "../../lib/ir/pretty" | |||||
import type { SSA } from "../../lib/ir/ssa" | |||||
describe("ir", () => { | |||||
describe("prettyPrintSSA", () => { | |||||
it("load with immediate", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: 42, | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = 42") | |||||
}) | |||||
it("load with register", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: Loc.reg("A"), | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = %A") | |||||
}) | |||||
it("load with temporary", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: Loc.temp("t0"), | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = #t0") | |||||
}) | |||||
it("load with variable", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = y") | |||||
}) | |||||
it("unary op", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
op: "arith_negate", | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = arith_negate(y)") | |||||
}) | |||||
it("binary op", () => { | |||||
const ssa: SSA = { | |||||
dest: Loc.vari("x"), | |||||
source: Loc.vari("y"), | |||||
source1: 42, | |||||
op: "add", | |||||
} | |||||
expect(prettyPrintSSA(ssa)).to.equal("x = add(y, 42)") | |||||
}) | |||||
}) | |||||
}) |
@ -0,0 +1,28 @@ | |||||
import { expect } from "chai" | |||||
import { Loc } from "../lib/ir/loc" | |||||
import type { SSA } from "../lib/ir/ssa" | |||||
import { liveness } from "../lib/live" | |||||
describe("liveness", () => { | |||||
it("computes liveness", () => { | |||||
const x = [0, 1, 2].map(i => Loc.vari("x" + i)) | |||||
const y = [0, 1].map(i => Loc.vari("y" + i)) | |||||
const block: Array<SSA> = [ | |||||
{ dest: x[0], source: 1 }, | |||||
{ dest: x[1], source: x[0], op: "add", source1: x[0] }, | |||||
{ dest: x[2], source: x[1], op: "add", source1: x[0] }, | |||||
{ dest: y[0], source: x[0], op: "add", source1: x[1] }, | |||||
{ dest: y[1], source: y[0], op: "add", source1: x[2] }, | |||||
] | |||||
const info = liveness(block) | |||||
expect(info[0]).to.deep.equal(new Set()) | |||||
expect(info[1]).to.deep.equal(new Set([x[0]])) | |||||
expect(info[2]).to.deep.equal(new Set([x[0], x[1]])) | |||||
expect(info[3]).to.deep.equal(new Set([x[0], x[1], x[2]])) | |||||
expect(info[4]).to.deep.equal(new Set([y[0], x[2]])) | |||||
}) | |||||
}) |