diff --git a/example.gby b/example.gby index 6b14518..7068d90 100644 --- a/example.gby +++ b/example.gby @@ -1,4 +1,8 @@ +[[ extern ]] u8 z; +[[ extern ]] u8 w; + u8 x; u8 y; -x <- -y + 3; +x <- -y + j; +z <- w + 7; diff --git a/gbuoy.js b/gbuoy.js index 2e3b77b..942fc57 100755 --- a/gbuoy.js +++ b/gbuoy.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -require("./build/main") +require("./build/lib/main") diff --git a/lib/compile.ts b/lib/compile.ts index d96bb6d..88888c5 100644 --- a/lib/compile.ts +++ b/lib/compile.ts @@ -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): 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 +} diff --git a/lib/ir/convert.ts b/lib/ir/convert.ts index f94a7ea..7f0d7bd 100644 --- a/lib/ir/convert.ts +++ b/lib/ir/convert.ts @@ -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 => { + 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) }, + ) + } + + 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 => { let expr_stmts: Array = [] 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] } diff --git a/lib/regalloc.ts b/lib/regalloc.ts index 91fe19e..5c047d1 100644 --- a/lib/regalloc.ts +++ b/lib/regalloc.ts @@ -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): RegAlloc => { +export const allocateRegisters = (block: Array, registers: Array): 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]) => { diff --git a/lib/sm83/codegen.ts b/lib/sm83/codegen.ts new file mode 100644 index 0000000..ecbe43a --- /dev/null +++ b/lib/sm83/codegen.ts @@ -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): Array => { + const output: Array = [] + + block.forEach(ssa => { + output.push(...generateSSA(alloc, ssa)) + }) + + return output +} + +export const generateSSA = (alloc: RegAlloc, ssa: SSA): Array => { + let output: Array = [] + + 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 => { + const output: Array = [] + + 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 => { + if (!isA(ssa.dest)) { + throw new Error("Unexpected form for unary operation") + } + + const output: Array = [] + 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 => { + if (!isA(ssa.dest) || !isA(ssa.source)) { + throw new Error("Unexpected form for binary operation") + } + + const output: Array = [] + 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 diff --git a/package.json b/package.json index 5a4f8c6..31ca2e3 100644 --- a/package.json +++ b/package.json @@ -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 ", diff --git a/test/regalloc.spec.ts b/test/regalloc.spec.ts index cbaa17d..070bde3 100644 --- a/test/regalloc.spec.ts +++ b/test/regalloc.spec.ts @@ -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")