import type { SSA, SSABinary, SSACopy, SSAUnary } 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))
|
|
|
|
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 => {
|
|
if ("source1" in ssa) { // Binary
|
|
convertASM_SSA_Binary(state, ssa)
|
|
} else if ("op" in ssa) { // Unary
|
|
convertASM_SSA_Unary(state, ssa)
|
|
} else { // Copy
|
|
convertASM_SSA_Copy(state, ssa)
|
|
}
|
|
}
|
|
|
|
export const convertASM_SSA_Binary = (state: ASMState, ssa: SSABinary): 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 {
|
|
state.insns.push(
|
|
`LD HL, SP + (${loc})`,
|
|
`LD A, (HL)`
|
|
)
|
|
}
|
|
}
|
|
|
|
switch (ssa.op) {
|
|
case "add":
|
|
if (typeof ssa.source1 === "number") {
|
|
state.insns.push(`ADD ${ssa.source1}`)
|
|
} else {
|
|
const loc = state.get_sym_loc(ssa.source1)
|
|
if (loc === null) {
|
|
throw new Error('fuck')
|
|
} else if (typeof loc === "string") {
|
|
state.insns.push(`ADD ${loc}`)
|
|
} else {
|
|
throw new Error('fuck')
|
|
state.insns.push(
|
|
`LD HL, SP + (${loc})`
|
|
)
|
|
}
|
|
}
|
|
break
|
|
|
|
default:
|
|
throw new Error(`unsupported binary 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_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 {
|
|
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
|
|
|
|
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 + (${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}`
|
|
)
|
|
}
|
|
}
|