Author | SHA1 | Message | Date |
---|---|---|---|
Forest Belton | 47c04cc383 | Generate code with results from register allocation | 3 years ago |
Forest Belton | 1a944de1a1 | Finish basic register allocation | 3 years ago |
Forest Belton | 17c023383b | Build interference graph | 3 years ago |
@ -1,4 +1,8 @@ | |||||
[[ extern ]] u8 z; | |||||
[[ extern ]] u8 w; | |||||
u8 x; | u8 x; | ||||
u8 y; | u8 y; | ||||
x <- -y + 3; | |||||
x <- -y + j; | |||||
z <- w + 7; |
@ -1,2 +1,2 @@ | |||||
#!/usr/bin/env node | #!/usr/bin/env node | ||||
require("./build/main") | |||||
require("./build/lib/main") |
@ -15,7 +15,7 @@ export const setEquals = (xs: Set, ys: Set): boolean => { | |||||
return false | return false | ||||
} | } | ||||
for (const x of xs) { | |||||
for (const x of Array.from(xs)) { | |||||
if (!ys.has(x)) { | if (!ys.has(x)) { | ||||
return false | return false | ||||
} | } | ||||
@ -1,33 +0,0 @@ | |||||
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,107 @@ | |||||
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" | |||||
export type LivenessInfo = Array<Set<Loc>> | |||||
export const liveness = (block: Array<SSA>): LivenessInfo => { | |||||
const info: LivenessInfo = [] | |||||
info[block.length] = new Set() | |||||
for (let i = block.length - 1; i >= 0; --i) { | |||||
const insn = block[i] | |||||
const last = info[i + 1] | |||||
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.toString() === insn.dest.toString()) { | |||||
return | |||||
} | |||||
info[i].add(loc) | |||||
}) | |||||
} | |||||
return info | |||||
} | |||||
export const locations = (block: Array<SSA>): Set<Loc> => { | |||||
const ls: Set<Loc> = new Set() | |||||
block.forEach(ssa => { | |||||
ls.add(ssa.dest) | |||||
if (typeof ssa.source !== "number") { | |||||
ls.add(ssa.source) | |||||
} | |||||
if ("source1" in ssa && typeof ssa.source1 !== "number") { | |||||
ls.add(ssa.source1) | |||||
} | |||||
}) | |||||
return ls | |||||
} | |||||
export const interference = (block: Array<SSA>, live: LivenessInfo): Graph<Loc> => | |||||
createGraph((v, e) => { | |||||
const locs = locations(block) | |||||
locs.forEach(loc => { | |||||
v(loc.toString(), loc) | |||||
}) | |||||
block.forEach((ssa, i) => | |||||
live[i + 1].forEach(u => { | |||||
if (ssa.dest.toString() !== u.toString()) { | |||||
e(ssa.dest.toString(), u.toString()) | |||||
} | |||||
}) | |||||
) | |||||
}) | |||||
export type RegAlloc = { | |||||
[s: string]: string | StackOffset, | |||||
} | |||||
export const allocateRegisters = (block: Array<SSA>, registers: Array<string>): RegAlloc => { | |||||
const info = liveness(block) | |||||
const graph = interference(block, info) | |||||
const ordering = maxCardinalitySearch(graph) | |||||
const coloring = colorGreedy(graph, ordering) | |||||
const allocation: RegAlloc = {} | |||||
const availableRegisters = new Set(registers) | |||||
const colorMap: { [c: number]: string | StackOffset } = {} | |||||
let nextStackOffset = 0 | |||||
Object.entries(coloring.colors).forEach(([vertex, color]) => { | |||||
if (typeof colorMap[color] !== 'undefined') { | |||||
allocation[vertex] = colorMap[color] | |||||
return | |||||
} | |||||
let value = null | |||||
if (availableRegisters.size == 0) { | |||||
value = { offset: nextStackOffset++ } | |||||
} else { | |||||
const result = availableRegisters.values().next() | |||||
value = result.value | |||||
availableRegisters.delete(value) | |||||
} | |||||
allocation[vertex] = value | |||||
colorMap[color] = value | |||||
}) | |||||
return allocation | |||||
} |
@ -0,0 +1,134 @@ | |||||
import { R8 } from "../sm83/cpu" | |||||
import { Loc, LocType } from "../ir/loc" | |||||
import type { Operand, SSA, SSABinary, SSACopy, SSAUnary } from "../ir/ssa" | |||||
import type { RegAlloc } from "../regalloc" | |||||
import { UnaryOp } from "../ast" | |||||
export const generateBlock = (alloc: RegAlloc, block: Array<SSA>): Array<string> => { | |||||
const output: Array<string> = [] | |||||
block.forEach(ssa => { | |||||
output.push(...generateSSA(alloc, ssa)) | |||||
}) | |||||
return output | |||||
} | |||||
export const generateSSA = (alloc: RegAlloc, ssa: SSA): Array<string> => { | |||||
let output: Array<string> = [] | |||||
if ("source1" in ssa) { | |||||
output = generateSSA_Binary(alloc, ssa) | |||||
} else if ("op" in ssa) { | |||||
output = generateSSA_Unary(alloc, ssa) | |||||
} else { | |||||
output = generateSSA_Copy(alloc, ssa) | |||||
} | |||||
return output | |||||
} | |||||
export const generateSSA_Copy = (alloc: RegAlloc, ssa: SSACopy): Array<string> => { | |||||
const output: Array<string> = [] | |||||
const source = typeof ssa.source === 'number' | |||||
? ssa.source.toString() | |||||
: getAlloc(alloc, ssa.source).name | |||||
switch (ssa.dest.type) { | |||||
case LocType.TEMPORARY: | |||||
const dest = getAlloc(alloc, ssa.dest) | |||||
// TODO: Remove later with peephole optimizer | |||||
if (dest.name !== source) { | |||||
output.push(`LD ${dest.name}, ${source}`) | |||||
} | |||||
break | |||||
case LocType.REGISTER: | |||||
if (isA(ssa.dest) && typeof ssa.source !== 'number' && ssa.source.type === LocType.VARIABLE) { | |||||
output.push(`LD A, (${ssa.source.name})`) | |||||
// TODO: Remove check with peephole optimizer | |||||
} else if (source !== ssa.dest.name) { | |||||
output.push(`LD ${ssa.dest.name}, ${source}`) | |||||
} | |||||
break | |||||
case LocType.VARIABLE: | |||||
if (source !== R8.A) { | |||||
console.log(source, ssa) | |||||
throw new Error("unsupported") | |||||
} | |||||
output.push(`LD (${ssa.dest.name}), A`) | |||||
break | |||||
default: | |||||
throw new Error(`unsupported destination type \`${ssa.dest.type}'`) | |||||
} | |||||
return output | |||||
} | |||||
const getAlloc = (alloc: RegAlloc, loc: Loc): Loc => { | |||||
const destAlloc = alloc[loc.toString()] | |||||
if (typeof destAlloc === "object") { | |||||
throw new Error("stack variables not yet supported") | |||||
} | |||||
return Loc.reg(destAlloc) | |||||
} | |||||
export const generateSSA_Unary = (alloc: RegAlloc, ssa: SSAUnary): Array<string> => { | |||||
if (!isA(ssa.dest)) { | |||||
throw new Error("Unexpected form for unary operation") | |||||
} | |||||
const output: Array<string> = [] | |||||
if (typeof ssa.source === "number") { | |||||
output.push(`LD A, ${ssa.source}`) | |||||
} else if (ssa.source.type === LocType.REGISTER) { | |||||
if (!isA(ssa.dest)) { | |||||
output.push(`LD A, ${ssa.source.name}`) | |||||
} | |||||
} else { | |||||
// TODO: ?? | |||||
output.push(`LD A, ${alloc[ssa.source.toString()]}`) | |||||
} | |||||
const ops = unaryOps[ssa.op] | |||||
if (!ops) { | |||||
throw new Error(`unsupported unary op \`${ssa.op}'`) | |||||
} | |||||
return output.concat(ops) | |||||
} | |||||
const unaryOps = { | |||||
"arith_negate": ["CPL", "INC A"], | |||||
"bit_negate": ["CPL"] | |||||
} | |||||
export const generateSSA_Binary = (alloc: RegAlloc, ssa: SSABinary): Array<string> => { | |||||
if (!isA(ssa.dest) || !isA(ssa.source)) { | |||||
throw new Error("Unexpected form for binary operation") | |||||
} | |||||
const output: Array<string> = [] | |||||
const source = typeof ssa.source1 === 'number' | |||||
? ssa.source1 | |||||
: getAlloc(alloc, ssa.source1).name | |||||
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 |
@ -0,0 +1,12 @@ | |||||
export enum R8 { | |||||
A = "A", | |||||
B = "B", | |||||
C = "C", | |||||
D = "D", | |||||
E = "E", | |||||
H = "H", | |||||
L = "L", | |||||
HL = "(HL)", | |||||
} | |||||
export type StackOffset = { offset: number } |
@ -1,28 +0,0 @@ | |||||
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]])) | |||||
}) | |||||
}) |
@ -0,0 +1,61 @@ | |||||
import { expect } from "chai" | |||||
import { edgeConnects } from "../lib/data/graph" | |||||
import { Loc } from "../lib/ir/loc" | |||||
import type { SSA } from "../lib/ir/ssa" | |||||
import { allocateRegisters, interference, liveness } from "../lib/regalloc" | |||||
import { R8 } from "../lib/sm83/cpu" | |||||
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]])) | |||||
}) | |||||
}) | |||||
describe("interference", () => { | |||||
it("computes interference", () => { | |||||
const block: Array<SSA> = [ | |||||
{ dest: Loc.vari("a"), source: 7 }, | |||||
{ dest: Loc.vari("b"), source: 3 }, | |||||
{ dest: Loc.vari("x"), source: Loc.vari("a") } | |||||
] | |||||
const info = liveness(block) | |||||
const g = interference(block, info) | |||||
expect(edgeConnects(g, "a", "b")).to.be.true | |||||
}) | |||||
}) | |||||
describe("allocateRegisters", () => { | |||||
it("allocates registers", () => { | |||||
const block: Array<SSA> = [ | |||||
{ dest: Loc.vari("a"), source: 7 }, | |||||
{ dest: Loc.vari("b"), source: 3 }, | |||||
{ dest: Loc.vari("x"), source: Loc.vari("a") } | |||||
] | |||||
const alloc = allocateRegisters(block, Object.values(R8)) | |||||
expect(alloc.a).to.equal("A") | |||||
expect(alloc.b).to.equal("B") | |||||
expect(alloc.x).to.equal("A") | |||||
}) | |||||
}) |