A "high-level" language for the Gameboy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
4.6 KiB

  1. import type { SSA, SSABinary, SSACopy, SSAUnary } from "./ir"
  2. const reg8 = ["B", "C", "D", "E"] as const
  3. type R8 = typeof reg8[number]
  4. class ASMState {
  5. regs: { [r in R8]: string | null }
  6. syms: { [s: string]: R8 }
  7. stack_offsets: { [s: string]: number }
  8. cur_offset: number
  9. insns: Array<string>
  10. constructor() {
  11. this.regs = { "B": null, "C": null, "D": null, "E": null }
  12. this.syms = {}
  13. this.stack_offsets = {}
  14. this.cur_offset = 0
  15. this.insns = []
  16. }
  17. // TODO: Generalize for u16
  18. get_sym_loc(sym: string): R8 | number | null {
  19. // return register if already allocated
  20. if (typeof this.syms[sym] !== "undefined") {
  21. return this.syms[sym]
  22. }
  23. // return stack offset if already allocated
  24. if (typeof this.stack_offsets[sym] !== "undefined") {
  25. return this.cur_offset - this.stack_offsets[sym]
  26. }
  27. return null
  28. }
  29. alloc_sym_loc(sym: string): R8 | number {
  30. const existing_loc = this.get_sym_loc(sym)
  31. if (existing_loc !== null) {
  32. return existing_loc
  33. }
  34. // look for free register
  35. for (const r8 of reg8) {
  36. if (this.regs[r8] === null) {
  37. this.regs[r8] = sym
  38. this.syms[sym] = r8
  39. return r8
  40. }
  41. }
  42. // allocate space on stack
  43. this.cur_offset++
  44. this.stack_offsets[sym] = this.cur_offset
  45. this.insns.push("ADD SP, -1")
  46. return this.stack_offsets[sym]
  47. }
  48. }
  49. export const convertASM = (ir: Array<SSA>): Array<string> => {
  50. const state = new ASMState()
  51. ir.forEach(ssa => convertASM_SSA(state, ssa))
  52. for (let sym of Object.keys(state.syms)) {
  53. if (sym.startsWith("temp")) {
  54. continue
  55. }
  56. state.insns.push(
  57. `LD A, ${state.syms[sym]}`,
  58. `LD (${sym}), A`
  59. )
  60. }
  61. for (let sym of Object.keys(state.stack_offsets)) {
  62. if (sym.startsWith("temp")) {
  63. continue
  64. }
  65. state.insns.push(
  66. `LD HL, SP + (${state.cur_offset - state.stack_offsets[sym]})`,
  67. `LD A, (HL)`,
  68. `LD (${sym}), A`
  69. )
  70. }
  71. if (state.cur_offset !== 0) {
  72. state.insns.push(`ADD SP, ${state.cur_offset}`)
  73. }
  74. return state.insns
  75. }
  76. // TODO: Track liveness
  77. const convertASM_SSA = (state: ASMState, ssa: SSA): void => {
  78. if ("source1" in ssa) { // Binary
  79. convertASM_SSA_Binary(state, ssa)
  80. } else if ("op" in ssa) { // Unary
  81. convertASM_SSA_Unary(state, ssa)
  82. } else { // Copy
  83. convertASM_SSA_Copy(state, ssa)
  84. }
  85. }
  86. export const convertASM_SSA_Binary = (state: ASMState, ssa: SSABinary): void => {
  87. const dest = state.alloc_sym_loc(ssa.dest)
  88. }
  89. export const convertASM_SSA_Unary = (state: ASMState, ssa: SSAUnary): void => {
  90. const dest = state.alloc_sym_loc(ssa.dest)
  91. if (typeof ssa.source == "number") {
  92. state.insns.push(`LD A, ${ssa.source}`)
  93. } else {
  94. const loc = state.get_sym_loc(ssa.source)
  95. if (loc === null) {
  96. state.insns.push(`LD A, (${ssa.source})`)
  97. } else if (typeof loc === "string") {
  98. state.insns.push(`LD A, ${loc}`)
  99. } else {
  100. state.insns.push(
  101. `LD HL, SP + (${loc})`,
  102. `LD A, (HL)`
  103. )
  104. }
  105. }
  106. switch (ssa.op) {
  107. case '-':
  108. state.insns.push(
  109. `CPL`,
  110. `INC A`,
  111. )
  112. break
  113. default:
  114. throw new Error(`unsupported unary op \`'${ssa.op}`)
  115. }
  116. if (typeof dest === "string") {
  117. state.insns.push(`LD ${dest}, A`)
  118. } else {
  119. state.insns.push(
  120. `LD HL, SP + (${dest})`,
  121. `LD (HL), A`
  122. )
  123. }
  124. }
  125. export const convertASM_SSA_Copy = (state: ASMState, ssa: SSACopy): void => {
  126. const dest = state.alloc_sym_loc(ssa.dest)
  127. let source = ""
  128. if (typeof ssa.source == "number") {
  129. source = ssa.source.toString()
  130. } else {
  131. const loc = state.get_sym_loc(ssa.source)
  132. if (loc === null) {
  133. state.insns.push(`LD A, (${ssa.source})`)
  134. source = "A"
  135. } else if (typeof loc === "string") {
  136. source = loc
  137. } else {
  138. state.insns.push(
  139. `LD HL, SP + (${loc})`,
  140. `LD A, (HL)`
  141. )
  142. source = "A"
  143. }
  144. }
  145. if (typeof dest === "string") {
  146. state.insns.push(`LD ${dest}, ${source}`)
  147. } else {
  148. state.insns.push(
  149. `LD HL, SP + (${dest})`,
  150. `LD (HL), ${source}`
  151. )
  152. }
  153. }