From 7218c1768917d570fad9e530701dd474612b15b0 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 13 Jan 2018 14:17:18 +0100 Subject: [PATCH] var inits --- .gitignore | 1 + il65/compile.py | 35 ++++++++++--- il65/datatypes.py | 7 --- il65/generateasm.py | 110 ++++++++++++++++++++++++++--------------- il65/lib/il65lib.ill | 83 +++++++++++-------------------- il65/lib/restorezp.asm | 43 ++++++++++++++++ il65/plyparse.py | 14 ++++-- reference.md | 3 +- todo.ill | 33 ++++++++++++- 9 files changed, 213 insertions(+), 116 deletions(-) create mode 100644 il65/lib/restorezp.asm diff --git a/.gitignore b/.gitignore index 2f0bbc865..95617e58a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ __pycache__/ parser.out parsetab.py +!/il65/lib/* diff --git a/il65/compile.py b/il65/compile.py index 2aec33175..4df3a8da1 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -16,7 +16,7 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti SymbolName, Dereference, AddressOf from .plylex import SourceRef, print_bold from .optimize import optimize -from .datatypes import DataType, datatype_sizes +from .datatypes import DataType, STRING_DATATYPES class CompileError(Exception): @@ -86,7 +86,7 @@ class PlyParser: zeropage = Zeropage(module.zp_options) for vardef in zpnode.scope.filter_nodes(VarDef): try: - vardef.zp_address = zeropage.allocate(vardef.name, vardef.datatype) + vardef.zp_address = zeropage.allocate(vardef) except CompileError as x: raise ParseError(str(x), vardef.sourceref) @@ -108,7 +108,6 @@ class PlyParser: except Exception as x: self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name)) - @no_type_check def create_multiassigns(self, module: Module) -> None: # create multi-assign statements from nested assignments (A=B=C=5), # and optimize TargetRegisters down to single Register if it's just one register. @@ -120,7 +119,7 @@ class PlyParser: return assign for block, parent in module.all_scopes(): - for node in block.nodes: + for node in block.nodes: # type: ignore if isinstance(node, Assignment): if isinstance(node.right, Assignment): multi = reduce_right(node) @@ -428,8 +427,8 @@ class Zeropage: assert self.SCRATCH_W1 not in self.free assert self.SCRATCH_W2 not in self.free - def allocate(self, name: str, datatype: DataType) -> int: - assert not name or name not in {a[0] for a in self.allocations.values()}, "var name is not unique" + def allocate(self, vardef: VarDef) -> int: + assert not vardef.name or vardef.name not in {a[0] for a in self.allocations.values()}, "var name is not unique" def sequential_free(location: int) -> bool: return all(location + i in self.free for i in range(size)) @@ -440,10 +439,30 @@ class Zeropage: def make_allocation(location: int) -> int: for loc in range(location, location + size): self.free.remove(loc) - self.allocations[location] = (name or "", datatype) + self.allocations[location] = (vardef.name or "", vardef.datatype) return location - size = datatype_sizes[datatype] + if vardef.datatype == DataType.BYTE: + size = 1 + elif vardef.datatype == DataType.WORD: + size = 2 + elif vardef.datatype == DataType.FLOAT: + print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref)) + size = 5 + elif vardef.datatype == DataType.BYTEARRAY: + print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref)) + size = vardef.size[0] + elif vardef.datatype == DataType.WORDARRAY: + print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref)) + size = vardef.size[0] * 2 + elif vardef.datatype == DataType.MATRIX: + print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref)) + size = vardef.size[0] * vardef.size[1] + elif vardef.datatype in STRING_DATATYPES: + print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref)) + size = vardef.size[0] + else: + raise CompileError("cannot put datatype {:s} in ZP".format(vardef.datatype.name)) if len(self.free) > 0: if size == 1: for candidate in range(min(self.free), max(self.free)+1): diff --git a/il65/datatypes.py b/il65/datatypes.py index 410e90b30..611088d9f 100644 --- a/il65/datatypes.py +++ b/il65/datatypes.py @@ -47,13 +47,6 @@ class DataType(enum.Enum): return NotImplemented -datatype_sizes = { - DataType.BYTE: 1, - DataType.WORD: 2, - DataType.FLOAT: 5 -} - - STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS} REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"} diff --git a/il65/generateasm.py b/il65/generateasm.py index 5603d447b..72cdaf48c 100644 --- a/il65/generateasm.py +++ b/il65/generateasm.py @@ -5,6 +5,7 @@ This is the assembly code generator (from the parse tree) Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ +import os import re import subprocess import datetime @@ -13,7 +14,7 @@ from typing import Dict, TextIO, List, Any from .plylex import print_bold from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \ InlineAssembly, Return, Register, LiteralValue -from .datatypes import VarType, DataType, datatype_sizes, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES +from .datatypes import VarType, DataType, to_hex, to_mflpt5, STRING_DATATYPES class CodeError(Exception): @@ -89,9 +90,9 @@ class AssemblyGenerator: self.p("* = " + to_hex(self.module.address)) year = datetime.datetime.now().year self.p("\v.word (+), {:d}".format(year)) - self.p("\v.null $9e, format(' %d ', _il65_sysaddr), $3a, $8f, ' il65 by idj'") + self.p("\v.null $9e, format(' %d ', _il65_entrypoint), $3a, $8f, ' il65 by idj'") self.p("+\v.word 0") - self.p("_il65_sysaddr\v; assembly code starts here\n") + self.p("_il65_entrypoint\v; assembly code starts here\n") else: self.p("; ---- program without sys call ----") self.p("* = " + to_hex(self.module.address) + "\n") @@ -101,7 +102,7 @@ class AssemblyGenerator: def init_vars_and_start(self) -> None: if self.module.zp_options == ZpOptions.CLOBBER_RESTORE: - self.p("\vjsr il65_lib_zp.save_zeropage") + self.p("\vjsr _il65_save_zeropage") self.p("\v; initialize all blocks (reset vars)") if self.module.zeropage(): self.p("\vjsr ZP._il65_init_block") @@ -112,7 +113,12 @@ class AssemblyGenerator: if self.module.zp_options == ZpOptions.CLOBBER_RESTORE: self.p("\vjsr {:s}.start".format(self.module.main().label)) self.p("\vcld") - self.p("\vjmp il65_lib_zp.restore_zeropage") + self.p("\vjmp _il65_restore_zeropage\n") + # include the assembly code for the save/restore zeropage routines + zprestorefile = os.path.join(os.path.split(__file__)[0], "lib", "restorezp.asm") + with open(zprestorefile, "rU") as f: + for line in f.readlines(): + self.p(line.rstrip("\n")) else: self.p("\vjmp {:s}.start".format(self.module.main().label)) self.p("") @@ -215,41 +221,61 @@ class AssemblyGenerator: def generate_block_init(self, block: Block) -> None: # generate the block initializer # @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer) + + def _memset(varname: str, value: int, size: int) -> None: + value = value or 0 + self.p("\vlda #<" + varname) + self.p("\vsta il65_lib.SCRATCH_ZPWORD1") + self.p("\vlda #>" + varname) + self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1") + self.p("\vlda #" + to_hex(value)) + self.p("\vldx #" + to_hex(size)) + self.p("\vjsr il65_lib.memset") + + def _memsetw(varname: str, value: int, size: int) -> None: + value = value or 0 + self.p("\vlda #<" + varname) + self.p("\vsta il65_lib.SCRATCH_ZPWORD1") + self.p("\vlda #>" + varname) + self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1") + self.p("\vlda #<" + to_hex(value)) + self.p("\vldy #>" + to_hex(value)) + self.p("\vldx #" + to_hex(size)) + self.p("\vjsr il65_lib.memsetw") + self.p("_il65_init_block\v; (re)set vars to initial values") - self.p("\vlda #0\n\vldx #0") float_inits = {} string_inits = [] - prev_value = 0 - vardefs = [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR] - # @todo optimize init order (sort on value first to avoid needless register loads, etc) - for variable in vardefs: - vname = variable.name - vvalue = variable.value - if variable.datatype == DataType.BYTE: - if vvalue != prev_value: - self.p("\vlda #${:02x}".format(vvalue)) - prev_value = vvalue - self.p("\vsta {:s}".format(vname)) - elif variable.datatype == DataType.WORD: - if vvalue != prev_value: - self.p("\vlda #<${:04x}".format(vvalue)) - self.p("\vldx #>${:04x}".format(vvalue)) - prev_value = vvalue - self.p("\vsta {:s}".format(vname)) - self.p("\vstx {:s}+1".format(vname)) - elif variable.datatype == DataType.FLOAT: - fpbytes = to_mflpt5(vvalue) # type: ignore - float_inits[variable.name] = (vname, fpbytes, vvalue) - elif variable.datatype in STRING_DATATYPES: - string_inits.append(variable) - elif variable.datatype == DataType.BYTEARRAY: - pass # @todo init bytearray - elif variable.datatype == DataType.WORDARRAY: - pass # @todo init wordarray - elif variable.datatype == DataType.MATRIX: - pass # @todo init matrix - else: - raise CodeError("weird var datatype", variable.datatype) + prev_value_a, prev_value_x = None, None + vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]] + for vardef in block.scope.filter_nodes(VarDef): + if vardef.vartype == VarType.VAR: + vars_by_datatype[vardef.datatype].append(vardef) + for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value): + if bytevar.value != prev_value_a: + self.p("\vlda #${:02x}".format(bytevar.value)) + prev_value_a = bytevar.value + self.p("\vsta {:s}".format(bytevar.name)) + for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value): + v_hi, v_lo = divmod(wordvar.value, 256) + if v_hi != prev_value_a: + self.p("\vlda #${:02x}".format(v_hi)) + prev_value_a = v_hi + if v_lo != prev_value_x: + self.p("\vldx #${:02x}".format(v_lo)) + prev_value_x = v_lo + self.p("\vsta {:s}".format(wordvar.name)) + self.p("\vstx {:s}+1".format(wordvar.name)) + for floatvar in vars_by_datatype[DataType.FLOAT]: + fpbytes = to_mflpt5(floatvar.value) # type: ignore + float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value) + for arrayvar in vars_by_datatype[DataType.BYTEARRAY]: + _memset(arrayvar.name, arrayvar.value, arrayvar.size[0]) + for arrayvar in vars_by_datatype[DataType.WORDARRAY]: + _memsetw(arrayvar.name, arrayvar.value, arrayvar.size[0]) + for arrayvar in vars_by_datatype[DataType.MATRIX]: + _memset(arrayvar.name, arrayvar.value, arrayvar.size[0] * arrayvar.size[1]) + # @todo string datatype inits with 1 memcopy if float_inits: self.p("\vldx #4") self.p("-") @@ -258,8 +284,6 @@ class AssemblyGenerator: self.p("\vsta {:s},x".format(vname)) self.p("\vdex") self.p("\vbpl -") - if string_inits: - pass # @todo init string block (1 memcopy) self.p("\vrts\n") for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()): self.p("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue)) @@ -324,8 +348,12 @@ class AssemblyGenerator: # zeropage uses the zp_address we've allocated, instead of allocating memory here for vardef in vars_by_vartype.get(VarType.VAR, []): assert vardef.zp_address is not None - self.p("\v{:s} = {:s}\t; {:s} ({:d})".format(vardef.name, to_hex(vardef.zp_address), - vardef.datatype.name.lower(), datatype_sizes[vardef.datatype])) + if vardef.datatype in (DataType.WORDARRAY, DataType.BYTEARRAY, DataType.MATRIX): + size_str = "size " + str(vardef.size) + else: + size_str = "" + self.p("\v{:s} = {:s}\t; {:s} {:s}".format(vardef.name, to_hex(vardef.zp_address), + vardef.datatype.name.lower(), size_str)) else: # create definitions for the variables that takes up empty space and will be initialized at startup string_vars = [] diff --git a/il65/lib/il65lib.ill b/il65/lib/il65lib.ill index ba37b2b95..9da9baed0 100644 --- a/il65/lib/il65lib.ill +++ b/il65/lib/il65lib.ill @@ -1,58 +1,10 @@ -; IL65 internal library routines +; IL65 internal library routines - always included by the compiler ; ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 -; ; +; ; indent format: TABS, size=8 -~ il65_lib_zp { -; note: separate block so the 64tass assembler can remove this when no zp restore is required - - %asm { - -; ---- store the Zeropage in a backup area -save_zeropage - sei - ldx #2 -- lda $00,x - sta zp_backup,x - inx - bne - - cli - rts - -restore_zeropage - php - pha - txa - pha - sei - - lda $a0 ; save the current jiffy clock - sta zp_backup+$a0 - lda $a1 - sta zp_backup+$a1 - lda $a2 - sta zp_backup+$a2 - - ldx #2 -- lda zp_backup-2,x - sta $00,x - inx - bne - - cli - pla - tax - pla - plp - rts - -zp_backup .fill 256, 0 - - } -} - - ~ il65_lib { ; note: the following ZP scratch registers must be the same as in c64lib memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP @@ -120,7 +72,7 @@ memcopy16_up rts ; done -; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (0 = 256) +; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (1 to 256, 0 meaning 256) ; destination must not overlap, or be before start, then overlap is possible. ; clobbers A, X, Y @@ -128,12 +80,37 @@ memcopy sta SCRATCH_ZPWORD2 sty SCRATCH_ZPWORD2+1 ldy #0 -- lda (SCRATCH_ZPWORD1),y - sta (SCRATCH_ZPWORD2),y +- lda (SCRATCH_ZPWORD1), y + sta (SCRATCH_ZPWORD2), y iny dex bne - rts + +; fill memory from (SCRATCH_ZPWORD1) length X (1-256, 0=256) with value in A. +; clobbers X, Y +memset ldy #0 +- sta (SCRATCH_ZPWORD1), y + iny + dex + bne - + rts + +; fill memory from (SCRATCH_ZPWORD1) length X (1-256, 0=256) with word value in AY. +; clobbers A, X, Y +memsetw sty _mod_hi+1 ; self-modify + sta _mod_lo+1 ; self-modify + ldy #0 +_mod_lo lda #$00 ; self-modified + sta (SCRATCH_ZPWORD1), y + iny +_mod_hi lda #$00 ; self-modified + sta (SCRATCH_ZPWORD1), y + iny + dex + bne _mod_lo + rts + } } diff --git a/il65/lib/restorezp.asm b/il65/lib/restorezp.asm new file mode 100644 index 000000000..a960ba517 --- /dev/null +++ b/il65/lib/restorezp.asm @@ -0,0 +1,43 @@ +; backup/restore the zero page +; this is in a separate file so it can be omitted completely if it's not needed. + +_il65_save_zeropage + lda #%00101111 + sta _il65_zp_backup ; default value for $00 + lda #%00100111 + sta _il65_zp_backup+1 ; default value for $01 + ldx #2 +- lda $00,x + sta _il65_zp_backup,x + inx + bne - + rts + +_il65_restore_zeropage + php + pha + txa + pha + sei + + lda $a0 ; save the current jiffy clock + sta _il65_zp_backup+$a0 + lda $a1 + sta _il65_zp_backup+$a1 + lda $a2 + sta _il65_zp_backup+$a2 + + ldx #0 +- lda _il65_zp_backup,x + sta $00,x + inx + bne - + cli + pla + tax + pla + plp + rts + +_il65_zp_backup + .fill 256 diff --git a/il65/plyparse.py b/il65/plyparse.py index bf0d07143..67884bdc8 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -402,7 +402,7 @@ class InlineAssembly(AstNode): assembly = attr.ib(type=str) -@attr.s(cmp=False, repr=False, slots=True) +@attr.s(cmp=False, repr=True, slots=True) class VarDef(AstNode): name = attr.ib(type=str) vartype = attr.ib() @@ -430,6 +430,8 @@ class VarDef(AstNode): assert self.size is None self.size = self.datatype.dimensions or [1] self.datatype = self.datatype.to_enum() + if self.datatype in {DataType.BYTEARRAY, DataType.WORDARRAY, DataType.MATRIX} and sum(self.size) in (0, 1): + print("warning: {}: array/matrix with size 1, use normal byte/word instead for efficiency".format(self.sourceref)) if self.vartype == VarType.CONST and self.value is None: raise ParseError("constant value assignment is missing", attr.evolve(self.sourceref, column=self.sourceref.column+len(self.name))) @@ -446,7 +448,9 @@ class VarDef(AstNode): if self.vartype in (VarType.CONST, VarType.VAR): try: _, self.value = coerce_value(self.datatype, self.value, self.sourceref) - except (TypeError, OverflowError) as x: + except OverflowError as x: + raise ParseError(str(x), self.sourceref) from None + except TypeError as x: raise ParseError("processed expression vor vardef is not a constant value: " + str(x), self.sourceref) from None @@ -679,11 +683,11 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc else: raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref) elif isinstance(target, Dereference): # '[...](1,2,3)' - return None # XXX + raise NotImplementedError("dereferenced call") # XXX elif isinstance(target, int): # '64738()' - return None # XXX + raise NotImplementedError("immediate address call") # XXX else: - raise NotImplementedError("weird call target", target) # XXX + raise NotImplementedError("weird call target", target) else: raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref) elif not isinstance(expr, Expression): diff --git a/reference.md b/reference.md index a86cc7dbf..9d960d068 100644 --- a/reference.md +++ b/reference.md @@ -116,7 +116,8 @@ The normal IRQ routine in the C-64's kernal will read and write several location These locations will not be used by the compiler for zero page variables, so your variables will not interfere with the IRQ routine and vice versa. This is true for the normal zp mode but also -for the mode where the whole zp has been taken over. +for the mode where the whole zp has been taken over. So the normal IRQ vector is still +running when your program is entered, even when you use ``%zp clobber``. @todo: some global way (in ZP block) to promote certian other blocks/variables from that block or even diff --git a/todo.ill b/todo.ill index b3af776f0..6207da32e 100644 --- a/todo.ill +++ b/todo.ill @@ -1,4 +1,5 @@ %output basic +%zp clobber,restore ~ ZP { var zp1_1 = 200 @@ -15,6 +16,17 @@ var zp2_2 = 100 var .word zp2_3 = $55aa var .word zp2_4 = $66bb + var .word zp2_5 = $66bc + var .word zp2_6 = $66bd + var .word zp2_7 = $66be + var .word zp2_8 = $67be + var .word zp2_9 = $68be + var .word zp2_10 = $69be + var .word zp2_11 = $69be + var .array(4) array1 + var .wordarray(4) warray1 + var .matrix(3,3) matrix1 + var .text string = "bye" const .text zpc2_1 = "hello" const zpc2_2 = 0 } @@ -22,12 +34,31 @@ ~ main { - var .text hello_str = "hello there" var .float float1 = 3.14 var .float float2 = 3.14 var .float float3 = 3.14 var .float float4 = 3.14 var .float float5 = 3.14 + var .array(10) array1 + var .wordarray(10) warray1 + var .matrix(4,4) matrix1 + var b1 = 10 + var b2 = 20 + var b3 = 10 + var b4 = 20 + var b5 = 10 + var b6 = 20 + var b7 = 10 + var b8 = 30 + var b9 = 30 + var b10 = 40 + var b11 = 40 + var b12 = 30 + var b13 = 40 + var b14 = 0 + var b15 = 0 + var b16 = 0 + start: