3 Commits

7 changed files with 178 additions and 38 deletions
Split View
  1. +2
    -0
      example.gby
  2. +112
    -24
      lib/asm.ts
  3. +9
    -1
      lib/ast.d.ts
  4. +17
    -3
      lib/compile.ts
  5. +21
    -6
      lib/ir.ts
  6. +6
    -2
      lib/main.ts
  7. +11
    -2
      lib/parser.pegjs

+ 2
- 0
example.gby View File

@ -1,3 +1,5 @@
u8 x;
u8 y;
x <- 1;
y <- -x;

+ 112
- 24
lib/asm.ts View File

@ -1,4 +1,4 @@
import type { SSA } from "./ir"
import type { SSA, SSABinary, SSACopy, SSAUnary } from "./ir"
const reg8 = ["B", "C", "D", "E"] as const
@ -62,42 +62,130 @@ export const convertASM = (ir: Array): Array => {
const state = new ASMState()
ir.forEach(ssa => convertASM_SSA(state, ssa))
for (let sym of Object.keys(state.syms)) {
if (sym.startsWith("temp")) {
continue
}
state.insns.push(
`LD A, ${state.syms[sym]}`,
`LD (${sym}), A`
)
}
for (let sym of Object.keys(state.stack_offsets)) {
if (sym.startsWith("temp")) {
continue
}
state.insns.push(
`LD HL, SP + (${state.cur_offset - state.stack_offsets[sym]})`,
`LD A, (HL)`,
`LD (${sym}), A`
)
}
if (state.cur_offset !== 0) {
state.insns.push(`ADD SP, ${state.cur_offset}`)
}
return state.insns
}
// TODO: Track liveness
const convertASM_SSA = (state: ASMState, ssa: SSA): void => {
const dest = state.alloc_sym_loc(ssa.dest)
if ("source1" in ssa) { // Binary
convertASM_SSA_Binary(state, ssa)
} else if ("op" in ssa) { // Unary
convertASM_SSA_Unary(state, ssa)
} else { // Copy
let source = ""
if (typeof ssa.source == "number") {
source = ssa.source.toString()
convertASM_SSA_Copy(state, ssa)
}
}
export const convertASM_SSA_Binary = (state: ASMState, ssa: SSABinary): void => {
const dest = state.alloc_sym_loc(ssa.dest)
}
export const convertASM_SSA_Unary = (state: ASMState, ssa: SSAUnary): void => {
const dest = state.alloc_sym_loc(ssa.dest)
if (typeof ssa.source == "number") {
state.insns.push(`LD A, ${ssa.source}`)
} else {
const loc = state.get_sym_loc(ssa.source)
if (loc === null) {
state.insns.push(`LD A, (${ssa.source})`)
} else if (typeof loc === "string") {
state.insns.push(`LD A, ${loc}`)
} else {
const loc = state.get_sym_loc(ssa.source)
if (loc === null) {
state.insns.push(`LD A, (${ssa.source})`)
source = "A"
} else if (typeof loc === "string") {
source = loc
} else {
state.insns.push(
`LD HL, SP + (${loc})`,
`LD A, (HL)`
)
source = "A"
}
state.insns.push(
`LD HL, SP + (${loc})`,
`LD A, (HL)`
)
}
}
switch (ssa.op) {
case 'arith_negate':
state.insns.push(
`CPL`,
`INC A`,
)
break
case 'bit_negate':
state.insns.push(
`CPL`
)
break
if (typeof dest === "string") {
state.insns.push(`LD ${dest}, ${source}`)
default:
throw new Error(`unsupported unary op \`'${ssa.op}`)
}
if (typeof dest === "string") {
state.insns.push(`LD ${dest}, A`)
} else {
state.insns.push(
`LD HL, SP + (${dest})`,
`LD (HL), A`
)
}
}
export const convertASM_SSA_Copy = (state: ASMState, ssa: SSACopy): void => {
const dest = state.alloc_sym_loc(ssa.dest)
let source = ""
if (typeof ssa.source == "number") {
source = ssa.source.toString()
} else {
const loc = state.get_sym_loc(ssa.source)
if (loc === null) {
state.insns.push(`LD A, (${ssa.source})`)
source = "A"
} else if (typeof loc === "string") {
source = loc
} else {
state.insns.push(
`LD HL, SP + (${dest})`,
`LD (HL), ${source}`
`LD HL, SP + (${loc})`,
`LD A, (HL)`
)
source = "A"
}
}
if (typeof dest === "string") {
state.insns.push(`LD ${dest}, ${source}`)
} else {
state.insns.push(
`LD HL, SP + (${dest})`,
`LD (HL), ${source}`
)
}
}

+ 9
- 1
lib/ast.d.ts View File

@ -30,7 +30,15 @@ type AStmt = {
}
// Expressions
export type Expr = number
export type Expr = UnaryExpr | BasicExpr
type UnaryExpr = {
type: "unary",
op: string,
arg: Expr,
}
type BasicExpr = number | string
// Attributes
export type Attr = {

+ 17
- 3
lib/compile.ts View File

@ -15,7 +15,7 @@ type SymbolMap = {
}
// TODO: Support more than one TU
export const compile = (source: string): string => {
export const compile = (fileName: string, source: string): string => {
// 1. Parse
const ast = parse(source)
@ -26,6 +26,9 @@ export const compile = (source: string): string => {
// 3. Create top-level symbol map
const symbols = processDecls(decls)
// TODO: Support declaring types other than U8/S8
const asmDecls = Object.values(symbols).map(symbol => `${symbol.name}:: DB`)
// TODO: Some form of type-checking
// 4. Generate IR
@ -36,10 +39,21 @@ export const compile = (source: string): string => {
// 5. Generate code
const insns = convertASM(ir)
const code = insns.join("\n")
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")
}
console.log("=== ASM === ")
return code
return output
}
export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce(processDecl, {})

+ 21
- 6
lib/ir.ts View File

@ -1,13 +1,16 @@
import type { AssignStmt, Expr, Stmt } from "./ast"
// dest <- op(x, y)
type Operand = string | number
type ASSA<Data> = { dest: string, source: Operand } & Data
export type SSA = ASSA<{ source: Operand }> // dest = source
| ASSA<{ op: string }> // dest = op(source)
| ASSA<{ op: string, source1: Operand }> // dest = op(source, source1)
export type SSACopy = ASSA<{ source: Operand }> // dest = source
export type SSAUnary = ASSA<{ op: string }> // dest = op(source)
export type SSABinary = ASSA<{ op: string, source1: Operand }> // dest = op(source, source1)
export type SSA = SSACopy | SSAUnary | SSABinary
type IRState = {
nextID: number
@ -40,9 +43,21 @@ export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array
return expr_stmts
}
export const convertExpr = (state: IRState, expr: Expr): [string, Array<SSA>] => {
export const convertExpr = (state: IRState, expr: Expr): [string | number, Array<SSA>] => {
if (typeof expr === "number" || typeof expr === "string") {
return [expr, []]
}
const [source, expr_stmts] = convertExpr(state, expr.arg)
const name = `temp${state.nextID++}`
return [name, [{ dest: name, source: expr }]]
expr_stmts.push({
dest: name,
source,
op: expr.op,
})
return [name, expr_stmts]
}
export const prettyPrintIR = (ssa: SSA): string => {

+ 6
- 2
lib/main.ts View File

@ -1,15 +1,19 @@
import { promises } from "fs"
import { basename, resolve } from "path"
import { compile } from "./compile"
const { readFile } = promises
if (process.argv.length !== 3) {
console.error("usage: gbuoy <program.b>")
console.error("usage: gbuoy <program.gby>")
process.exit(1)
}
const fileName = process.argv[2]
const localName = basename(resolve(fileName))
readFile(fileName, { encoding: "utf-8" }).then(source => {
const output = compile(source)
const output = compile(localName, source)
console.log(output)
})

+ 11
- 2
lib/parser.pegjs View File

@ -3,6 +3,8 @@
const attr = (name, args) => ({ name, args })
const stmt = (stmt_type, args) => ({ type: "stmt", stmt_type, args })
const decl = (decl_type, name, attrs, args) => ({ type: "decl", decl_type, name, attrs: attrs || [], args })
const uexpr = (op, arg) => ({ type: "unary", op, arg })
const bexpr = (op, arg1, arg2) => ({ type: "binary", op, arg1, arg2 })
}
Program = WS @(Decl / Stmt)* WS
@ -16,8 +18,13 @@ Stmt = @AssignStmt SEMI
AssignStmt = name:Ident ASSIGN expr:Expr { return stmt("assign", { name, expr }) }
// Expressions
Expr = BaseExpr
BaseExpr = Number
Expr = UnaryExpr
UnaryExpr = op:UnaryOp? e:BaseExpr { return op ? uexpr(op, e) : e }
UnaryOp = TILDE { return "bit_negate" }
/ MINUS { return "arith_negate" }
BaseExpr = Ident / Number / LPAREN @Expr RPAREN
// Attributes
Attrs = LATTR @AttrList RATTR
@ -44,6 +51,8 @@ U8 = @'u8' WS
U16 = @'u16' WS
// Terminal symbols
TILDE = '~' WS
MINUS = '-' WS
SEMI = ';' WS
ASSIGN = '<-' WS
LPAREN = '(' WS

Loading…
Cancel
Save