more optimizations

This commit is contained in:
Irmen de Jong 2018-01-28 21:58:16 +01:00
parent 197a4e503e
commit 2a662ba256
7 changed files with 267 additions and 111 deletions

View File

@ -192,10 +192,12 @@ class PlyParser:
self.handle_internal_error(x, "process_expressions of node {}".format(node)) self.handle_internal_error(x, "process_expressions of node {}".format(node))
elif isinstance(node, IncrDecr) and node.howmuch not in (0, 1): 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) _, node.howmuch = coerce_constant_value(datatype_of(node.target, node.my_scope()), node.howmuch, node.sourceref)
attr.validate(node)
elif isinstance(node, VarDef): elif isinstance(node, VarDef):
dtype = DataType.WORD if node.vartype == VarType.MEMORY else node.datatype dtype = DataType.WORD if node.vartype == VarType.MEMORY else node.datatype
try: try:
_, node.value = coerce_constant_value(dtype, node.value, node.sourceref) # type: ignore _, node.value = coerce_constant_value(dtype, node.value, node.sourceref) # type: ignore
attr.validate(node)
except OverflowError as x: except OverflowError as x:
raise ParseError(str(x), node.sourceref) from None raise ParseError(str(x), node.sourceref) from None
elif isinstance(node, Assignment): elif isinstance(node, Assignment):

View File

@ -13,33 +13,33 @@ from ..compile import Zeropage
def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None: def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None:
pass
out("\v\t\t\t; " + stmt.lineref) out("\v\t\t\t; " + stmt.lineref)
out("\v; @todo assignment") out("\v; @todo assignment")
# @todo assignment # @todo assignment
def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> None: 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 # left: Register, SymbolName, or Dereference. right: Expression/LiteralValue
out("\v\t\t\t; " + stmt.lineref) out("\v\t\t\t; " + stmt.lineref)
out("\v; @todo aug-assignment")
lvalue = stmt.left lvalue = stmt.left
rvalue = stmt.right rvalue = stmt.right
if isinstance(lvalue, Register): if isinstance(lvalue, Register):
if isinstance(rvalue, LiteralValue): if isinstance(rvalue, LiteralValue):
if type(rvalue.value) is int: 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) _generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope)
else: else:
raise CodeError("incr/decr value should be 1..255", rvalue) raise CodeError("assignment value must be 0..255", rvalue)
else: else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, SymbolName): elif isinstance(rvalue, SymbolName):
symdef = scope.lookup(rvalue.name) symdef = scope.lookup(rvalue.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger(): if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger():
# @todo check value range if 0 <= symdef.value.const_num_val() <= 255:
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope) _generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope)
else:
raise CodeError("assignment value must be 0..255", rvalue)
else: else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, Register): 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: 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) r_str = rname or to_hex(rvalue)
if operator == "+=": if operator == "+=":
if lvalue.register == "A": if lvalue.name == "A":
out("\vclc") out("\vclc")
out("\vadc #" + r_str) out("\vadc #" + r_str)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vclc") out("\vclc")
out("\vadc #" + r_str) out("\vadc #" + r_str)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vclc") out("\vclc")
@ -73,16 +73,16 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=": elif operator == "-=":
if lvalue.register == "A": if lvalue.name == "A":
out("\vsec") out("\vsec")
out("\vsbc #" + r_str) out("\vsbc #" + r_str)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vsec") out("\vsec")
out("\vsbc #" + r_str) out("\vsbc #" + r_str)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vsec") out("\vsec")
@ -91,14 +91,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=": elif operator == "&=":
if lvalue.register == "A": if lvalue.name == "A":
out("\vand #" + r_str) out("\vand #" + r_str)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vand #" + r_str) out("\vand #" + r_str)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vand #" + r_str) out("\vand #" + r_str)
@ -106,14 +106,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=": elif operator == "|=":
if lvalue.register == "A": if lvalue.name == "A":
out("\vora #" + r_str) out("\vora #" + r_str)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vora #" + r_str) out("\vora #" + r_str)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vora #" + r_str) out("\vora #" + r_str)
@ -121,14 +121,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=": elif operator == "^=":
if lvalue.register == "A": if lvalue.name == "A":
out("\veor #" + r_str) out("\veor #" + r_str)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\veor #" + r_str) out("\veor #" + r_str)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\veor #" + r_str) out("\veor #" + r_str)
@ -143,14 +143,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
for _ in range(min(8, times)): for _ in range(min(8, times)):
out("\vlsr a") out("\vlsr a")
if lvalue.register == "A": if lvalue.name == "A":
shifts_A(rvalue.value) shifts_A(rvalue.value)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
shifts_A(rvalue.value) shifts_A(rvalue.value)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
shifts_A(rvalue.value) shifts_A(rvalue.value)
@ -165,14 +165,14 @@ def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: st
else: else:
for _ in range(min(8, times)): for _ in range(min(8, times)):
out("\vasl a") out("\vasl a")
if lvalue.register == "A": if lvalue.name == "A":
shifts_A(rvalue.value) shifts_A(rvalue.value)
elif lvalue.register == "X": elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
shifts_A(rvalue.value) shifts_A(rvalue.value)
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
shifts_A(rvalue.value) 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: def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue: Register, scope: Scope) -> None:
if operator == "+=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vclc") out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vclc") out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vclc") out("\vclc")
@ -206,21 +206,21 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vsec") out("\vsec")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vsec") out("\vsec")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vsec") out("\vsec")
@ -229,19 +229,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vand " + to_hex(Zeropage.SCRATCH_B1)) out("\vand " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vand " + to_hex(Zeropage.SCRATCH_B1)) out("\vand " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vand " + to_hex(Zeropage.SCRATCH_B1)) out("\vand " + to_hex(Zeropage.SCRATCH_B1))
@ -249,19 +249,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vora " + to_hex(Zeropage.SCRATCH_B1)) out("\vora " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vora " + to_hex(Zeropage.SCRATCH_B1)) out("\vora " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vora " + to_hex(Zeropage.SCRATCH_B1)) out("\vora " + to_hex(Zeropage.SCRATCH_B1))
@ -269,19 +269,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\veor " + to_hex(Zeropage.SCRATCH_B1)) out("\veor " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\veor " + to_hex(Zeropage.SCRATCH_B1)) out("\veor " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\veor " + to_hex(Zeropage.SCRATCH_B1)) out("\veor " + to_hex(Zeropage.SCRATCH_B1))
@ -289,19 +289,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
@ -309,19 +309,19 @@ def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue
else: else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=": 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 raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
if lvalue.register == "A": if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X": elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtxa") out("\vtxa")
out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax") out("\vtax")
elif lvalue.register == "Y": elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out): with preserving_registers({'A'}, scope, out):
out("\vtya") out("\vtya")
out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) out("\vasl " + to_hex(Zeropage.SCRATCH_B1))

