diff --git a/lib/ir/convert.ts b/lib/ir/convert.ts index 2973461..66bd948 100644 --- a/lib/ir/convert.ts +++ b/lib/ir/convert.ts @@ -1,77 +1,78 @@ import type { AssignStmt, Expr, Stmt } from "../ast" import { R8 } from "../sm83/cpu" import { Loc, LocType } from "./loc" -import type { Operand, SSA, SSAWithBinary } from "./ssa" +import type { Operand, AbsInsn2, AbsInsn3 } from "./insn" -type IRState = { +export type IRState = { nextID: number - ssa_stmts: Array + insns: Array } -export const convertIR = (stmts: Array): Array => { +export const convertIR = (stmts: Array): Array => { const state: IRState = { nextID: 0, - ssa_stmts: [], + insns: [], } stmts.forEach(stmt => { - const ssa_stmts = convertAssignStmt(state, stmt) - state.ssa_stmts.push(...ssa_stmts) + convertAssignStmt(state, stmt) }) - return state.ssa_stmts + return state.insns } -export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array => { - return convertExpr(state, Loc.vari(stmt.args.name), stmt.args.expr) +export const convertAssignStmt = (state: IRState, stmt: AssignStmt): void => { + convertExpr(state, Loc.vari(stmt.args.name), stmt.args.expr) } -export const convertExpr = (state: IRState, dest: Loc, expr: Expr): Array => { +export const convertExpr = (state: IRState, dest: Loc, expr: Expr): void => { const threeAddress = convertExpr3(state, dest, expr) - const output: Array = [] threeAddress.forEach(ssa => { - // skip copies - if (!("op" in ssa)) { - output.push(ssa) - return - } else if ("source1" in ssa) { - let source1 = ssa.source1 - if (typeof source1 !== 'number' && source1.type === LocType.VARIABLE) { - source1 = Loc.temp(`t${state.nextID++}`) - output.push( - { dest: Loc.reg(R8.A), source: ssa.source1 }, - { dest: source1, source: Loc.reg(R8.A) }, + switch (ssa.type) { + case 'copy': + state.insns.push(ssa) + break + + case 'unary': + state.insns.push( + { type: "copy", dest: Loc.reg(R8.A), source: ssa.source }, + { type: "unary", dest: Loc.reg(R8.A), source: Loc.reg(R8.A), op: ssa.op }, + { type: "copy", dest: ssa.dest, source: Loc.reg(R8.A) }, ) - } - - output.push( - { dest: Loc.reg(R8.A), source: ssa.source }, - { dest: Loc.reg(R8.A), op: ssa.op, source: source1 }, - { dest: ssa.dest, source: Loc.reg(R8.A) }, - ) - } else { - output.push( - { dest: Loc.reg(R8.A), source: ssa.source }, - { dest: Loc.reg(R8.A), source: Loc.reg(R8.A), op: ssa.op }, - { dest: ssa.dest, source: Loc.reg(R8.A) }, - ) + break + + case 'binary': + let source1 = ssa.source1 + if (typeof source1 !== 'number' && source1.type === LocType.VARIABLE) { + source1 = Loc.temp(`t${state.nextID++}`) + state.insns.push( + { type: "copy", dest: Loc.reg(R8.A), source: ssa.source1 }, + { type: "copy", dest: source1, source: Loc.reg(R8.A) }, + ) + } + + state.insns.push( + { type: "copy", dest: Loc.reg(R8.A), source: ssa.source }, + { type: "unary", dest: Loc.reg(R8.A), op: ssa.op, source: source1 }, + { type: "copy", dest: ssa.dest, source: Loc.reg(R8.A) }, + ) + break } }) - - return output } -export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array => { - let expr_stmts: Array = [] +export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array => { + let expr_stmts: Array = [] if (typeof expr === "number") { - expr_stmts = [{ dest, source: expr }] + expr_stmts = [{ type: "copy", dest, source: expr }] } else if (typeof expr === "string") { - expr_stmts = [{ dest, source: Loc.vari(expr) }] + expr_stmts = [{ type: "copy", dest, source: Loc.vari(expr) }] } else if (expr.type === "unary") { const [source, stmts] = getSource(state, expr.arg) stmts.push({ + type: "unary", dest, op: expr.op, source, @@ -84,6 +85,7 @@ export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array] => { +const getSource = (state: IRState, expr: Expr): [Operand, Array] => { if (typeof expr === "number") { return [expr, []] } else if (typeof expr === "string") { diff --git a/lib/ir/insn.ts b/lib/ir/insn.ts new file mode 100644 index 0000000..981a918 --- /dev/null +++ b/lib/ir/insn.ts @@ -0,0 +1,39 @@ +import type { BinaryOp, UnaryOp } from "../ast" +import type { Loc } from "./loc" + +export type Operand = Loc | number + +export type AbsInsnCopy = { + type: "copy", + dest: Loc, + source: Operand, +} + +export type AbsInsn2Unary = { + type: "unary", + dest: Loc, + // NOTE: We convert binary -> unary when going SSA3 -> SSA2 + op: UnaryOp | BinaryOp, + source: Operand, +} + +// Abstract instruction in two-address form +export type AbsInsn2 = AbsInsnCopy | AbsInsn2Unary + +export type AbsInsn3Unary = { + type: "unary", + dest: Loc, + op: UnaryOp, + source: Operand, +} + +export type AbsInsnBinary = { + type: "binary", + dest: Loc, + source: Operand, + op: BinaryOp, + source1: Operand +} + +// Abstract instruction in three-address form +export type AbsInsn3 = AbsInsnCopy | AbsInsn3Unary | AbsInsnBinary diff --git a/lib/ir/pretty.ts b/lib/ir/pretty.ts index 8e41742..f9ab860 100644 --- a/lib/ir/pretty.ts +++ b/lib/ir/pretty.ts @@ -1,11 +1,11 @@ import type { Loc } from "./loc" -import type { Operand, SSA, SSAWithBinary } from "./ssa" +import type { AbsInsn2, AbsInsn3, Operand } from "./insn" -export const prettyPrintBlock = (block: Array): string => { - return block.map(prettyPrintSSA).join("\n") +export const prettyPrintBlock = (block: Array): string => { + return block.map(prettyPrintInsn).join("\n") } -export const prettyPrintSSA = (ssa: SSAWithBinary): string => { +export const prettyPrintInsn = (ssa: AbsInsn2 | AbsInsn3): string => { let output = "" if ("source1" in ssa) { diff --git a/lib/ir/ssa.d.ts b/lib/ir/ssa.d.ts deleted file mode 100644 index c17441a..0000000 --- a/lib/ir/ssa.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { BinaryOp, UnaryOp } from "../ast" -import type { Loc } from "./loc" - -export type Operand = Loc | number - -type ASSA = { dest: Loc, source: Operand } & Data - -// dest = source -export type SSACopy = ASSA<{}> - -// dest = op(source) -export type SSAUnary = ASSA<{ op: UnaryOp | BinaryOp }> - -// dest = op(source, source1) -export type SSABinary = ASSA<{ op: BinaryOp, source1: Operand }> - -export type SSA = SSACopy | SSAUnary - -export type SSAWithBinary = SSA | SSABinary diff --git a/lib/regalloc.ts b/lib/regalloc.ts index 3679e26..c00ef84 100644 --- a/lib/regalloc.ts +++ b/lib/regalloc.ts @@ -1,11 +1,11 @@ import { colorGreedy, createGraph, Graph, maxCardinalitySearch } from "./data/graph" import { R8, StackOffset } from "./sm83/cpu" import type { Loc } from "./ir/loc" -import type { SSA } from "./ir/ssa" +import type { AbsInsn2 } from "./ir/insn" export type LivenessInfo = Array> -export const liveness = (block: Array): LivenessInfo => { +export const liveness = (block: Array): LivenessInfo => { const info: LivenessInfo = [] info[block.length] = new Set() @@ -31,7 +31,7 @@ export const liveness = (block: Array): LivenessInfo => { return info } -export const locations = (block: Array): Set => { +export const locations = (block: Array): Set => { const ls: Set = new Set() block.forEach(ssa => { @@ -45,17 +45,17 @@ export const locations = (block: Array): Set => { return ls } -export const interference = (block: Array, live: LivenessInfo): Graph => +export const interference = (block: Array, live: LivenessInfo): Graph => createGraph((v, e) => { const locs = locations(block) locs.forEach(loc => { v(loc.toString(), loc) }) - block.forEach((ssa, i) => + block.forEach((insn, i) => live[i + 1].forEach(u => { - if (ssa.dest.toString() !== u.toString()) { - e(ssa.dest.toString(), u.toString()) + if (insn.dest.toString() !== u.toString()) { + e(insn.dest.toString(), u.toString()) } }) ) @@ -65,7 +65,7 @@ export type RegAlloc = { [s: string]: string | StackOffset, } -export const allocateRegisters = (block: Array, registers: Array): RegAlloc => { +export const allocateRegisters = (block: Array, registers: Array): RegAlloc => { const info = liveness(block) const graph = interference(block, info) const ordering = maxCardinalitySearch(graph) diff --git a/lib/sm83/codegen.ts b/lib/sm83/codegen.ts index 55e7c49..f121239 100644 --- a/lib/sm83/codegen.ts +++ b/lib/sm83/codegen.ts @@ -1,10 +1,9 @@ import { R8 } from "../sm83/cpu" import { Loc, LocType } from "../ir/loc" -import type { Operand, SSA, SSABinary, SSACopy, SSAUnary } from "../ir/ssa" +import type { Operand, AbsInsn2, AbsInsnCopy, AbsInsn2Unary } from "../ir/insn" import type { RegAlloc } from "../regalloc" -import { BinaryOp, UnaryOp } from "../ast" -export const generateBlock = (alloc: RegAlloc, block: Array): Array => { +export const generateBlock = (alloc: RegAlloc, block: Array): Array => { const output: Array = [] block.forEach(ssa => { @@ -14,19 +13,23 @@ export const generateBlock = (alloc: RegAlloc, block: Array): Array return output } -export const generateSSA = (alloc: RegAlloc, ssa: SSA): Array => { +export const generateSSA = (alloc: RegAlloc, ssa: AbsInsn2): Array => { let output: Array = [] - if ("op" in ssa) { - output = generateSSA_Unary(alloc, ssa) - } else { - output = generateSSA_Copy(alloc, ssa) + switch (ssa.type) { + case 'copy': + output = generateSSA_Copy(alloc, ssa) + break + + case 'unary': + output = generateSSA_Unary(alloc, ssa) + break } return output } -export const generateSSA_Copy = (alloc: RegAlloc, ssa: SSACopy): Array => { +export const generateSSA_Copy = (alloc: RegAlloc, ssa: AbsInsnCopy): Array => { const output: Array = [] const source = typeof ssa.source === 'number' @@ -76,7 +79,7 @@ const getAlloc = (alloc: RegAlloc, loc: Loc): Loc => { return Loc.reg(destAlloc) } -export const generateSSA_Unary = (alloc: RegAlloc, ssa: SSAUnary): Array => { +export const generateSSA_Unary = (alloc: RegAlloc, ssa: AbsInsn2Unary): Array => { if (!isA(ssa.dest)) { throw new Error("Unexpected form for unary operation") } @@ -100,26 +103,5 @@ export const generateSSA_Unary = (alloc: RegAlloc, ssa: SSAUnary): Array } } -/* -export const generateSSA_Binary = (alloc: RegAlloc, ssa: SSABinary): Array => { - if (!isA(ssa.dest) || !isA(ssa.source)) { - throw new Error("Unexpected form for binary operation") - } - - const output: Array = [] - - - switch (ssa.op) { - case "add": - output.push(`ADD ${source}`) - break - - default: - throw new Error(`unsupported binary op \`${ssa.op}'`) - } - - return output -} */ - const isA = (loc: Operand): boolean => typeof loc === "object" && loc.type === LocType.REGISTER && loc.name === R8.A diff --git a/test/ir/convert.spec.ts b/test/ir/convert.spec.ts deleted file mode 100644 index e024e4c..0000000 --- a/test/ir/convert.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -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"), - }, - ]) - }) - }) -}) diff --git a/test/ir/pretty.spec.ts b/test/ir/pretty.spec.ts deleted file mode 100644 index 1c674df..0000000 --- a/test/ir/pretty.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { expect } from "chai" - -import { Loc } from "../../lib/ir/loc" -import { prettyPrintSSA } from "../../lib/ir/pretty" -import type { SSA, SSAWithBinary } 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: SSAWithBinary = { - dest: Loc.vari("x"), - source: Loc.vari("y"), - source1: 42, - op: "add", - } - - expect(prettyPrintSSA(ssa)).to.equal("x = add(y, 42)") - }) - }) -}) diff --git a/test/regalloc.spec.ts b/test/regalloc.spec.ts index fdad006..0a747f7 100644 --- a/test/regalloc.spec.ts +++ b/test/regalloc.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai" import { edgeConnects } from "../lib/data/graph" import { Loc } from "../lib/ir/loc" -import type { SSA } from "../lib/ir/ssa" +import type { AbsInsn2 } from "../lib/ir/insn" import { allocateRegisters, interference, liveness } from "../lib/regalloc" import { R8 } from "../lib/sm83/cpu" @@ -11,12 +11,12 @@ describe("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 = [ - { dest: x[0], source: 1 }, - { dest: x[1], source: x[0], op: "add" }, - { dest: x[2], source: x[1], op: "add" }, - { dest: y[0], source: x[0], op: "add" }, - { dest: y[1], source: y[0], op: "add" }, + const block: Array = [ + { type: "copy", dest: x[0], source: 1 }, + { type: "unary", dest: x[1], source: x[0], op: "add" }, + { type: "unary", dest: x[2], source: x[1], op: "add" }, + { type: "unary", dest: y[0], source: x[0], op: "add" }, + { type: "unary", dest: y[1], source: y[0], op: "add" }, ] const info = liveness(block) @@ -31,10 +31,10 @@ describe("liveness", () => { describe("interference", () => { it("computes interference", () => { - const block: Array = [ - { dest: Loc.vari("a"), source: 7 }, - { dest: Loc.vari("b"), source: 3 }, - { dest: Loc.vari("x"), source: Loc.vari("a") } + const block: Array = [ + { type: "copy", dest: Loc.vari("a"), source: 7 }, + { type: "copy", dest: Loc.vari("b"), source: 3 }, + { type: "copy", dest: Loc.vari("x"), source: Loc.vari("a") }, ] const info = liveness(block) @@ -46,10 +46,10 @@ describe("interference", () => { describe("allocateRegisters", () => { it("allocates registers", () => { - const block: Array = [ - { dest: Loc.vari("a"), source: 7 }, - { dest: Loc.vari("b"), source: 3 }, - { dest: Loc.vari("x"), source: Loc.vari("a") } + const block: Array = [ + { type: "copy", dest: Loc.vari("a"), source: 7 }, + { type: "copy", dest: Loc.vari("b"), source: 3 }, + { type: "copy", dest: Loc.vari("x"), source: Loc.vari("a") } ] const alloc = allocateRegisters(block, Object.values(R8))