Browse Source

Begin work on code generation

master
Forest Belton 2 years ago
parent
commit
e181e5a007
5 changed files with 139 additions and 15 deletions
  1. +1
    -1
      gbuoy.js
  2. +103
    -0
      lib/asm.ts
  3. +16
    -10
      lib/compile.ts
  4. +18
    -4
      lib/ir.ts
  5. +1
    -0
      package.json

+ 1
- 1
gbuoy.js View File

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

+ 103
- 0
lib/asm.ts View File

@ -0,0 +1,103 @@
import type { SSA } from "./ir"
const reg8 = ["B", "C", "D", "E"] as const
type R8 = typeof reg8[number]
class ASMState {
regs: { [r in R8]: string | null }
syms: { [s: string]: R8 }
stack_offsets: { [s: string]: number }
cur_offset: number
insns: Array<string>
constructor() {
this.regs = { "B": null, "C": null, "D": null, "E": null }
this.syms = {}
this.stack_offsets = {}
this.cur_offset = 0
this.insns = []
}
// TODO: Generalize for u16
get_sym_loc(sym: string): R8 | number | null {
// return register if already allocated
if (typeof this.syms[sym] !== "undefined") {
return this.syms[sym]
}
// return stack offset if already allocated
if (typeof this.stack_offsets[sym] !== "undefined") {
return this.cur_offset - this.stack_offsets[sym]
}
return null
}
alloc_sym_loc(sym: string): R8 | number {
const existing_loc = this.get_sym_loc(sym)
if (existing_loc !== null) {
return existing_loc
}
// look for free register
for (const r8 of reg8) {
if (this.regs[r8] === null) {
this.regs[r8] = sym
this.syms[sym] = r8
return r8
}
}
// allocate space on stack
this.cur_offset++
this.stack_offsets[sym] = this.cur_offset
this.insns.push("ADD SP, -1")
return this.stack_offsets[sym]
}
}
export const convertASM = (ir: Array<SSA>): Array<string> => {
const state = new ASMState()
ir.forEach(ssa => convertASM_SSA(state, ssa))
return state.insns
}
const convertASM_SSA = (state: ASMState, ssa: SSA): void => {
const dest = state.alloc_sym_loc(ssa.dest)
if ("source1" in ssa) { // Binary
} else if ("op" in ssa) { // Unary
} else { // Copy
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 + (${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}`
)
}
}
}

+ 16
- 10
lib/compile.ts View File

@ -1,5 +1,7 @@
import { convertASM } from "./asm"
import { convertIR, prettyPrintIR } from "./ir"
import { parse } from "./parser"
import { convertIR } from "./ir"
import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast"
type SymbolDefn = {
@ -21,31 +23,35 @@ export const compile = (source: string): string => {
const decls = ast.filter((x): x is Decl => x.type == "decl")
const stmts = ast.filter((x): x is Stmt => x.type == "stmt")
console.log("Declarations", decls)
console.log("Statements", stmts)
// 3. Create top-level symbol map
const symbols = processDecls(decls)
console.log("Symbols", symbols)
// TODO: Some form of type-checking
// 4. Generate IR
const ir = convertIR(stmts)
console.log("IR", ir)
console.log("=== IR === ")
console.log(ir.map(prettyPrintIR).join("\n"))
// 5. Generate code
const insns = convertASM(ir)
const code = insns.join("\n")
return ""
console.log("=== ASM === ")
return code
}
export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce((symbols, decl) => {
export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce(processDecl, {})
export const processDecl = (symbols: SymbolMap, decl: Decl): SymbolMap => {
const symbol = processVarDecl(symbols, decl)
return {
...symbols,
[symbol.name]: symbol,
}
}, {})
}
export const processVarDecl = (symbols: SymbolMap, decl: VarDecl): SymbolDefn => {
if (typeof symbols[decl.name] !== 'undefined') {

+ 18
- 4
lib/ir.ts View File

@ -3,11 +3,11 @@ import type { AssignStmt, Expr, Stmt } from "./ast"
// dest <- op(x, y)
type Operand = string | number
type ASSA<Data> = { dest: string } & Data
type ASSA<Data> = { dest: string, source: Operand } & Data
type SSA = ASSA<{ source: Operand }>
| ASSA<{ source: Operand, op: string }>
| ASSA<{ source1: Operand, op: string, source2: Operand }>
export type SSA = ASSA<{ source: Operand }> // dest = source
| ASSA<{ op: string }> // dest = op(source)
| ASSA<{ op: string, source1: Operand }> // dest = op(source, source1)
type IRState = {
nextID: number
@ -44,3 +44,17 @@ export const convertExpr = (state: IRState, expr: Expr): [string, Array] =>
const name = `temp${state.nextID++}`
return [name, [{ dest: name, source: expr }]]
}
export const prettyPrintIR = (ssa: SSA): string => {
let output = ""
if ("source1" in ssa) {
output = `${ssa.dest} = ${ssa.source} ${ssa.op} ${ssa.source1}`
} else if ("op" in ssa) {
output = `${ssa.dest} = ${ssa.op} ${ssa.source}`
} else {
output = `${ssa.dest} = ${ssa.source}`
}
return output
}

+ 1
- 0
package.json View File

@ -11,6 +11,7 @@
"lib": "lib"
},
"scripts": {
"build": "mkdir -p build && peggy -o build/parser.js lib/parser.pegjs && tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Forest Belton <forest@homolo.gy>",

Loading…
Cancel
Save