Browse Source

Generate code with results from register allocation

master
Forest Belton 3 years ago
parent
commit
47c04cc383
8 changed files with 239 additions and 13 deletions
  1. +5
    -1
      example.gby
  2. +1
    -1
      gbuoy.js
  3. +51
    -1
      lib/compile.ts
  4. +39
    -2
      lib/ir/convert.ts
  5. +5
    -5
      lib/regalloc.ts
  6. +134
    -0
      lib/sm83/codegen.ts
  7. +1
    -1
      package.json
  8. +3
    -2
      test/regalloc.spec.ts

+ 5
- 1
example.gby View File

@ -1,4 +1,8 @@
[[ extern ]] u8 z;
[[ extern ]] u8 w;
u8 x;
u8 y;
x <- -y + 3;
x <- -y + j;
z <- w + 7;

+ 1
- 1
gbuoy.js View File

@ -1,2 +1,2 @@
#!/usr/bin/env node
require("./build/main")
require("./build/lib/main")

+ 51
- 1
lib/compile.ts View File

@ -1,6 +1,9 @@
import { convertIR } from "./ir/convert"
import { prettyPrintBlock } from "./ir/pretty"
import { parse } from "./parser"
import { allocateRegisters } from "./regalloc"
import { generateBlock } from "./sm83/codegen"
import { R8 } from "./sm83/cpu"
import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast"
@ -38,8 +41,23 @@ export const compile = (fileName: string, source: string): string => {
console.log(prettyPrintBlock(ir))
// 5. Generate code
const alloc = allocateRegisters(ir, Object.values(R8))
const insns = generateBlock(alloc, ir)
let output = ''
if (asmDecls.length > 0) {
output += `SECTION "${fileName} Data", WRAM0\n\n`
output += asmDecls.join("\n")
output += "\n\n"
}
if (insns.length > 0) {
output += `SECTION "${fileName} Code", ROM0\n\n`
output += insns.join("\n")
}
return ''
console.log("=== ASM ===")
return output
}
export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce(processDecl, {})
@ -47,6 +65,11 @@ export const processDecls = (decls: Array): SymbolMap => decls.reduce(proc
export const processDecl = (symbols: SymbolMap, decl: Decl): SymbolMap => {
const symbol = processVarDecl(symbols, decl)
// Don't declare extern symbols
if (hasAttr(symbol, { name: 'extern', args: [] })) {
return symbols
}
return {
...symbols,
[symbol.name]: symbol,
@ -64,3 +87,30 @@ export const processVarDecl = (symbols: SymbolMap, decl: VarDecl): SymbolDefn =>
type: decl.args.type,
}
}
export const hasAttr = (symbol: SymbolDefn, attr: Attr) => {
let has = false
for (let i = 0; i < symbol.attrs.length; ++i) {
has = attrEquals(symbol.attrs[i], attr)
if (has) {
break
}
}
return has
}
const attrEquals = (attr1: Attr, attr2: Attr): boolean => {
if (attr1.name !== attr2.name || attr1.args.length !== attr2.args.length) {
return false
}
for (let i = 0; i < attr1.args.length; ++i) {
if (attr1.args[i] !== attr2.args[i]) {
return false
}
}
return true
}

+ 39
- 2
lib/ir/convert.ts View File

@ -1,5 +1,6 @@
import type { AssignStmt, Expr, Stmt } from "../ast"
import { Loc } from "./loc"
import { R8 } from "../sm83/cpu"
import { Loc, LocType } from "./loc"
import type { Operand, SSA } from "./ssa"
type IRState = {
@ -26,6 +27,42 @@ export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array
}
export const convertExpr = (state: IRState, dest: Loc, expr: Expr): Array<SSA> => {
const threeAddress = convertExpr3(state, dest, expr)
const output: Array<SSA> = []
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) },
)
}
output.push(
{ dest: Loc.reg(R8.A), source: ssa.source },
{ dest: Loc.reg(R8.A), source: Loc.reg(R8.A), op: ssa.op, 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) },
)
}
})
return output
}
export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array<SSA> => {
let expr_stmts: Array<SSA> = []
if (typeof expr === "number") {
@ -67,6 +104,6 @@ const getSource = (state: IRState, expr: Expr): [Operand, Array] => {
}
const source = Loc.temp(`t${state.nextID++}`)
const stmts = convertExpr(state, source, expr)
const stmts = convertExpr3(state, source, expr)
return [source, stmts]
}

+ 5
- 5
lib/regalloc.ts View File

@ -69,19 +69,19 @@ export const interference = (block: Array, live: LivenessInfo): Graph
)
})
type RegAlloc = {
[s: string]: R8 | StackOffset,
export type RegAlloc = {
[s: string]: string | StackOffset,
}
export const allocateRegisters = (block: Array<SSA>): RegAlloc => {
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(Object.values(R8))
const colorMap: { [c: number]: R8 | StackOffset } = {}
const availableRegisters = new Set(registers)
const colorMap: { [c: number]: string | StackOffset } = {}
let nextStackOffset = 0
Object.entries(coloring.colors).forEach(([vertex, color]) => {

+ 134
- 0
lib/sm83/codegen.ts View File

@ -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

+ 1
- 1
package.json View File

@ -19,7 +19,7 @@
"lib": "lib"
},
"scripts": {
"build": "mkdir -p build && peggy -o build/parser.js lib/parser.pegjs && tsc",
"build": "tsc && peggy -o build/lib/parser.js lib/parser.pegjs",
"test": "ts-mocha"
},
"author": "Forest Belton <forest@homolo.gy>",

+ 3
- 2
test/regalloc.spec.ts View File

@ -1,9 +1,10 @@
import { expect } from "chai"
import { edgeConnects } from "../lib/data/graph"
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", () => {
@ -51,7 +52,7 @@ describe("allocateRegisters", () => {
{ dest: Loc.vari("x"), source: Loc.vari("a") }
]
const alloc = allocateRegisters(block)
const alloc = allocateRegisters(block, Object.values(R8))
expect(alloc.a).to.equal("A")
expect(alloc.b).to.equal("B")

Loading…
Cancel
Save