diff --git a/il65/compile.py b/il65/compile.py index 5dbe1a65f..9d2fcb618 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -192,10 +192,12 @@ class PlyParser: self.handle_internal_error(x, "process_expressions of node {}".format(node)) elif isinstance(node, IncrDecr) and node.howmuch not in (0, 1): _, node.howmuch = coerce_constant_value(datatype_of(node.target, node.my_scope()), node.howmuch, node.sourceref) + attr.validate(node) elif isinstance(node, VarDef): dtype = DataType.WORD if node.vartype == VarType.MEMORY else node.datatype try: _, node.value = coerce_constant_value(dtype, node.value, node.sourceref) # type: ignore + attr.validate(node) except OverflowError as x: raise ParseError(str(x), node.sourceref) from None elif isinstance(node, Assignment): diff --git a/il65/emit/assignment.py b/il65/emit/assignment.py index ce5fd5dd9..2e781e3d9 100644 --- a/il65/emit/assignment.py +++ b/il65/emit/assignment.py @@ -13,33 +13,33 @@ from ..compile import Zeropage def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None: - pass out("\v\t\t\t; " + stmt.lineref) out("\v; @todo assignment") # @todo assignment def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> None: - # for instance: value += 3 (value = 1-255) + # for instance: value += 3 (value = 0-255 for now) # left: Register, SymbolName, or Dereference. right: Expression/LiteralValue out("\v\t\t\t; " + stmt.lineref) - out("\v; @todo aug-assignment") lvalue = stmt.left rvalue = stmt.right if isinstance(lvalue, Register): if isinstance(rvalue, LiteralValue): if type(rvalue.value) is int: - if 0 < rvalue.value < 256: + if 0 <= rvalue.value <= 255: _generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope) else: - raise CodeError("incr/decr value should be 1..255", rvalue) + raise CodeError("assignment value must be 0..255", rvalue) else: raise CodeError("constant integer literal or variable required for now", rvalue) # XXX elif isinstance(rvalue, SymbolName): symdef = scope.lookup(rvalue.name) if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger(): - # @todo check value range - _generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope) + if 0 <= symdef.value.const_num_val() <= 255: + _generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope) + else: + raise CodeError("assignment value must be 0..255", rvalue) else: raise CodeError("constant integer literal or variable required for now", rvalue) # XXX elif isinstance(rvalue, Register): @@ -55,16 +55,16 @@ def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: str, rvalue: int, rname: str, scope: Scope) -> None: r_str = rname or to_hex(rvalue) if operator == "+=": - if lvalue.register == "A": + if lvalue.name == "A": out("\vclc") out("\vadc #" + r_str) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vclc") out("\vadc #" + r_str) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") out("\vclc") @@ -73,16 +73,16 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word elif operator == "-=": - if lvalue.register == "A": + if lvalue.name == "A": out("\vsec") out("\vsbc #" + r_str) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vsec") out("\vsbc #" + r_str) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") out("\vsec") @@ -91,14 +91,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word elif operator == "&=": - if lvalue.register == "A": + if lvalue.name == "A": out("\vand #" + r_str) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vand #" + r_str) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") out("\vand #" + r_str) @@ -106,14 +106,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word elif operator == "|=": - if lvalue.register == "A": + if lvalue.name == "A": out("\vora #" + r_str) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vora #" + r_str) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") out("\vora #" + r_str) @@ -121,14 +121,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word elif operator == "^=": - if lvalue.register == "A": + if lvalue.name == "A": out("\veor #" + r_str) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") out("\veor #" + r_str) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") out("\veor #" + r_str) @@ -143,14 +143,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: for _ in range(min(8, times)): out("\vlsr a") - if lvalue.register == "A": + if lvalue.name == "A": shifts_A(rvalue.value) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") shifts_A(rvalue.value) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") shifts_A(rvalue.value) @@ -165,14 +165,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st else: for _ in range(min(8, times)): out("\vasl a") - if lvalue.register == "A": + if lvalue.name == "A": shifts_A(rvalue.value) - elif lvalue.register == "X": + elif lvalue.name == "X": with preserving_registers({'A'}, scope, out): out("\vtxa") shifts_A(rvalue.value) out("\vtax") - elif lvalue.register == "Y": + elif lvalue.name == "Y": with preserving_registers({'A'}, scope, out): out("\vtya") shifts_A(rvalue.value) @@ -183,21 +183,21 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue: Register, scope: Scope) -> None: if operator == "+=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vclc") out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vclc") out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vclc") @@ -206,21 +206,21 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word elif operator == "-=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vsec") out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vsec") out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vsec") @@ -229,19 +229,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word elif operator == "&=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vand " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vand " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vand " + to_hex(Zeropage.SCRATCH_B1)) @@ -249,19 +249,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word elif operator == "|=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vora " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vora " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vora " + to_hex(Zeropage.SCRATCH_B1)) @@ -269,19 +269,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word elif operator == "^=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\veor " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\veor " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\veor " + to_hex(Zeropage.SCRATCH_B1)) @@ -289,19 +289,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word elif operator == ">>=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) @@ -309,19 +309,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue else: raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word elif operator == "<<=": - if rvalue.register not in REGISTER_BYTES: + if rvalue.name not in REGISTER_BYTES: raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word - if lvalue.register == "A": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + if lvalue.name == "A": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "X": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtxa") out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) out("\vtax") - elif lvalue.register == "Y": - out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + elif lvalue.name == "Y": + out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1))) with preserving_registers({'A'}, scope, out): out("\vtya") out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) diff --git a/il65/emit/generate.py b/il65/emit/generate.py index be196c8ca..4482a22a2 100644 --- a/il65/emit/generate.py +++ b/il65/emit/generate.py @@ -186,19 +186,19 @@ class AssemblyGenerator: assignment = Assignment(sourceref=stmt.sourceref) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(stmt.value_A) - generate_assignment(out, assignment) + generate_assignment(out, assignment, scope) if stmt.value_X: reg = Register(name="X", sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(stmt.value_X) - generate_assignment(out, assignment) + generate_assignment(out, assignment, scope) if stmt.value_Y: reg = Register(name="Y", sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(stmt.value_Y) - generate_assignment(out, assignment) + generate_assignment(out, assignment, scope) out("\vrts") elif isinstance(stmt, InlineAssembly): out("\n\v; inline asm, " + stmt.lineref) diff --git a/il65/emit/incrdecr.py b/il65/emit/incrdecr.py index 05598eb8a..619e53c36 100644 --- a/il65/emit/incrdecr.py +++ b/il65/emit/incrdecr.py @@ -1,7 +1,7 @@ """ Programming Language for 6502/6510 microprocessors, codename 'Sick' This is the code generator for the in-place incr and decr instructions. -Incrementing or decrementing variables by 1 (or another constant amount) +Incrementing or decrementing variables by a small value 0..255 (for integers) is quite frequent and this generates assembly code tweaked for this case. Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 @@ -16,6 +16,8 @@ from . import CodeError, preserving_registers def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None: assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0 assert stmt.operator in ("++", "--") + if stmt.howmuch == 0: + return target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef if isinstance(target, SymbolName): symdef = scope.lookup(target.name) diff --git a/il65/emit/variables.py b/il65/emit/variables.py index 6d8e06551..d42295f1d 100644 --- a/il65/emit/variables.py +++ b/il65/emit/variables.py @@ -6,7 +6,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ from collections import defaultdict -from typing import Dict, List, Callable, Any +from typing import Dict, List, Callable, Any, no_type_check from ..plyparse import Block, VarType, VarDef, LiteralValue from ..datatypes import DataType, STRING_DATATYPES from . import to_hex, to_mflpt5, CodeError @@ -122,7 +122,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No if vardef.datatype == DataType.FLOAT: out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value))) elif vardef.datatype in (DataType.BYTE, DataType.WORD): - assert isinstance(vardef.value.value, int) + assert isinstance(vardef.value.value, int) # type: ignore out("\v{:s} = {:s}".format(vardef.name, _numeric_value_str(vardef.value, True))) elif vardef.datatype.isstring(): # a const string is just a string variable in the generated assembly @@ -132,16 +132,16 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No out("; memory mapped variables") for vardef in vars_by_vartype.get(VarType.MEMORY, []): # create a definition for variables at a specific place in memory (memory-mapped) - assert isinstance(vardef.value.value, int) + assert isinstance(vardef.value.value, int) # type: ignore if vardef.datatype.isnumeric(): assert vardef.size == [1] - out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower())) + out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower())) # type: ignore elif vardef.datatype == DataType.BYTEARRAY: assert len(vardef.size) == 1 - out("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) + out("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore elif vardef.datatype == DataType.WORDARRAY: assert len(vardef.size) == 1 - out("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) + out("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore elif vardef.datatype == DataType.MATRIX: assert len(vardef.size) in (2, 3) if len(vardef.size) == 2: @@ -149,8 +149,8 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No elif len(vardef.size) == 3: comment = "matrix of {:d} by {:d}, interleave {:d}".format(vardef.size[0], vardef.size[1], vardef.size[2]) else: - raise CodeError("matrix size should be 2 or 3 numbers") - out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) + raise CodeError("matrix size must be 2 or 3 numbers") + out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) # type: ignore else: raise CodeError("invalid var type") out("; normal variables - initial values will be set by init code") @@ -199,6 +199,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No out("") +@no_type_check def _generate_string_var(out: Callable, vardef: VarDef) -> None: if vardef.datatype == DataType.STRING: # 0-terminated string diff --git a/il65/optimize.py b/il65/optimize.py index d067677a0..2bb121955 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -6,10 +6,12 @@ eliminates statements that have no effect, optimizes calculations etc. Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ -from typing import List, no_type_check +from typing import List, no_type_check, Union from .plyparse import AstNode, Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\ - datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope, Register -from .plylex import print_warning, print_bold + datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope, Register, SymbolName, \ + Dereference, TargetRegisters, VarDef +from .plylex import print_warning, print_bold, SourceRef +from .datatypes import DataType class Optimizer: @@ -19,10 +21,13 @@ class Optimizer: def optimize(self) -> None: self.num_warnings = 0 + self.create_aug_assignments() self.optimize_assignments() self.remove_superfluous_assignments() self.combine_assignments_into_multi() self.optimize_multiassigns() + # @todo optimize some simple multiplications into shifts (A*=8 -> A<<3) + # @todo optimize addition with self into shift 1 (A+=A -> A<<=1) self.remove_unused_subroutines() self.optimize_goto_compare_with_zero() self.join_incrdecrs() @@ -30,8 +35,106 @@ class Optimizer: self.remove_empty_blocks() def join_incrdecrs(self) -> None: - # @todo joins multiple incr/decr of same var into one (if value stays < 256 which ...) - pass + for scope in self.module.all_nodes(Scope): + incrdecrs = [] # type: List[IncrDecr] + target = None + for node in list(scope.nodes): + if isinstance(node, IncrDecr): + if target is None: + target = node.target + incrdecrs.append(node) + continue + if self._same_target(target, node.target): + incrdecrs.append(node) + continue + if len(incrdecrs) > 1: + # optimize... + replaced = False + total = 0 + for i in incrdecrs: + if i.operator == "++": + total += i.howmuch + else: + total -= i.howmuch + if total == 0: + replaced = True + for x in incrdecrs: + scope.remove_node(x) + else: + is_float = False + if isinstance(target, SymbolName): + symdef = target.my_scope().lookup(target.name) + if isinstance(symdef, VarDef) and symdef.datatype == DataType.FLOAT: + is_float = True + elif isinstance(target, Dereference): + is_float = target.datatype == DataType.FLOAT + if is_float: + replaced = True + for x in incrdecrs[1:]: + scope.remove_node(x) + incrdecr = self._make_incrdecr(incrdecrs[0], target, abs(total), "++" if total >= 0 else "--") + scope.replace_node(incrdecrs[0], incrdecr) + elif 0 < total <= 255: + replaced = True + for x in incrdecrs[1:]: + scope.remove_node(x) + incrdecr = self._make_incrdecr(incrdecrs[0], target, total, "++") + scope.replace_node(incrdecrs[0], incrdecr) + elif -255 <= total < 0: + replaced = True + total = -total + for x in incrdecrs[1:]: + scope.remove_node(x) + incrdecr = self._make_incrdecr(incrdecrs[0], target, total, "--") + scope.replace_node(incrdecrs[0], incrdecr) + if replaced: + self.num_warnings += 1 + print_warning("{}: merged a sequence of incr/decrs or augmented assignments".format(incrdecrs[0].sourceref)) + incrdecrs.clear() + target = None + if isinstance(node, IncrDecr): + incrdecrs.append(node) + target = node.target + + def _same_target(self, node1: Union[TargetRegisters, Register, SymbolName, Dereference], + node2: Union[TargetRegisters, Register, SymbolName, Dereference]) -> bool: + if isinstance(node1, Register) and isinstance(node2, Register) and node1.name == node2.name: + return True + if isinstance(node1, SymbolName) and isinstance(node2, SymbolName) and node1.name == node2.name: + return True + if isinstance(node1, Dereference) and isinstance(node2, Dereference) and node1.operand == node2.operand: + return True + return False + + @no_type_check + def create_aug_assignments(self) -> None: + # create augmented assignments from regular assignment that only refers to the lvalue + # A=A+10, A=10+A -> A+=10, A=A*4, A=4*A -> A*=4, etc + for assignment in self.module.all_nodes(Assignment): + if len(assignment.left.nodes) > 1: + continue + if not isinstance(assignment.right, Expression) or assignment.right.unary: + continue + expr = assignment.right + if expr.operator in ('-', '/', '//', '**', '<<', '>>', '&'): # non-associative operators + if isinstance(expr.right, (LiteralValue, SymbolName)) and self._same_target(assignment.left.nodes[0], expr.left): + num_val = expr.right.const_num_val() + operator = expr.operator + '=' + aug_assign = self._make_aug_assign(assignment, assignment.left.nodes[0], num_val, operator) + assignment.my_scope().replace_node(assignment, aug_assign) + continue + if expr.operator not in ('+', '*', '|', '^'): # associative operators + continue + if isinstance(expr.right, (LiteralValue, SymbolName)) and self._same_target(assignment.left.nodes[0], expr.left): + num_val = expr.right.const_num_val() + operator = expr.operator + '=' + aug_assign = self._make_aug_assign(assignment, assignment.left.nodes[0], num_val, operator) + assignment.my_scope().replace_node(assignment, aug_assign) + elif isinstance(expr.left, (LiteralValue, SymbolName)) and self._same_target(assignment.left.nodes[0], expr.right): + num_val = expr.left.const_num_val() + operator = expr.operator + '=' + aug_assign = self._make_aug_assign(assignment, assignment.left.nodes[0], num_val, operator) + assignment.my_scope().replace_node(assignment, aug_assign) def remove_superfluous_assignments(self) -> None: # remove consecutive assignment statements to the same target, only keep the last value (only if its a constant!) @@ -49,15 +152,13 @@ class Optimizer: def optimize_assignments(self) -> None: # remove assignment statements that do nothing (A=A) - # and augmented assignments that have no effect (x+=0, x-=0, x/=1, x//=1, x*=1) + # remove augmented assignments that have no effect (x+=0, x-=0, x/=1, x//=1, x*=1) # convert augmented assignments to simple incr/decr if possible (A+=10 => A++ by 10) # simplify some calculations (x*=0, x**=0) to simple constant value assignment # @todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?) for assignment in self.module.all_nodes(): if isinstance(assignment, Assignment): - if any(lv != assignment.right for lv in assignment.left.nodes): - assignment.left.nodes = [lv for lv in assignment.left.nodes if lv != assignment.right] - if not assignment.left: + if all(lv == assignment.right for lv in assignment.left.nodes): assignment.my_scope().remove_node(assignment) self.num_warnings += 1 print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) @@ -110,6 +211,23 @@ class Optimizer: new_assignment.nodes.append(value) return new_assignment + @no_type_check + def _make_aug_assign(self, old_assign: Assignment, target: Union[TargetRegisters, Register, SymbolName, Dereference], + value: Union[int, float], operator: str) -> AugAssignment: + a = AugAssignment(operator=operator, sourceref=old_assign.sourceref) + a.nodes.append(target) + a.nodes.append(LiteralValue(value=value, sourceref=old_assign.sourceref)) + a.parent = old_assign.parent + return a + + @no_type_check + def _make_incrdecr(self, old_stmt: AstNode, target: Union[TargetRegisters, Register, SymbolName, Dereference], + howmuch: Union[int, float], operator: str) -> AugAssignment: + a = IncrDecr(operator=operator, howmuch=howmuch, sourceref=old_stmt.sourceref) + a.nodes.append(target) + a.parent = old_stmt.parent + return a + def combine_assignments_into_multi(self) -> None: # fold multiple consecutive assignments with the same rvalue into one multi-assignment for scope in self.module.all_nodes(Scope): diff --git a/todo.ill b/todo.ill index 997439910..78f04b1ab 100644 --- a/todo.ill +++ b/todo.ill @@ -1,24 +1,57 @@ ~ main { + var .float flt + start: - XY=0 - XY+=255 - XY+=256 - XY+=257 - XY-=255 - XY-=256 - XY-=257 + Y+=10 + Y-=5 + Y-=8 + Y-- - XY++ - XY+=1 - XY+=1 ; @todo? (optimize) join with previous - XY+=0 ; is removed. + flt+=2 + flt+=2 + flt+=2 + flt+=2 + flt+=2 + + X=0 + X+=5 + X=X+22 + X=33+X + X-=3 + X=X-4 + X=X-5 + X=8*X + X=24|X + X=X^66 + X+=250 + X+=5 + X-=100 + X-=50 + X-=5 + + Y=Y + X=X + A=A + + X++ + X-- + X+=1 + X+=1 ; @todo? (optimize) join with previous + X+=0 ; is removed. A+=0 ; is removed. Y+=0 ; is removed. - XY+=1 ; @todo? (optimize) join with previous - XY+=1 ; @todo? (optimize) join with previous + X+=1 ; @todo? (optimize) join with previous + X+=1 ; @todo? (optimize) join with previous + + ;@todo float incrdecr/augassign + ;flt += 0.1 + ;flt += 1.1 + ;flt += 10.1 + ;flt += 100.1 + ;flt += 1000.1 return 44 }