aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2023-11-07 16:29:52 +0200
committerJan Tuomi <jans.tuomi@gmail.com>2023-11-07 16:29:52 +0200
commit5a0e57601a9489d9f56602e0184e722c85ffc310 (patch)
treed217f13caa628bba83411da15a027fecbbb7244c
parent7309d563c6661f6e17c1ed3577b40b9646a85bb4 (diff)
WIP tree walking compiler
-rw-r--r--Makefile4
-rw-r--r--asm/ast_compiler_bootstrap.atk1674
-rw-r--r--asm/atk16.py23
-rw-r--r--asm/test_py_src.atk16235
-rw-r--r--asm/test_py_src.atk16_optimized115
-rw-r--r--asm/test_py_src.py23
-rw-r--r--asm/test_py_src.pydis2
-rw-r--r--high-level-lang-idea.perl36
-rw-r--r--src/ast_compiler.py350
-rw-r--r--src/bytecode_compiler.py (renamed from src/compiler.py)5
10 files changed, 699 insertions, 168 deletions
diff --git a/Makefile b/Makefile
index f06557f..df72879 100644
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,10 @@ convert-ttf:
dig-install:
$(py) src/dig_install.py $(bin) $(dig) $(label)
+# make compile-py-to-atk16 in=asm/test_py_src.py out=asm/test_py_src.atk16
+compile-py-to-atk16:
+ $(py) src/ast_compiler.py $(in) $(out)
+
# make test
test:
$(py) src/test_alu.py digital/atk16_alu.dig
diff --git a/asm/ast_compiler_bootstrap.atk16 b/asm/ast_compiler_bootstrap.atk16
new file mode 100644
index 0000000..ad401f1
--- /dev/null
+++ b/asm/ast_compiler_bootstrap.atk16
@@ -0,0 +1,74 @@
+;; BEGIN BOOTSTRAP
+
+@opt stack_pointer RG
+@opt csr_scratch RH
+
+@use ext_std:*
+
+@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 0xE000
+@let terminal_addr 0xE000
+@let keyboard_addr 0xE001
+@let gr_mode_addr 0xE002
+@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 __STACK_POINTER
+ 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
+ ldi 0x55 RE
+ 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/asm/atk16.py b/asm/atk16.py
index 143dd52..f1a2c95 100644
--- a/asm/atk16.py
+++ b/asm/atk16.py
@@ -1,11 +1,26 @@
-def atk_store(addr: int, value: int):
+from typing import Literal, Any, TypeVar, NewType, Never
+a = ["A", "B", "C"]
+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', '®', '¯', '°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ']
+ConstInt = int
+
+def store(addr: int, value: int | Char):
+ return NotImplemented
+
+def load(addr: int) -> int:
+ return NotImplemented
+
+def put_char(c: int):
return NotImplemented
-def atk_load(addr: int) -> int:
+def set_graphics_mode(mode: Literal[0, 1, 2]):
return NotImplemented
-def atk_put_char(c: int):
+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."""
return NotImplemented
-def atk_enable_text_mode():
+def asm(asm: str):
+ """Inject ATK16 assembly `asm` into the compiled output."""
return NotImplemented
diff --git a/asm/test_py_src.atk16 b/asm/test_py_src.atk16
index af8489b..c1bf1af 100644
--- a/asm/test_py_src.atk16
+++ b/asm/test_py_src.atk16
@@ -1,165 +1,160 @@
+;; BEGIN BOOTSTRAP
+
@opt stack_pointer RG
@opt csr_scratch RH
+
@use ext_std:*
-@include bootstrap
-@label py_consts
-; 0: 0
- 0
-; 1: ('*',)
- 0
-; 2: 63488
+
+@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 0xE000
+@let terminal_addr 0xE000
+@let keyboard_addr 0xE001
+@let gr_mode_addr 0xE002
+@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 __STACK_POINTER
+ 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
+ ldi 0x55 RE
+ 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
+
+@label TEXT_MODE
+ 1
+@label int_0
+ 23
+@label int_1
63488
-; 3: 72
+@label int_2
72
-; 4: 63489
+@label int_3
63489
-; 5: 69
+@label int_4
69
-; 6: 63490
+@label int_5
63490
-; 7: 76
+@label int_6
76
-; 8: 63491
+@label int_7
63491
-; 9: 63492
+@label int_8
+ 76
+@label int_9
63492
-; 10: 79
+@label int_10
79
-; 11: None
- 0
-@label atk_store
- str RB RA
- rsr
-@label atk_enable_text_mode
- set_graphics_mode gr_text_mode
- rsr
+
@label main
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 1
- ldi atk_enable_text_mode RA
+; builtin call store [<ast.Constant object at 0x1045a3d90>, <ast.Name object at 0x1045a3d60>]
+ ldi int_0 RA
+ ldr RA RA
spu RA
-; PRECALL 0
-; CALL 0
- spo RA
- csr RA
-; POP_TOP None
+ spo RB
spo RA
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 2
- ldi atk_store RA
- spu RA
-; LOAD_CONST 2
- ldi ${py_consts + 2} RA
+ str RB RA
+; builtin call store [<ast.Constant object at 0x1045a3c70>, <ast.Constant object at 0x1045a3c40>]
+ ldi int_1 RA
ldr RA RA
spu RA
-; LOAD_CONST 3
- ldi ${py_consts + 3} RA
+ ldi int_2 RA
ldr RA RA
spu RA
-; PRECALL 2
-; CALL 2
spo RB
spo RA
- spo RC
- csr RC
-; POP_TOP None
- spo RA
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 2
- ldi atk_store RA
- spu RA
-; LOAD_CONST 4
- ldi ${py_consts + 4} RA
+ str RB RA
+; builtin call store [<ast.Constant object at 0x1045a3b50>, <ast.Constant object at 0x1045a3b20>]
+ ldi int_3 RA
ldr RA RA
spu RA
-; LOAD_CONST 5
- ldi ${py_consts + 5} RA
+ ldi int_4 RA
ldr RA RA
spu RA
-; PRECALL 2
-; CALL 2
spo RB
spo RA
- spo RC
- csr RC
-; POP_TOP None
- spo RA
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 2
- ldi atk_store RA
- spu RA
-; LOAD_CONST 6
- ldi ${py_consts + 6} RA
+ str RB RA
+; builtin call store [<ast.Constant object at 0x1045a3a30>, <ast.Constant object at 0x1045a3a00>]
+ ldi int_5 RA
ldr RA RA
spu RA
-; LOAD_CONST 7
- ldi ${py_consts + 7} RA
+ ldi int_6 RA
ldr RA RA
spu RA
-; PRECALL 2
-; CALL 2
spo RB
spo RA
- spo RC
- csr RC
-; POP_TOP None
- spo RA
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 2
- ldi atk_store RA
- spu RA
-; LOAD_CONST 8
- ldi ${py_consts + 8} RA
+ str RB RA
+; builtin call store [<ast.Constant object at 0x1045a3910>, <ast.Constant object at 0x1045a38e0>]
+ ldi int_7 RA
ldr RA RA
spu RA
-; LOAD_CONST 7
- ldi ${py_consts + 7} RA
+ ldi int_8 RA
ldr RA RA
spu RA
-; PRECALL 2
-; CALL 2
spo RB
spo RA
- spo RC
- csr RC
-; POP_TOP None
- spo RA
-; PUSH_NULL None
- ldi 0 RA
- spu RA
-; LOAD_NAME 2
- ldi atk_store RA
- spu RA
-; LOAD_CONST 9
- ldi ${py_consts + 9} RA
+ str RB RA
+; builtin call store [<ast.Constant object at 0x1045a37f0>, <ast.Constant object at 0x1045a37c0>]
+ ldi int_9 RA
ldr RA RA
spu RA
-; LOAD_CONST 10
- ldi ${py_consts + 10} RA
+ ldi int_10 RA
ldr RA RA
spu RA
-; PRECALL 2
-; CALL 2
spo RB
spo RA
- spo RC
- csr RC
-; POP_TOP None
- spo RA
-; LOAD_CONST 11
- ldi ${py_consts + 11} RA
- ldr RA RA
- spu RA
-; RETURN_VALUE None
-@label keep_alive
- jpi keep_alive \ No newline at end of file
+ str RB RA
+; builtin call asm [<ast.Constant object at 0x1045a2c20>]
+ ldi 1 RA \ No newline at end of file
diff --git a/asm/test_py_src.atk16_optimized b/asm/test_py_src.atk16_optimized
new file mode 100644
index 0000000..a6a77ab
--- /dev/null
+++ b/asm/test_py_src.atk16_optimized
@@ -0,0 +1,115 @@
+@opt stack_pointer RG
+@opt csr_scratch RH
+@use ext_std:*
+@let vector_table 0x10
+@let vt_ISR0 0x10
+@let vt_ISR1 0x11
+@let vt_ISR2 0x12
+@let vt_ISR3 0x13
+@let vt_stack_addr 0x14
+@let vt_term_pp_addr 0x15
+@let vt_kb_pp_addr 0x16
+@let vt_gr_mode_addr 0x17
+@let vt_sprite_mem 0x18
+@let vt_text_mem 0x19
+@let stack_segment 0x8000
+@let mmio_segment 0xE000
+@let terminal_addr 0xE000
+@let keyboard_addr 0xE001
+@let gr_mode_addr 0xE002
+@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 __STACK_POINTER
+ jpi program_segment
+@address vector_table
+ keyboard_isr
+ hlt_isr
+ hlt_isr
+ hlt_isr
+ stack_segment
+ terminal_addr
+ keyboard_addr
+ gr_mode_addr
+ sprite_mem
+ text_mem
+@label hlt_isr
+ ldi 0x55 RE
+ 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
+@label TEXT_MODE
+ 1
+@label int_0
+ 23
+@label int_1
+ 63488
+@label int_2
+ 72
+@label int_3
+ 63489
+@label int_4
+ 69
+@label int_5
+ 63490
+@label int_6
+ 76
+@label int_7
+ 63491
+@label int_8
+ 76
+@label int_9
+ 63492
+@label int_10
+ 79
+@label main
+ ldi int_0 RA
+ ldr RA RB
+ spo RA
+ str RB RA
+ ldi int_1 RA
+ ldr RA RA
+ ldi int_2 RB
+ ldr RB RB
+ str RB RA
+ ldi int_3 RA
+ ldr RA RA
+ ldi int_4 RB
+ ldr RB RB
+ str RB RA
+ ldi int_5 RA
+ ldr RA RA
+ ldi int_6 RB
+ ldr RB RB
+ str RB RA
+ ldi int_7 RA
+ ldr RA RA
+ ldi int_8 RB
+ ldr RB RB
+ str RB RA
+ ldi int_9 RA
+ ldr RA RA
+ ldi int_10 RB
+ ldr RB RB
+ str RB RA
+ ldi 1 RA \ No newline at end of file
diff --git a/asm/test_py_src.py b/asm/test_py_src.py
index 89658f8..a1eca3f 100644
--- a/asm/test_py_src.py
+++ b/asm/test_py_src.py
@@ -1,8 +1,17 @@
-from atk16 import *
+import atk16
-atk_enable_text_mode()
-atk_store(0xF800, 65 + 7) # H
-atk_store(0xF801, 65 + 4) # E
-atk_store(0xF802, 65 + 11) # L
-atk_store(0xF803, 65 + 11) # L
-atk_store(0xF804, 65 + 14) # O
+TEXT_MODE: atk16.ConstInt = 1
+GRAPHICS_MODE_ADDR: atk16.ConstInt = 0x17
+
+atk16.store(GRAPHICS_MODE_ADDR, TEXT_MODE)
+atk16.store(0xF800, 'H')
+atk16.store(0xF801, 'E')
+atk16.store(0xF802, 'L')
+atk16.store(0xF803, 'L')
+atk16.store(0xF804, 'O')
+
+a = atk16.call_inline(
+ atk16.load(0xF000)
+)
+
+atk16.asm("ldi 1 RA")
diff --git a/asm/test_py_src.pydis b/asm/test_py_src.pydis
index 483f073..f4e427f 100644
--- a/asm/test_py_src.pydis
+++ b/asm/test_py_src.pydis
@@ -1,5 +1,5 @@
### Constants
-(0, ('*',), 63488, 72, 63489, 69, 63490, 76, 63491, 63492, 79, None)
+(0, ('*',), 63488, 'H', 63489, 'E', 63490, 'L', 63491, 63492, 'O', None)
### Names
('atk16', 'atk_enable_text_mode', 'atk_store')
diff --git a/high-level-lang-idea.perl b/high-level-lang-idea.perl
deleted file mode 100644
index 426cae8..0000000
--- a/high-level-lang-idea.perl
+++ /dev/null
@@ -1,36 +0,0 @@
-# Builtin types:
-# u8, u16, i8, i16, block[T], void
-
-# allocate 16 words, returning block
-# a block is similar to a C pointer but contains the allocated size as well
-decl v :: block[u16] = alloc-words 16
-
-# define a fn that zeroes a block of memory
-decl zero-block :: block[u16] -> void
-func zero-block block
- # for each address in block
- for-addr addr block
- # store the value 0 in memory at address addr
- store addr 0
-
-# call fn zero-block in inlined mode
-zero-block% v
-
-# compute first 16 fibonacci numbers
-decl a :: u16 = 0
-decl b :: u16 = 1
-for-index idx block
- # declarations in loop constructs are hoisted
- decl addr :: u16 = addr-at idx block
- decl v :: u16
-
- if idx == 0
- v = a
- else if idx == 1
- v = b
- else
- v = a + b
-
- store addr v
- a = b
- b = v
diff --git a/src/ast_compiler.py b/src/ast_compiler.py
new file mode 100644
index 0000000..2208e43
--- /dev/null
+++ b/src/ast_compiler.py
@@ -0,0 +1,350 @@
+#!/usr/bin/env python3
+# Generate .atk16 assembly from a subset of Python
+
+from _ast import AnnAssign, Expr, Module
+import sys
+import ast
+
+from dataclasses import dataclass
+from typing import Literal, Set, cast, Any, TypeAlias
+from collections import OrderedDict
+
+if len(sys.argv) != 3:
+ print("usage: ast_compiler.py <infile.py> <outfile.atk16>")
+ sys.exit(1)
+
+infile_path = sys.argv[1]
+outfile_path = sys.argv[2]
+
+Addr = int
+Label = str
+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"]
+STACK_POINTER_REG = "G"
+CSR_SCRATCH_REG = "H"
+SPECIAL_REGS: list[RegChar] = [STACK_POINTER_REG, CSR_SCRATCH_REG]
+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.const_bindings: dict[str, Label] = {}
+ self.call_depth: int = 0
+ self.unique_name_counter = 0
+
+ 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):
+ self.const_asm.append(f"@label {name}")
+ self.const_asm.append(f" {value}")
+ self.const_bindings[name] = name
+
+ def emit(self, asm: str):
+ asm = asm.strip()
+ asm = format_asm_row(asm)
+
+ self.program_asm.append(asm)
+
+ 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)
+
+ 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.program_asm)
+ ])
+
+ 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(value)]:
+ if type(value) != str:
+ raise Exception(f"asm: invalid arg type: {type(value)}")
+
+ self.emit(value)
+ case _: raise Exception(f"asm: invalid args: {args}")
+ case "set_graphics_mode":
+ match args:
+ case [ast.Constant(value)]:
+ if type(value) != int:
+ raise Exception(f"set_graphics_mode: invalid arg type: {type(value)}")
+
+ reg1 = self.alloc_reg()
+ reg2 = self.alloc_reg()
+ self.emit(f"ldi vt_gr_mode_addr {reg1}")
+ self.emit(f"ldr {reg1} {reg1}")
+ self.emit(f"ldi {value} {reg2}")
+ self.emit(f"str {reg2} {reg1}")
+ self.free_reg(reg1)
+ self.free_reg(reg2)
+
+ case _: raise Exception(f"set_graphics_mode: invalid args: {args}")
+ case "store":
+ if len(args) != 2:
+ raise Exception("Invalid number of arguments to store: " + str(len(args)))
+
+ self.eval_expr_and_spu(args[0])
+ self.eval_expr_and_spu(args[1])
+ arg1 = self.alloc_reg()
+ arg2 = self.alloc_reg()
+ self.emit(f"spo {arg2}")
+ self.emit(f"spo {arg1}")
+ self.emit(f"str {arg2} {arg1}")
+ self.free_reg(arg1)
+ self.free_reg(arg2)
+
+ def eval_int_constant_and_spu(self, value: int):
+ reg = self.alloc_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}")
+
+ self.emit(f"spu {reg}")
+ self.free_reg(reg)
+
+ def eval_expr_and_spu(self, expr: ast.expr):
+ match expr:
+ case ast.Constant(int(value)):
+ self.eval_int_constant_and_spu(value)
+ case ast.Constant(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)
+
+
+ def visit_Module(self, node: Module):
+ for stmt in node.body:
+ self.visit(stmt)
+
+ def visit_Expr(self, expr: Expr):
+ if self.call_depth == 0 and type(expr.value) != ast.Call:
+ self.emit("; NOP top-level expression")
+ return
+
+ match expr.value:
+ case ast.Call(func, args, keywords):
+ match func:
+ case ast.Attribute(ast.Name(id="atk16"), attr):
+ print("FOUND CAPTURED ATK16 CALL: " + attr)
+ self.emit_builtin_call(attr, args)
+ case ast.Name(name):
+ addr = self.const_bindings[name]
+ self.emit(f"csi {addr}")
+ case _:
+ raise NotImplementedError("Unhandled Call: " + str(func))
+ case _:
+ raise NotImplementedError("Unhandled Expr.value: " + str(expr.value))
+
+ def visit_AnnAssign(self, node: AnnAssign):
+ match node:
+ case ast.AnnAssign(
+ target=ast.Name(name),
+ annotation=ast.Attribute(
+ value=ast.Name(id="atk16"),
+ attr="ConstInt"),
+ value=ast.Constant(value) # TODO: constant folding
+ ):
+ if type(value) == int:
+ self.assign_const(name, value)
+ return
+
+ raise NotImplementedError("Unhandled AnnAssign:\n" + ast.dump(node, indent=4))
+
+class Optimizer:
+ def __init__(self):
+ pass
+
+ def optimize(self, asm_str: str):
+ asm = asm_str.split("\n")
+ asm = [row.strip() for row in asm]
+ asm = [self.strip_comment(row) for row in asm]
+ asm = [row for row in asm if not len(row) == 0]
+ asm = [row.split() for row in asm]
+ asm = self.compact_spu_spo_pattern(asm)
+ asm = self.compact_load_mov_pattern(asm)
+ asm = self.compact_load_mov_pattern(asm)
+ asm = self.compact_spu_load_spo_pattern(asm)
+ result = "\n".join([format_asm_row(" ".join(row)) for row in asm])
+
+ return result
+
+ def strip_comment(self, row: str):
+ ret: str = ""
+ for c in row:
+ if c == ";": break
+ ret += c
+
+ return ret
+
+ def compact_spu_spo_pattern(self, asm: list[list[str]]) -> list[list[str]]:
+ i = 0
+ result: list[list[str]] = []
+ while i < len(asm):
+ current = asm[i]
+ next = asm[i + 1] if i + 1 < len(asm) else None
+ i += 1
+ if current[0].startswith("@") or next is None:
+ result.append(current)
+ continue
+
+ if current[0] == "spu" and next[0] == "spo":
+ arg_current = current[1]
+ arg_next = next[1]
+
+ if arg_current == arg_next:
+ pass # remove both spu and spo
+ else:
+ mov = ["mov", arg_current, arg_next]
+ result.append(mov)
+
+ i += 1
+ continue
+
+ result.append(current)
+
+ return result
+
+ def compact_load_mov_pattern(self, asm: list[list[str]]) -> list[list[str]]:
+ i = 0
+ result: list[list[str]] = []
+ while i < len(asm):
+ current = asm[i]
+ next = asm[i + 1] if i + 1 < len(asm) else None
+ i += 1
+ if current[0].startswith("@") or next is None:
+ result.append(current)
+ continue
+
+ if (current[0] == "ldi" or current[0] == "ldr") and next[0] == "mov":
+ load_op, load_from, load_target_reg = current[0], current[1], current[2]
+ mov_from_reg, mov_to_reg = next[1], next[2]
+
+ if load_target_reg == mov_from_reg:
+ ret = [load_op, load_from, mov_to_reg]
+ result.append(ret)
+ i += 1
+ continue
+
+ result.append(current)
+
+ return result
+
+ def compact_spu_load_spo_pattern(self, asm: list[list[str]]) -> list[list[str]]:
+ # spu RA
+ # ldi int_7 RA
+ # ldr RA RB
+ # spo RA
+ # OR
+ # spu RA
+ # ldi 3 RB
+ # spo RA
+
+ i = 0
+ result: list[list[str]] = []
+ while i < len(asm):
+ instr0 = asm[i]
+ instr1 = asm[i + 1] if i + 1 < len(asm) else None
+ instr2 = asm[i + 2] if i + 2 < len(asm) else None
+ instr3 = asm[i + 3] if i + 3 < len(asm) else None
+ i += 1
+ if instr0[0].startswith("@") or instr1 is None or instr2 is None or instr3 is None:
+ result.append(instr0)
+ continue
+
+ if instr0[0] == "spu" and instr1[0] == "ldi" and instr2[0] == "ldr" and instr3[0] == "spo":
+ spu_op, spu_reg = instr0
+ ldi_op, ldi_imm, ldi_target_reg = instr1
+ ldr_op, ldr_from_reg, ldr_to_reg = instr2
+ spo_op, spo_reg = instr3
+
+ if spu_reg == spo_reg and ldi_target_reg == ldr_from_reg and ldr_from_reg != ldr_to_reg:
+ ret0 = f"ldi {ldi_imm} {ldr_to_reg}".split()
+ ret1 = f"ldr {ldr_to_reg} {ldr_to_reg}".split()
+ result.append(ret0)
+ result.append(ret1)
+ i += 3
+ continue
+
+ elif instr0[0] == "spu" and instr1[0] == "ldi" and instr2[0] == "spo":
+ spu_op, spu_reg = instr0
+ ldi_op, ldi_imm, ldi_target_reg = instr1
+ spo_op, spo_reg = instr2
+
+ if spu_reg == spo_reg and ldi_target_reg != spu_reg:
+ ret0 = f"ldi {ldi_imm} {ldi_target_reg}".split()
+ result.append(ret0)
+ i += 2
+ continue
+
+ result.append(instr0)
+
+ 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,
+)
+
+optimizer = Optimizer()
+asm_out_optimized = optimizer.optimize(asm_out)
+
+with open(outfile_path, "w") as f:
+ f.write(asm_out)
+
+with open(f"{outfile_path}_optimized", "w") as f:
+ f.write(asm_out_optimized)
diff --git a/src/compiler.py b/src/bytecode_compiler.py
index d08d8db..2a5bc82 100644
--- a/src/compiler.py
+++ b/src/bytecode_compiler.py
@@ -85,6 +85,11 @@ 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")