View File

@ -186,19 +186,19 @@ class AssemblyGenerator:
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_A) assignment.nodes.append(stmt.value_A)
generate_assignment(out, assignment) generate_assignment(out, assignment, scope)
if stmt.value_X: if stmt.value_X:
reg = Register(name="X", sourceref=stmt.sourceref) reg = Register(name="X", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_X) assignment.nodes.append(stmt.value_X)
generate_assignment(out, assignment) generate_assignment(out, assignment, scope)
if stmt.value_Y: if stmt.value_Y:
reg = Register(name="Y", sourceref=stmt.sourceref) reg = Register(name="Y", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref) assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref)) assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_Y) assignment.nodes.append(stmt.value_Y)
generate_assignment(out, assignment) generate_assignment(out, assignment, scope)
out("\vrts") out("\vrts")
elif isinstance(stmt, InlineAssembly): elif isinstance(stmt, InlineAssembly):
out("\n\v; inline asm, " + stmt.lineref) out("\n\v; inline asm, " + stmt.lineref)

View File

@ -1,7 +1,7 @@
""" """
Programming Language for 6502/6510 microprocessors, codename 'Sick' Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the code generator for the in-place incr and decr instructions. 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. 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 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: def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0 assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0
assert stmt.operator in ("++", "--") assert stmt.operator in ("++", "--")
if stmt.howmuch == 0:
return
target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef
if isinstance(target, SymbolName): if isinstance(target, SymbolName):
symdef = scope.lookup(target.name) symdef = scope.lookup(target.name)

View File

