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 y; | |||
x <- -y + 3; | |||
x <- -y + j; | |||
z <- w + 7; |
@ -1,2 +1,2 @@ | |||
#!/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 | |||
} | |||
for (const x of xs) { | |||
for (const x of Array.from(xs)) { | |||
if (!ys.has(x)) { | |||
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") | |||
}) | |||
}) |