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.

240 lines
6.1 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. if (typeof ssa.source === "number") {
  89. state.insns.push(`LD A, ${ssa.source}`)
  90. } else {
  91. const loc = state.get_sym_loc(ssa.source)
  92. if (loc === null) {
  93. state.insns.push(`LD A, (${ssa.source})`)
  94. } else if (typeof loc === "string") {
  95. state.insns.push(`LD A, ${loc}`)
  96. } else {
  97. state.insns.push(
  98. `LD HL, SP + (${loc})`,
  99. `LD A, (HL)`
  100. )
  101. }
  102. }
  103. switch (ssa.op) {
  104. case "add":
  105. if (typeof ssa.source1 === "number") {
  106. state.insns.push(`ADD ${ssa.source1}`)
  107. } else {
  108. const loc = state.get_sym_loc(ssa.source1)
  109. if (loc === null) {
  110. throw new Error('fuck')
  111. } else if (typeof loc === "string") {
  112. state.insns.push(`ADD ${loc}`)
  113. } else {
  114. throw new Error('fuck')
  115. state.insns.push(
  116. `LD HL, SP + (${loc})`
  117. )
  118. }
  119. }
  120. break
  121. default:
  122. throw new Error(`unsupported binary op \`${ssa.op}'`)
  123. }
  124. if (typeof dest === "string") {
  125. state.insns.push(`LD ${dest}, A`)
  126. } else {
  127. state.insns.push(
  128. `LD HL, SP + (${dest})`,
  129. `LD (HL), A`
  130. )
  131. }
  132. }
  133. export const convertASM_SSA_Unary = (state: ASMState, ssa: SSAUnary): void => {
  134. const dest = state.alloc_sym_loc(ssa.dest)
  135. if (typeof ssa.source == "number") {
  136. state.insns.push(`LD A, ${ssa.source}`)
  137. } else {
  138. const loc = state.get_sym_loc(ssa.source)
  139. if (loc === null) {
  140. state.insns.push(`LD A, (${ssa.source})`)
  141. } else if (typeof loc === "string") {
  142. state.insns.push(`LD A, ${loc}`)
  143. } else {
  144. state.insns.push(
  145. `LD HL, SP + (${loc})`,
  146. `LD A, (HL)`
  147. )
  148. }
  149. }
  150. switch (ssa.op) {
  151. case 'arith_negate':
  152. state.insns.push(
  153. `CPL`,
  154. `INC A`,
  155. )
  156. break
  157. case 'bit_negate':
  158. state.insns.push(
  159. `CPL`
  160. )
  161. break
  162. default:
  163. throw new Error(`unsupported unary op \`'${ssa.op}`)
  164. }
  165. if (typeof dest === "string") {
  166. state.insns.push(`LD ${dest}, A`)
  167. } else {
  168. state.insns.push(
  169. `LD HL, SP + (${dest})`,
  170. `LD (HL), A`
  171. )
  172. }
  173. }
  174. export const convertASM_SSA_Copy = (state: ASMState, ssa: SSACopy): void => {
  175. const dest = state.alloc_sym_loc(ssa.dest)
  176. let source = ""
  177. if (typeof ssa.source == "number") {
  178. source = ssa.source.toString()
  179. } else {
  180. const loc = state.get_sym_loc(ssa.source)
  181. if (loc === null) {
  182. state.insns.push(`LD A, (${ssa.source})`)
  183. source = "A"
  184. } else if (typeof loc === "string") {
  185. source = loc
  186. } else {
  187. state.insns.push(
  188. `LD HL, SP + (${loc})`,
  189. `LD A, (HL)`
  190. )
  191. source = "A"
  192. }
  193. }
  194. if (typeof dest === "string") {
  195. state.insns.push(`LD ${dest}, ${source}`)
  196. } else {
  197. state.insns.push(
  198. `LD HL, SP + (${dest})`,
  199. `LD (HL), ${source}`
  200. )
  201. }
  202. }