@ -6,7 +6,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
""" """
from collections import defaultdict 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 ..plyparse import Block, VarType, VarDef, LiteralValue
from ..datatypes import DataType, STRING_DATATYPES from ..datatypes import DataType, STRING_DATATYPES
from . import to_hex, to_mflpt5, CodeError 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: if vardef.datatype == DataType.FLOAT:
out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value))) out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value)))
elif vardef.datatype in (DataType.BYTE, DataType.WORD): 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))) out("\v{:s} = {:s}".format(vardef.name, _numeric_value_str(vardef.value, True)))
elif vardef.datatype.isstring(): elif vardef.datatype.isstring():
# a const string is just a string variable in the generated assembly # 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") out("; memory mapped variables")
for vardef in vars_by_vartype.get(VarType.MEMORY, []): for vardef in vars_by_vartype.get(VarType.MEMORY, []):
# create a definition for variables at a specific place in memory (memory-mapped) # 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(): if vardef.datatype.isnumeric():
assert vardef.size == [1] 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: elif vardef.datatype == DataType.BYTEARRAY:
assert len(vardef.size) == 1 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: elif vardef.datatype == DataType.WORDARRAY:
assert len(vardef.size) == 1 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: elif vardef.datatype == DataType.MATRIX:
assert len(vardef.size) in (2, 3) assert len(vardef.size) in (2, 3)
if len(vardef.size) == 2: 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: elif len(vardef.size) == 3:
comment = "matrix of {:d} by {:d}, interleave {:d}".format(vardef.size[0], vardef.size[1], vardef.size[2]) comment = "matrix of {:d} by {:d}, interleave {:d}".format(vardef.size[0], vardef.size[1], vardef.size[2])
else: else:
raise CodeError("matrix size should be 2 or 3 numbers") 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)) out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) # type: ignore
else: else:
raise CodeError("invalid var type") raise CodeError("invalid var type")
out("; normal variables - initial values will be set by init code") 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("") out("")
@no_type_check
def _generate_string_var(out: Callable, vardef: VarDef) -> None: def _generate_string_var(out: Callable, vardef: VarDef) -> None:
if vardef.datatype == DataType.STRING: if vardef.datatype == DataType.STRING:
# 0-terminated string # 0-terminated string

View File

@ -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 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,\ from .plyparse import AstNode, Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope, Register datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope, Register, SymbolName, \
from .plylex import print_warning, print_bold Dereference, TargetRegisters, VarDef
from .plylex import print_warning, print_bold, SourceRef
from .datatypes import DataType
class Optimizer: class Optimizer:
@ -19,10 +21,13 @@ class Optimizer:
def optimize(self) -> None: def optimize(self) -> None:
self.num_warnings = 0 self.num_warnings = 0
self.create_aug_assignments()
self.optimize_assignments() self.optimize_assignments()
self.remove_superfluous_assignments() self.remove_superfluous_assignments()
self.combine_assignments_into_multi() self.combine_assignments_into_multi()
self.optimize_multiassigns() 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.remove_unused_subroutines()
self.optimize_goto_compare_with_zero() self.optimize_goto_compare_with_zero()
self.join_incrdecrs() self.join_incrdecrs()
@ -30,8 +35,106 @@ class Optimizer:
self.remove_empty_blocks() self.remove_empty_blocks()
def join_incrdecrs(self) -> None: def join_incrdecrs(self) -> None:
# @todo joins multiple incr/decr of same var into one (if value stays < 256 which ...) for scope in self.module.all_nodes(Scope):
pass 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: def remove_superfluous_assignments(self) -> None:
# remove consecutive assignment statements to the same target, only keep the last value (only if its a constant!) # 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: def optimize_assignments(self) -> None:
# remove assignment statements that do nothing (A=A) # 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) # 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 # 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?) # @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(): for assignment in self.module.all_nodes():
if isinstance(assignment, Assignment): if isinstance(assignment, Assignment):
if any(lv != assignment.right for lv in assignment.left.nodes): if all(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:
assignment.my_scope().remove_node(assignment) assignment.my_scope().remove_node(assignment)
self.num_warnings += 1 self.num_warnings += 1
print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
@ -110,6 +211,23 @@ class Optimizer:
new_assignment.nodes.append(value) new_assignment.nodes.append(value)
return new_assignment 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: def combine_assignments_into_multi(self) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment # fold multiple consecutive assignments with the same rvalue into one multi-assignment
for scope in self.module.all_nodes(Scope): for scope in self.module.all_nodes(Scope):

View File

@ -1,24 +1,57 @@
~ main { ~ main {
var .float flt
start: start:
XY=0
XY+=255
XY+=256
XY+=257
XY-=255
XY-=256
XY-=257
Y+=10
Y-=5
Y-=8
Y--
XY++ flt+=2
XY+=1 flt+=2
XY+=1 ; @todo? (optimize) join with previous flt+=2
XY+=0 ; is removed. 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. A+=0 ; is removed.
Y+=0 ; is removed. Y+=0 ; is removed.
XY+=1 ; @todo? (optimize) join with previous X+=1 ; @todo? (optimize) join with previous
XY+=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 return 44
} }