diff options
Diffstat (limited to 'atk16_bytecode_compiler')
| -rw-r--r-- | atk16_bytecode_compiler/bytecode_compiler.py | 220 |
1 files changed, 0 insertions, 220 deletions
diff --git a/atk16_bytecode_compiler/bytecode_compiler.py b/atk16_bytecode_compiler/bytecode_compiler.py deleted file mode 100644 index 42cf8dc..0000000 --- a/atk16_bytecode_compiler/bytecode_compiler.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python3 -# Generate .atk16 assembly from Python bytecode - -import sys -import dis - -from dataclasses import dataclass -from typing import Literal, Set, cast, Any -from collections import OrderedDict - -if len(sys.argv) != 3: - print("usage: compiler.py <infile.py> <outfile.atk16>") - sys.exit(1) - -infile_path = sys.argv[1] -outfile_path = sys.argv[2] - -with open(infile_path, "r") as f: - source_py = f.read() - -compiled_code = compile(source_py, infile_path, 'exec') -disassembled = dis.get_instructions(compiled_code) - -co_consts = compiled_code.co_consts -co_names = compiled_code.co_names -print("### Constants") -print(co_consts) -print("\n### Names") -print(co_names) -print("\n### Instructions") -instrs: list[tuple[str, int | None]] = [] -for instr in disassembled: - print(f"{instr.opname}\t{instr.arg}") - instrs.append((instr.opname, instr.arg)) - -### compile -asm_out: list[str] = [] -def emit(stmt: str) -> int: - ret = len(asm_out) - asm_out.append(stmt) - return ret - -_counter = 0 -def get_unique_name(prefix: str): - global _counter - ret = f"{prefix}_{_counter}" - _counter += 1 - return ret - -@dataclass -class Label: - label: str - - def __str__(self): - return f"@label {self.label}" - -RegChar = Literal["A", "B", "C", "D", "E", "F", "G", "H"] -GENERIC_REGS_LST: list[RegChar] = ["A", "B", "C", "D", "E", "F"] -GENERIC_REGS: OrderedDict[RegChar, None] = OrderedDict() -for char in GENERIC_REGS_LST: - GENERIC_REGS[cast(RegChar, char)] = None - -@dataclass -class Reg: - reg: RegChar - - def __str__(self): - return f"R{self.reg}" - -Value = Label | Reg - -reserved_regs: OrderedDict[RegChar, None] = OrderedDict() -def alloc_reg() -> Reg: - for reg in GENERIC_REGS: - if not reg in reserved_regs: - reserved_regs[reg] = None - return Reg(reg) - - raise Exception("Ran out of registers, TODO use stack") - -def free_reg(reg: Reg): - reserved_regs.pop(reg.reg) - -def emit_serialize_const(const: Any): - match const: - case int(v): - emit(f" {v}") - case str(v): - if len(v) > 1: - raise Exception("Cannot serialize const string: " + v) - - emit(f" {ord(v[0])}") - case _: - print(f"warn: cannot serialize {type(const)} ({const}), emitting zero") - emit(" 0") - -STACK_POINTER_REG = Reg('H') -emit(f"@let SP {STACK_POINTER_REG}") -emit("@use ext_std:*") -emit("@include bootstrap") - -py_consts_addr = emit("@label py_consts") - -for const_i, const in enumerate(co_consts): - emit(f"; {const_i}: {const}") - emit_serialize_const(const) - -emit("@label atk_store") -_reg_value = alloc_reg() -_reg_addr = alloc_reg() -emit(f" str RB RA") -emit(f" rsr") -free_reg(_reg_value) -free_reg(_reg_addr) - -emit("@label atk_enable_text_mode") -emit(" set_graphics_mode gr_text_mode") -emit(" rsr") - -emit("@label main") - -instr_i = 0 - -def instr_pop(): - global instr_i - ret = instrs[instr_i] - instr_i += 1 - return ret - -def instr_pop_expect(opcode: str, arg: int | None = -1): - ret_op, ret_arg = instr_pop() - if ret_op != opcode: - raise Exception(f"Expected opcode {opcode}, got {ret_op}") - if arg != -1 and arg != ret_arg: - raise Exception(f"Expected instruction arg {arg}, got {ret_arg}") - return ret_op, ret_arg - -def instr_peek(): - return instrs[instr_i] - -def instr_peek_expect(opcode: str, arg: int | None = -1): - ret_op, ret_arg = instr_peek() - if ret_op != opcode: - raise Exception(f"Expected {opcode}, got {ret_op}") - if arg != -1 and arg != ret_arg: - raise Exception(f"Expected instruction arg {arg}, got {ret_arg}") - return ret_op, ret_arg - -instr_pop_expect("RESUME") -instr_pop_expect("LOAD_CONST", 0) -instr_pop_expect("LOAD_CONST", 1) -instr_pop_expect("IMPORT_NAME", 0) -instr_pop_expect("IMPORT_STAR", None) - -while instr_i < len(instrs): - opcode, arg = instr_pop() - emit(f"; {opcode} {arg}") - match opcode: - case "LOAD_CONST": - reg = alloc_reg() - emit(f" ldi ${{py_consts + {cast(int, arg)}}} {reg}") - emit(f" ldr {reg} {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "PUSH_NULL": - reg = alloc_reg() - emit(f" ldi 0 {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "LOAD_NAME": - name = co_names[cast(int, arg)] - reg = alloc_reg() - emit(f" ldi {name} {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "CALL": - arg_count = cast(int, arg) - # if arg_count > len(GENERIC_REGS_LST): - # raise Exception(f"Function calls with > {len(GENERIC_REGS_LST)} arguments not supported") - - regs_to_restore: list[tuple[Reg, Reg]] = [] - for j in range(arg_count): - i = arg_count - j - 1 - ith_reg_char = GENERIC_REGS_LST[i] - ith_reg = Reg(ith_reg_char) - if ith_reg_char in reserved_regs: - reg = alloc_reg() - emit(f" mov {ith_reg} {reg}") - regs_to_restore.append((reg, ith_reg)) - - emit(f" spo {ith_reg}") - reserved_regs[ith_reg_char] = None # manually reserve ith_reg - - fn_reg = alloc_reg() - emit(f" spo {fn_reg}") - # emit(f" ldr {fn_reg} {fn_reg}") - emit(f" csr {fn_reg}") - free_reg(fn_reg) - - for from_reg, ith_reg in regs_to_restore: - emit(f" mov {from_reg} {ith_reg}") - - for j in range(arg_count): - i = arg_count - j - 1 - ith_reg_char = GENERIC_REGS_LST[i] - ith_reg = Reg(ith_reg_char) - free_reg(ith_reg) - - case "POP_TOP": - reg = alloc_reg() - emit(f" spo {reg}") - free_reg(reg) - case _: - print(f"Skipping not implemented opcode: {opcode}") - -emit("@label keep_alive") -emit(" jpi keep_alive") - -with open(outfile_path, "w") as f: - f.write("\n".join(asm_out)) |
