diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2025-01-02 15:37:24 +0200 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2025-01-02 15:37:24 +0200 |
| commit | 47706066cdaa967f9f81dfd454bbdf2bffa4df97 (patch) | |
| tree | afaf98ae1623a6d72a15a864b7841f217cd6f636 | |
| parent | 99203fb27559f91ff903690338af76f6f29cf52d (diff) | |
Remove obsolete compilers
| -rw-r--r-- | atk16_ast_walking_compiler/ast_compiler_bootstrap.atk16 | 74 | ||||
| -rw-r--r-- | atk16_ast_walking_compiler/compiler.py | 716 | ||||
| -rw-r--r-- | atk16_ast_walking_compiler/sample_py/aoc23_1a.py | 48 | ||||
| -rw-r--r-- | atk16_ast_walking_compiler/sample_py/atk16.py | 39 | ||||
| -rw-r--r-- | atk16_ast_walking_compiler/sample_py/sample_py_src.py | 36 | ||||
| -rw-r--r-- | atk16_bytecode_compiler/bytecode_compiler.py | 220 |
6 files changed, 0 insertions, 1133 deletions
diff --git a/atk16_ast_walking_compiler/ast_compiler_bootstrap.atk16 b/atk16_ast_walking_compiler/ast_compiler_bootstrap.atk16 deleted file mode 100644 index b38e5f5..0000000 --- a/atk16_ast_walking_compiler/ast_compiler_bootstrap.atk16 +++ /dev/null @@ -1,74 +0,0 @@ -;; BEGIN BOOTSTRAP - -@use ext_std:* - -@let sp RH ; stack pointer (points to empty slot at top of stack) -@let fp RG ; frame pointer (points to base of currently active frame) - -@let vector_table 0x10 -@let vt_ISR0 0x10 ; ISR0 -@let vt_ISR1 0x11 ; ISR1 -@let vt_ISR2 0x12 ; ISR2 -@let vt_ISR3 0x13 ; ISR3 -@let vt_stack_addr 0x14 ; Stack address -@let vt_term_pp_addr 0x15 ; Terminal peripheral address -@let vt_kb_pp_addr 0x16 ; Keyboard peripheral address -@let vt_gr_mode_addr 0x17 ; Graphics mode setting address -@let vt_sprite_mem 0x18 ; Sprite memory address -@let vt_text_mem 0x19 ; Text memory buffer address - -@let stack_segment 0x8000 -@let mmio_segment 0xE7F0 -@let terminal_addr 0xE7F0 -@let keyboard_addr 0xE7F1 -@let gr_mode_addr 0xE7F2 -@let sprite_mem 0xE800 -@let text_mem 0xF800 - -@let gr_disabled_mode 0b00 -@let gr_text_mode 0b01 -@let gr_sprite_mode 0b10 - -@address 0x0 - ldi vt_stack_addr RA - ldr RA SP - mov SP FP - jpi program_segment - -@address vector_table - keyboard_isr ; 0x10 - hlt_isr ; 0x11 - hlt_isr ; 0x12 - hlt_isr ; 0x13 - stack_segment ; 0x14 - terminal_addr ; 0x15 - keyboard_addr ; 0x16 - gr_mode_addr ; 0x17 - sprite_mem ; 0x18 - text_mem ; 0x19 - -@label hlt_isr - hlt - -@label keyboard_isr - spu RA - spu RB - ldi vt_kb_pp_addr RA - ldr RA RA - ldr RA RA - ldi vt_term_pp_addr RB - ldr RB RB - str RA RB - spo RB - spo RA - rti - -@label program_segment - ldi vt_gr_mode_addr RA - ldr RA RA - ldi gr_disabled_mode RB - str RB RA - - jpi main - -;; END BOOTSTRAP
\ No newline at end of file diff --git a/atk16_ast_walking_compiler/compiler.py b/atk16_ast_walking_compiler/compiler.py deleted file mode 100644 index 1634bed..0000000 --- a/atk16_ast_walking_compiler/compiler.py +++ /dev/null @@ -1,716 +0,0 @@ -#!/usr/bin/env python3 -# Generate .atk16 assembly from a subset of Python - -import sys -import ast - -from dataclasses import dataclass -from typing import Literal, Set, cast, Any, TypeAlias, TypeVar -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] - -Label = str -StackOffset = int -RegChar = Literal["A", "B", "C", "D", "E", "F", "G", "H"] - -@dataclass -class Reg(): - reg: RegChar - - def __str__(self): - return f"R{self.reg}" - -ALL_REGS: list[RegChar] = ["A", "B", "C", "D", "E", "F", "G", "H"] -FRAME_POINTER_REG_CHAR: RegChar = "G" # points to base of stack frame -FRAME_POINTER_REG = Reg(FRAME_POINTER_REG_CHAR) -STACK_POINTER_REG_CHAR: RegChar = "H" -STACK_POINTER_REG = Reg(STACK_POINTER_REG_CHAR) -SPECIAL_REGS: list[RegChar] = [FRAME_POINTER_REG_CHAR, STACK_POINTER_REG_CHAR] -GENERIC_REGS: OrderedDict[RegChar, None] = OrderedDict() -for char in ALL_REGS: - if char not in SPECIAL_REGS: - GENERIC_REGS[cast(RegChar, char)] = None - -def format_asm_row(asm: str) -> str: - if not (asm.startswith("@") or asm.startswith(";")) and not asm.startswith(" ") and len(asm) > 0: - return " " + asm - else: - return asm - -class Compiler(ast.NodeVisitor): - def __init__(self): - self.const_asm: list[str] = [] - self.program_asm: list[str] = [ - "@label main" - ] - self.function_def_asms: list[str] = [] - self.currently_emitting_asm_list = self.program_asm - - self.local_bindings: dict[str, StackOffset] = {} - self.const_bindings: dict[str, Label] = {} - - self.unique_name_counter = 0 - self.latest_break_target: Label | None = None - - self.reserved_regs: OrderedDict[RegChar, None] = OrderedDict() - - def get_unique_name(self, prefix: str): - ret = f"{prefix}_{self.unique_name_counter}" - self.unique_name_counter += 1 - return ret - - def assign_const(self, name: str, value: int): - prev_currently_emitting_asm_list = self.currently_emitting_asm_list - self.currently_emitting_asm_list = self.const_asm - - self.emit_label(name) - self.emit(f"{value}") - - self.const_bindings[name] = name - - self.currently_emitting_asm_list = prev_currently_emitting_asm_list - - def emit(self, asm: str): - asm = asm.strip() - asm = format_asm_row(asm) - - self.currently_emitting_asm_list.append(asm) - - def emit_label(self, label_name: str): - label_name = label_name.lower() - self.emit(f"@label {label_name}") - - def alloc_reg(self) -> Reg: - for reg in GENERIC_REGS: - if not reg in self.reserved_regs: - self.reserved_regs[reg] = None - return Reg(reg) - - raise Exception("Ran out of registers, TODO use stack") - - def free_reg(self, reg: Reg): - self.reserved_regs.pop(reg.reg) - - class RegContextManager: - def __init__(self, compiler): - self.compiler = compiler - - def __enter__(self) -> Reg: - self.reg = self.compiler.alloc_reg() - return self.reg - - def __exit__(self, exc_type, exc_value, exc_tb): - self.compiler.free_reg(self.reg) - - def allocated_reg(self): - return Compiler.RegContextManager(self) - - def compile(self, bootstrap_asm: str, source: str) -> str: - tree = ast.parse(source) - print(ast.dump(tree, indent=4)) - self.visit(tree) - return "\n".join([ - bootstrap_asm, - "", - "\n".join(self.const_asm), - "", - "\n".join(self.function_def_asms), - "", - "\n".join(self.program_asm), - "" - ]) - - def generic_visit(self, node: ast.AST) -> Any: - raise NotImplementedError(f"type {type(node)}, value: {node}") - - def emit_builtin_call(self, name: str, args: list[ast.expr]): - self.emit(f"; Builtin call {name} {args}") - match name: - case "asm": - match args: - case [ast.Constant(str(value))]: - self.emit(value) - - # return None - with self.allocated_reg() as arg: - self.emit(f"ldi 0 {arg}") - self.emit(f"spu {arg}") - - case other: raise Exception(f"asm: invalid args: {other}") - - case "store": - if len(args) != 2: - raise Exception("Invalid number of arguments to store: " + str(len(args))) - - # Evaluate args before call - for arg in args: - self.visit(arg) - - with self.allocated_reg() as arg1, self.allocated_reg() as arg2: - self.emit(f"spo {arg2}") - self.emit(f"spo {arg1}") - self.emit(f"str {arg2} {arg1}") - - # return None - self.emit(f"ldi 0 {arg1}") - self.emit(f"spu {arg1}") - - case "load": - if len(args) != 1: - raise Exception("Invalid number of arguments to load: " + str(len(args))) - - self.visit(args[0]) - - self.emit("; (load) dereferencing top of stack") - with self.allocated_reg() as arg: - self.emit(f"spo {arg}") - self.emit(f"ldr {arg} {arg}") - self.emit(f"spu {arg}") - - case "ord": - if len(args) != 1: - raise Exception("Invalid number of arguments to ord: " + str(len(args))) - - self.visit(args[0]) - - case other: - raise Exception(f"Unknown builtin: {other}") - - def eval_int_constant_and_spu(self, value: int): - with self.allocated_reg() as reg: - if value >= 0 and value < 8: - self.emit(f"ldi {value} {reg}") - else: - name = self.get_unique_name("int") - self.assign_const(name, value) - self.emit(f"ldi {name} {reg}") - self.emit(f"ldr {reg} {reg} ; {name} = {value}") - - self.emit(f"spu {reg}") - - def emit_add_imm(self, reg1: str, addend: int, target_reg: str): - if addend >= 0 and addend < 8: - self.emit(f"addi {reg1} {addend} {target_reg}") - else: - name = self.get_unique_name("int") - self.assign_const(name, addend) - - with self.allocated_reg() as addend_reg: - self.emit(f"ldi {name} {addend_reg}") - self.emit(f"ldr {addend_reg} {addend_reg}") - self.emit(f"add {reg1} {addend_reg} {target_reg}") - - def visit_Module(self, node: ast.Module): - stmts = node.body - frame_names = self.collect_local_variables(stmts) - - self.emit("; stack frame with offsets") - for offset, name in enumerate(frame_names): - self.local_bindings[name] = offset - self.emit(f"; {name} {offset}") - - self.emit_add_imm("SP", len(frame_names), "SP") - - for stmt in stmts: - if type(stmt) == ast.Expr and type(stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(stmt) - - self.emit(f"mov FP SP") - self.emit("hlt") - - def visit_Expr(self, expr: ast.Expr): - self.visit(expr.value) - - with self.allocated_reg() as reg: - self.emit("; popping free-standing Expr result from stack") - self.emit(f"spo {reg}") - - def visit_UnaryOp(self, node: ast.UnaryOp): - self.emit(f"; {node.op}") - self.visit(node.operand) - match node.op: - case ast.Not(): - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"spo {reg1}") - self.emit(f"ldi 1 {reg2}") - self.emit(f"andi {reg1} 1 {reg1}") - self.emit(f"xor {reg1} {reg2} {reg1}") - self.emit(f"spu {reg1}") - - case ast.Invert(): # aka bitwise not - with self.allocated_reg() as reg1: - self.emit(f"spo {reg1}") - self.emit(f"not {reg1}") - self.emit(f"spu {reg1}") - - case ast.UAdd(): # +a - pass # nop - - case ast.USub(): # -a - with self.allocated_reg() as reg1: - self.emit(f"spo {reg1}") - self.emit(f"not {reg1}") - self.emit(f"addi {reg1} 1 {reg1}") - self.emit(f"spu {reg1}") - - case other: - raise NotImplementedError(f"Unhandled UnaryOp: {other}") - - def visit_BoolOp(self, node: ast.BoolOp): - self.emit(f"; {node.op}") - - match node.op: - case ast.And(): - label_short_circuit = self.get_unique_name("And_short_circuit") - - with self.allocated_reg() as reg: - for arg in node.values: - self.emit(f"; And operand {arg}") - self.visit(arg) - - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_short_circuit}") - - self.emit_label(label_short_circuit) - self.emit(f"spu {reg}") - - case ast.Or(): - label_short_circuit = self.get_unique_name("Or_short_circuit") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - for arg in node.values: - self.emit(f"; Or operand {arg}") - self.visit(arg) - - self.emit(f"spo {reg1}") - self.emit(f"subi {reg1} 1 {reg2}") - self.emit(f"bri carry {label_short_circuit}") - - self.emit_label(label_short_circuit) - self.emit(f"spu {reg1}") - - case other: - raise NotImplementedError(f"Unhandled BoolOp: {other}") - - def visit_BinOp(self, node: ast.BinOp): - self.emit(f"; {node.op}") - - self.emit(f"; BinOp lhs {node}") - self.visit(node.left) - self.emit(f"; BinOp rhs {node}") - self.visit(node.right) - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"spo {reg2}") - self.emit(f"spo {reg1}") - - match node.op: - case ast.Add(): - self.emit(f"add {reg1} {reg2} {reg1}") - case ast.Sub(): - self.emit(f"sub {reg1} {reg2} {reg1}") - case ast.BitAnd(): - self.emit(f"and {reg1} {reg2} {reg1}") - case ast.BitOr(): - self.emit(f"or {reg1} {reg2} {reg1}") - case ast.BitXor(): - self.emit(f"xor {reg1} {reg2} {reg1}") - case ast.LShift(): - self.emit(f"sll {reg1} {reg2} {reg1}") - case ast.RShift(): - self.emit(f"slr {reg1} {reg2} {reg1}") - case other: - raise NotImplementedError(f"Unhandled BinOp: {other}") - - self.emit(f"spu {reg1}") - - def visit_Constant(self, node: ast.Constant): - self.emit(f"; {node}") - match node.value: - case bool(value): - int_value = 1 if value else 0 - self.eval_int_constant_and_spu(int_value) - case int(value): - self.eval_int_constant_and_spu(value) - case str(value): - if len(value) > 1: - raise Exception("Invalid string, only single char values allowed: " + value) - - c = value[0] - int_value = ord(c) - self.eval_int_constant_and_spu(int_value) - case other: - raise NotImplementedError(f"Unhandled Constant: {other}") - - def visit_If(self, node: ast.If): - self.emit(f"; {node}") - - self.visit(node.test) - label_false = self.get_unique_name("If_false_branch") - label_end = self.get_unique_name("If_end_branch") - - with self.allocated_reg() as reg: - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_false}") - - for true_branch_stmt in node.body: - if type(true_branch_stmt) == ast.Expr and type(true_branch_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(true_branch_stmt) - - self.emit(f"jpi {label_end}") - self.emit_label(label_false) - - for false_branch_stmt in node.orelse: - if type(false_branch_stmt) == ast.Expr and type(false_branch_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(false_branch_stmt) - - self.emit_label(label_end) - - def visit_Compare(self, node: ast.Compare): - self.emit(f"; {node}") - - if len(node.ops) > 1: - raise Exception("Multiple compare ops not supported") - - if len(node.comparators) > 1: - raise Exception("Multiple comparators not supported") - - op = node.ops[0] - left = node.left - right = node.comparators[0] - - label_true = self.get_unique_name("Compare_true") - label_end = self.get_unique_name("Compare_end") - - self.visit(left) - self.visit(right) - - self.emit(f"; Comparing {left} {op} {right}") - with self.allocated_reg() as reg_lhs, self.allocated_reg() as reg_rhs: - match op: - case ast.Lt(): # lhs < rhs - self.emit(f"spo {reg_rhs}") - self.emit(f"spo {reg_lhs}") - - self.emit(f"sub {reg_lhs} {reg_rhs} {reg_lhs}") - self.emit(f"bri carry {label_true}") - - # false branch - self.emit(f"ldi 0 {reg_lhs}") - self.emit(f"spu {reg_lhs}") - self.emit(f"jpi {label_end}") - - # true branch - self.emit_label(label_true) - self.emit(f"ldi 1 {reg_lhs}") - self.emit(f"spu {reg_lhs}") - - self.emit_label(label_end) - - def visit_While(self, node: ast.While): - self.emit(f"; {node}") - - label_test = self.get_unique_name("While_test") - label_else = self.get_unique_name("While_else") - label_end = self.get_unique_name("While_end") - - prev_break_target = self.latest_break_target - self.latest_break_target = label_end - - with self.allocated_reg() as reg: - self.emit_label(label_test) - self.visit(node.test) - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_else}") - - for body_stmt in node.body: - if type(body_stmt) == ast.Expr and type(body_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(body_stmt) - - self.emit(f"jpi {label_test}") - - self.emit_label(label_else) - - for else_stmt in node.orelse: - self.visit(else_stmt) - - self.emit_label(label_end) - - self.latest_break_target = prev_break_target - - def visit_Pass(self, node: ast.Pass): - self.emit(f"; {node}") - pass - - def visit_Break(self, node: ast.Break): - self.emit(f"; {node}") - - if self.latest_break_target is None: - raise Exception("Invalid break: no break target defined, i.e. no place to break out to") - - self.emit(f"jpi {self.latest_break_target}") - - def visit_For(self, node: ast.For): - raise Exception("For loops not supported. Consider using a while loop instead.") - - def visit_Lambda(self, node: ast.Lambda): - raise Exception("Lambda functions not supported. Consider using a named function instead.") - - def collect_local_variables(self, stmts: list[ast.stmt]): - result: list[str] = [] - symbols: list[str] = [] - for stmt in stmts: - match stmt: - case ast.Assign(targets=[ast.Name(name)]): - symbols.append(name) - case ast.Assign(other): - raise Exception(f"Unsupported assignment in function definition body: {other}") - case ast.While(body=body): - symbols += self.collect_local_variables(body) - case ast.If(body=tb, orelse=fb): - symbols += self.collect_local_variables(tb) - symbols += self.collect_local_variables(fb) - case other: pass - - for symbol in symbols: - if symbol not in result: - result.append(symbol) - - return result - - def visit_FunctionDef(self, node: ast.FunctionDef): - prev_currently_emitting_asm_list = self.currently_emitting_asm_list - self.currently_emitting_asm_list = self.function_def_asms - self.emit(f"; {node}") - - fn_name = node.name - fn_params = [str(param.arg) for param in node.args.args] - fn_stmts = node.body - - # Add a "return None" to the end to make sure the function returns - if len(fn_stmts) == 0 or type(fn_stmts[len(fn_stmts) - 1]) != ast.Return: - fn_stmts.append(ast.Return(value=None)) - - if len(node.args.kwonlyargs) > 0 or len(node.args.posonlyargs) > 0 or len(node.args.kw_defaults) > 0 or len(node.args.defaults) > 0: - raise Exception("Only simple positional args are supported for now in function definitions.") - - self.emit_label(fn_name) - local_var_names = self.collect_local_variables(fn_stmts) - frame_names = fn_params + local_var_names - - # Move stack pointer to accommodate local variables - if len(local_var_names) > 0: - self.emit_add_imm("SP", len(local_var_names), "SP") - - prev_local_bindings = self.local_bindings - self.local_bindings = {} - self.emit("; stack frame with offsets") - for offset, name in enumerate(frame_names): - self.local_bindings[name] = offset - self.emit(f"; {name} {offset}") - - for stmt in fn_stmts: - self.visit(stmt) - - self.local_bindings = prev_local_bindings - self.currently_emitting_asm_list = prev_currently_emitting_asm_list - - def visit_Return(self, node: ast.Return): - self.emit(f"; {node}") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - if node.value is not None: - self.visit(node.value) - else: - self.emit(f"ldi 0 {reg1}") - self.emit(f"spu {reg1}") - - self.emit(f"; return value is on top of stack") - - self.emit(f"; return from function") - self.emit(f"subi FP 1 FP") - self.emit(f"ldr FP {reg2}") - self.emit(f"jpr {reg2}") - - def visit_Call(self, node: ast.Call): - self.emit(f"; {node}") - - match node.func: - case ast.Attribute(ast.Name(id="atk16"), attr): - self.emit_builtin_call(attr, node.args) - case ast.Name(name): - self.emit(f"; Call function {name}") - - # Push the current FP - self.emit("spu FP") - #self.emit("mov SP FP") # BUG: FP needs to be moved AFTER EVALING ARGS! args depend on FP in calling frame if there are names to resolve! - - # Reserve a stack slot for the return address - # FP will point to this slot - with self.allocated_reg() as reg: - self.emit(f"ldi 0 {reg}") - self.emit(f"spu {reg}") - - # Evaluate args before call, pushing them to stack - for arg in node.args: - self.visit(arg) - - self.emit(f"subi SP {1 + len(node.args)} FP") - - with self.allocated_reg() as reg: - self.emit("; set up return address and jump to subroutine") - self.emit(f"lpc {reg}") - self.emit(f"addi {reg} 4 {reg}") # imm must equal number of primitive instrs from lpc until jpi - self.emit(f"str {reg} FP") - self.emit(f"addi FP 1 FP") - self.emit(f"jpi {name}") - # ...after return from call... - self.emit(f"spo {reg}") - - self.emit("mov FP SP") # move stack pointer to base of stack frame, i.e. top of previous frame - self.emit("spo FP") # restore FP of previous frame - self.emit(f"spu {reg}") - - case other: - raise NotImplementedError(f"Unhandled Call: {other}") - - def resolve_name(self, name: str) -> Label | StackOffset: - if name in self.local_bindings: - return self.local_bindings[name] - - if name in self.const_bindings: - return self.const_bindings[name] - - raise Exception(f"{name} is unbound") - - def visit_Name(self, node: ast.Name): - self.emit(f"; {node} ({node.id})") - - name = node.id.lower() - addr = self.resolve_name(name) - - with self.allocated_reg() as reg: - match addr: - case Label(label): - self.emit(f"ldi {label} {reg}") - case StackOffset(offset): - self.emit(f"ldi {offset} {reg}") - self.emit(f"add FP {reg} {reg}") - case other: - raise Exception(f"Unsupported addr value: {other}") - - self.emit(f"ldr {reg} {reg}") - self.emit(f"spu {reg}") - - def visit_Assign(self, node: ast.Assign): - self.emit(f"; {node}") - - targets = node.targets - value = node.value - match targets: - case [ast.Name(name)]: - offset = self.resolve_name(name) - - if type(offset) != StackOffset: - raise Exception(f"Invalid address {offset} for name {name}. Can only assign to stack offsets.") - - self.emit(f"; assigning {name} at FP + {offset}") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"; evaluating value to be assigned") - self.visit(value) - self.emit(f"; assigning stack address (FP + {offset}) := top of stack") - self.emit(f"spo {reg1}") # value - - self.emit(f"ldi {offset} {reg2}") - self.emit(f"add {reg2} FP {reg2}") # address - self.emit(f"str {reg1} {reg2}") - - case other: - raise Exception(f"Unsupported assign targets: {other}") - - def visit_AugAssign(self, node: ast.AugAssign): - op = node.op - lhs = node.target - rhs = node.value - - if type(lhs) != ast.Name: - raise Exception("AugAssign to non-Name lhs not yet supported!") - - # construct bin op and assignment - bin_op = ast.BinOp(left=lhs, op=op, right=rhs) - assign = ast.Assign(targets=[lhs], value=bin_op) - - self.visit(assign) - - def visit_Import(self, node: ast.Import) -> Any: - match node.names: - case [ast.alias(name="atk16")]: - return - case other: - raise NotImplementedError(f"Unsupported import {other}") - - def visit_AnnAssign(self, node: ast.AnnAssign): - match node: - case ast.AnnAssign( - target=ast.Name(name), - annotation=ast.Attribute( - value=ast.Name(id="atk16"), - attr="ConstWord16"), - value=ast.Constant(value) # TODO: constant folding - ): - if type(value) == int: - self.assign_const(name.lower(), value) - return - - raise NotImplementedError("Unhandled AnnAssign:\n" + ast.dump(node, indent=4)) - - def get_module_exports(self, node: ast.Module): - result: list[str] = [] - for stmt in node.body: - match stmt: - case ast.FunctionDef(name): - result.append(name) - case _: - # only function exported since recognizing which assignments are constants is kinda hard - pass - - return result - -with open(infile_path, "r") as f: - source_py = f.read() - -with open("asm/ast_compiler_bootstrap.atk16", "r") as f: - bootstrap_asm = f.read() - -compiler = Compiler() -asm_out = compiler.compile( - bootstrap_asm, - source_py, -) - -with open(outfile_path, "w") as f: - f.write(asm_out) - diff --git a/atk16_ast_walking_compiler/sample_py/aoc23_1a.py b/atk16_ast_walking_compiler/sample_py/aoc23_1a.py deleted file mode 100644 index 12e3953..0000000 --- a/atk16_ast_walking_compiler/sample_py/aoc23_1a.py +++ /dev/null @@ -1,48 +0,0 @@ -import atk16 - -GRAPHICS_NO_MODE: atk16.ConstWord16 = 0 -GRAPHICS_TEXT_MODE: atk16.ConstWord16 = 1 -GRAPHICS_SPRITE_MODE: atk16.ConstWord16 = 2 - -GRAPHICS_MODE_PP: atk16.ConstWord16 = 0x17 -TEXT_MEM_PP: atk16.ConstWord16 = 0x19 - -graphics_mode_p = atk16.load(GRAPHICS_MODE_PP) -text_mem_p = atk16.load(TEXT_MEM_PP) - -atk16.store(graphics_mode_p, GRAPHICS_TEXT_MODE) - -INPUT_P: atk16.ConstWord16 = 0x9000 -INPUT_ELEM_SIZE: atk16.ConstWord16 = 256 - -# 1a example - -atk16.store(INPUT_P + 0, "1") -atk16.store(INPUT_P + 1, "a") -atk16.store(INPUT_P + 2, "b") -atk16.store(INPUT_P + 3, "c") -atk16.store(INPUT_P + 4, "2") -atk16.store(INPUT_P + 5, "\0") -# atk16.store_const_vec(INPUT_P + 0, "1abc2\0") -# atk16.store_const_vec(INPUT_P + 256, "pqr3stu8vwx\0") -# atk16.store_const_vec(INPUT_P + 512, "a1b2c3d4e5f\0") -# atk16.store_const_vec(INPUT_P + 768, "treb7uchet\0") - -row_p = INPUT_P -i = 0 - -first = atk16.load(row_p) -last = 0 - -while True: - char_p = INPUT_P + i - char = atk16.load(char_p) - - if char == "\0": - break - - last = char - i += 1 - -atk16.store(text_mem_p + 0, first) -atk16.store(text_mem_p + 1, last)
\ No newline at end of file diff --git a/atk16_ast_walking_compiler/sample_py/atk16.py b/atk16_ast_walking_compiler/sample_py/atk16.py deleted file mode 100644 index 9aa3c55..0000000 --- a/atk16_ast_walking_compiler/sample_py/atk16.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Literal, Any, TypeVar, NewType, Never, Callable, Generic, overload, cast -Char = Literal['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\n', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', '\xad', '®', '¯', '°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ'] -Void = Literal[0] -Word16 = int -ConstWord16 = int - -def store(p: Word16, value: Word16 | Char) -> Void: - raise NotImplementedError - -@overload -def store_const_vec(p: Word16, value: list[Word16]) -> Void: ... -@overload -def store_const_vec(p: Word16, value: list[Char]) -> Void: ... -@overload -def store_const_vec(p: Word16, value: str) -> Void: ... - -def store_const_vec(p, value) -> Void: - raise NotImplementedError - -def load(p: Word16) -> Word16: - raise NotImplementedError - -T = TypeVar("T") -def call_inline(expr: T) -> T: - """Inline the function call `expr` at the callsite. - This can lead to a larger output size but avoids having to do a subroutine call.""" - raise NotImplementedError - -def asm(asm: str): - """Inject ATK16 assembly `asm` into the compiled output.""" - raise NotImplementedError - -def ord(char: Char) -> int: - """Convert char to int""" - raise NotImplementedError - -def const(word: Word16) -> ConstWord16: - """When used in an assignment such as `A = const(0xFF)`, stores the value as a globally accessible constant.""" - raise NotImplementedError
\ No newline at end of file diff --git a/atk16_ast_walking_compiler/sample_py/sample_py_src.py b/atk16_ast_walking_compiler/sample_py/sample_py_src.py deleted file mode 100644 index 619d22e..0000000 --- a/atk16_ast_walking_compiler/sample_py/sample_py_src.py +++ /dev/null @@ -1,36 +0,0 @@ -# TODO: actual imports, not just `import atk16` -from atk16 import * - -GRAPHICS_NO_MODE: ConstWord16 = 0 -GRAPHICS_TEXT_MODE: ConstWord16 = 1 -GRAPHICS_SPRITE_MODE: ConstWord16 = 2 - -GRAPHICS_MODE_PP: ConstWord16 = 0x17 -TEXT_MEM_PP: ConstWord16 = 0x19 - -TEXT_MEM_PP: ConstWord16 = 0x19 - -# asm( -# "@label keyboard_isr" -# " spu RA" - -# " spo RA" -# ) - -# set_isr(0, "keyboard_isr") - -graphics_mode_p = load(GRAPHICS_MODE_PP) -text_mem_p = load(TEXT_MEM_PP) - -store(graphics_mode_p, GRAPHICS_TEXT_MODE) - -def nth_letter(n: int) -> int: - return ord('A') + n - -i = 0 -while i < 26: - store(text_mem_p + i, nth_letter(i)) - i += 1 - -while True: - pass 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)) |
