zero division checks and more optimizations

This commit is contained in:
Irmen de Jong 2018-01-24 00:41:50 +01:00
parent 6573368a69
commit 31c6186245
9 changed files with 368 additions and 559 deletions

View File

@ -102,7 +102,7 @@ class PlyParser:
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
raise ParseError("cannot modify a constant", target.sourceref)
elif isinstance(node, AugAssignment):
# the assignment target must be a variable
# the assignment target must not be a constant
if isinstance(node.left, SymbolName):
symdef = node.my_scope().lookup(node.left.name)
if isinstance(symdef, VarDef):
@ -111,6 +111,10 @@ class PlyParser:
elif symdef.datatype not in {DataType.BYTE, DataType.WORD, DataType.FLOAT}:
raise ParseError("cannot modify that datatype ({:s}) in this way"
.format(symdef.datatype.name.lower()), node.sourceref)
# check for divide by (constant) zero
if node.operator in ("/=", "//="):
if isinstance(node.right, LiteralValue) and node.right.value == 0:
raise ParseError("division by zero", node.right.sourceref)
previous_stmt = node
def check_subroutine_arguments(self, call: SubCall, subdef: Subroutine) -> None:

View File

@ -46,6 +46,9 @@ class DataType(enum.Enum):
def isnumeric(self) -> bool:
return self.value in (1, 2, 3)
def isinteger(self) -> bool:
return self.value in (1, 2)
def isarray(self) -> bool:
return self.value in (4, 5, 6)

View File

@ -6,18 +6,320 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from typing import Callable
from ..plyparse import AstNode, Scope, VarDef, Dereference, Register, TargetRegisters,\
LiteralValue, Assignment, AugAssignment
from ..plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef
from . import CodeError, preserving_registers, to_hex
from ..datatypes import REGISTER_BYTES, REGISTER_WORDS, VarType, DataType
from ..compile import Zeropage
def generate_assignment(out: Callable, stmt: Assignment) -> None:
assert stmt.right is not None
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
# 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(stmt.right, LiteralValue):
rvalue = stmt.right.value
# @todo
if isinstance(lvalue, Register):
if isinstance(rvalue, LiteralValue):
if type(rvalue.value) is int:
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope)
else:
raise CodeError("integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, SymbolName):
symdef = scope.lookup(rvalue.name)
if isinstance(symdef, VarDef) and symdef.datatype.isinteger():
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope)
else:
raise CodeError("integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, Register):
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, scope)
else:
# @todo Register += symbolname / dereference , _generate_aug_reg_mem?
raise CodeError("invalid rvalue for augmented assignment on register", rvalue)
else:
raise CodeError("augmented assignment only implemented for registers for now", stmt.sourceref) # XXX
def generate_aug_assignment(out: Callable, stmt: AugAssignment) -> None:
assert stmt.right is not None
pass # @todo
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":
out("\vclc")
out("\vadc #" + r_str)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc #" + r_str)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vclc")
out("\vadc #" + r_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
out("\vsec")
out("\vsbc #" + r_str)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vsec")
out("\vsbc #" + r_str)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vsec")
out("\vsbc #" + r_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
out("\vand #" + r_str)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vand #" + r_str)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vand #" + r_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
out("\vora #" + r_str)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vora #" + r_str)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vora #" + r_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
out("\veor #" + r_str)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\veor #" + r_str)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\veor #" + r_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
out("\vlda #0")
else:
for _ in range(min(8, times)):
out("\vlsr a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
shifts_A(rvalue.value)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
shifts_A(rvalue.value)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
out("\vlda #0")
else:
for _ in range(min(8, times)):
out("\vasl a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
shifts_A(rvalue.value)
out("\vtax")
elif lvalue.register == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
shifts_A(rvalue.value)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
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:
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)))
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)))
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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if rvalue.register 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)))
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)))
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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vsec")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if rvalue.register 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)))
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
out("\vst{:s} {:s}".format(rvalue.register.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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if rvalue.register 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)))
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
out("\vst{:s} {:s}".format(rvalue.register.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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if rvalue.register 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)))
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
out("\vst{:s} {:s}".format(rvalue.register.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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.register 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)))
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
out("\vst{:s} {:s}".format(rvalue.register.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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.register 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)))
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
out("\vst{:s} {:s}".format(rvalue.register.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)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word

View File

@ -211,9 +211,9 @@ class AssemblyGenerator:
elif isinstance(stmt, SubCall):
generate_subcall(out, stmt)
elif isinstance(stmt, Assignment):
generate_assignment(out, stmt)
generate_assignment(out, stmt, scope)
elif isinstance(stmt, AugAssignment):
generate_aug_assignment(out, stmt)
generate_aug_assignment(out, stmt, scope)
elif isinstance(stmt, Directive):
if stmt.name == "breakpoint":
# put a marker in the source so that we can generate a list of breakpoints later

View File

@ -1,203 +1,9 @@
# old deprecated code
# old deprecated code, in the process of moving this to the new emit/... modules
class CodeGenerator:
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
def generate_incr_or_decr(self, stmt: Union[InplaceIncrStmt, InplaceDecrStmt]) -> None:
assert stmt.value.constant
assert (stmt.value.value is None and stmt.value.name) or stmt.value.value > 0
if stmt.what.datatype != DataType.FLOAT and not stmt.value.name and stmt.value.value > 0xff:
raise CodeError("only supports integer incr/decr by up to 255 for now") # XXX
is_incr = isinstance(stmt, InplaceIncrStmt)
howmuch = stmt.value.value
value_str = stmt.value.name or str(howmuch)
if isinstance(stmt.what, RegisterValue):
reg = stmt.what.register
# note: these operations below are all checked to be ok
if is_incr:
if reg == 'A':
# a += 1..255
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
elif reg in REGISTER_BYTES:
if howmuch == 1:
# x/y += 1
self.p("\t\tin{:s}".format(reg.lower()))
else:
# x/y += 2..255
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX += 1..255
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tbcc +")
self.p("\t\tinx")
self.p("+")
elif reg == "AY":
# AY += 1..255
self.p("\t\tclc")
self.p("\t\tadc # " + value_str)
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+")
elif reg == "XY":
if howmuch == 1:
# XY += 1
self.p("\t\tinx")
self.p("\t\tbne +")
self.p("\t\tiny")
self.p("+")
else:
# XY += 2..255
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+")
else:
raise CodeError("invalid incr register: " + reg)
else:
if reg == 'A':
# a -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
elif reg in REGISTER_BYTES:
if howmuch == 1:
# x/y -= 1
self.p("\t\tde{:s}".format(reg.lower()))
else:
# x/y -= 2..255
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tbcs +")
self.p("\t\tdex")
self.p("+")
elif reg == "AY":
# AY -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+")
elif reg == "XY":
if howmuch == 1:
# XY -= 1
self.p("\t\tcpx #0")
self.p("\t\tbne +")
self.p("\t\tdey")
self.p("+\t\tdex")
else:
# XY -= 2..255
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+")
else:
raise CodeError("invalid decr register: " + reg)
elif isinstance(stmt.what, (MemMappedValue, IndirectValue)):
what = stmt.what
if isinstance(what, IndirectValue):
if isinstance(what.value, IntegerValue):
what_str = what.value.name or Parser.to_hex(what.value.value)
else:
raise CodeError("invalid incr indirect type", what.value)
else:
what_str = what.name or Parser.to_hex(what.address)
if what.datatype == DataType.BYTE:
if howmuch == 1:
self.p("\t\t{:s} {:s}".format("inc" if is_incr else "dec", what_str))
else:
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
if is_incr:
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
else:
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
elif what.datatype == DataType.WORD:
if howmuch == 1:
# mem.word +=/-= 1
if is_incr:
self.p("\t\tinc " + what_str)
self.p("\t\tbne +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
self.p("\t\tbne +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+\t\tdec " + what_str)
else:
# mem.word +=/-= 2..255
if is_incr:
with self.preserving_registers({'A'}):
self.p("\t\tclc")
self.p("\t\tlda " + what_str)
self.p("\t\tadc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcc +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
with self.preserving_registers({'A'}):
self.p("\t\tsec")
self.p("\t\tlda " + what_str)
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcs +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+")
elif what.datatype == DataType.FLOAT:
if howmuch == 1.0:
# special case for +/-1
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
if is_incr:
self.p("\t\tjsr c64flt.float_add_one")
else:
self.p("\t\tjsr c64flt.float_sub_one")
elif stmt.value.name:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + stmt.value.name)
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
self.p("\t\tlda #>" + stmt.value.name)
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
if is_incr:
self.p("\t\tjsr c64flt.float_add_SW1_to_XY")
else:
self.p("\t\tjsr c64flt.float_sub_SW1_from_XY")
else:
raise CodeError("incr/decr missing float constant definition")
else:
raise CodeError("cannot in/decrement memory of type " + str(what.datatype), howmuch)
else:
raise CodeError("cannot in/decrement " + str(stmt.what))
def generate_call(self, stmt: CallStmt) -> None:
self.p("\t\t\t\t\t; " + stmt.lineref)
if stmt.condition:
@ -623,22 +429,7 @@ class CodeGenerator:
branch_emitter(targetstr, False, False)
generate_result_assignments()
def generate_augmented_assignment(self, stmt: AugmentedAssignmentStmt) -> None:
# for instance: value += 3
lvalue = stmt.leftvalues[0]
rvalue = stmt.right
self.p("\t\t\t\t\t; " + stmt.lineref)
if isinstance(lvalue, RegisterValue):
if isinstance(rvalue, IntegerValue):
self._generate_aug_reg_int(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, RegisterValue):
self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, MemMappedValue):
self._generate_aug_reg_mem(lvalue, stmt.operator, rvalue)
else:
raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue))
else:
raise CodeError("augmented assignment only implemented for registers for now", str(rvalue)) # XXX
def _generate_aug_reg_mem(self, lvalue: RegisterValue, operator: str, rvalue: MemMappedValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.address)
@ -760,281 +551,7 @@ class CodeGenerator:
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
def _generate_aug_reg_int(self, lvalue: RegisterValue, operator: str, rvalue: IntegerValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.value)
if operator == "+=":
if lvalue.register == "A":
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
self.p("\t\tlda #0")
else:
for _ in range(min(8, times)):
self.p("\t\tlsr a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
self.p("\t\tlda #0")
else:
for _ in range(min(8, times)):
self.p("\t\tasl a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
def _generate_aug_reg_reg(self, lvalue: RegisterValue, operator: str, rvalue: RegisterValue) -> None:
if operator == "+=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
def generate_assignment(self, stmt: AssignmentStmt) -> None:
def unwrap_indirect(iv: IndirectValue) -> MemMappedValue:
@ -1256,58 +773,6 @@ class CodeGenerator:
else:
raise CodeError("invalid register " + lv.register)
@contextlib.contextmanager
def preserving_registers(self, registers: Set[str], loads_a_within: bool=False, always_preserve: bool=False):
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html
if not self.cur_block.preserve_registers and not always_preserve:
yield
return
if registers == {'A'}:
self.p("\t\tpha")
yield
self.p("\t\tpla")
elif registers:
if not loads_a_within:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
if 'A' in registers:
self.p("\t\tpha")
if 'X' in registers:
self.p("\t\ttxa\n\t\tpha")
if 'Y' in registers:
self.p("\t\ttya\n\t\tpha")
if not loads_a_within:
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
yield
if 'X' in registers and 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
else:
if 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
if 'X' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttax")
if 'A' in registers:
self.p("\t\tpla")
else:
yield
def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None:
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)

View File

@ -43,15 +43,26 @@ class Optimizer:
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
if isinstance(assignment, AugAssignment):
if isinstance(assignment.right, LiteralValue) and isinstance(assignment.right.value, (int, float)):
if assignment.right.value == 0 and assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
if assignment.right.value == 0:
if assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
self.num_warnings += 1
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
assignment.my_scope().remove_node(assignment)
elif assignment.operator == "*=":
self.num_warnings += 1
print_warning("{}: statement replaced by = 0".format(assignment.sourceref))
new_assignment = self._make_new_assignment(assignment, 0)
assignment.my_scope().replace_node(assignment, new_assignment)
elif assignment.operator == "**=":
self.num_warnings += 1
print_warning("{}: statement replaced by = 1".format(assignment.sourceref))
new_assignment = self._make_new_assignment(assignment, 1)
assignment.my_scope().replace_node(assignment, new_assignment)
if assignment.right.value >= 8 and assignment.operator in ("<<=", ">>="):
print("{}: shifting result is always zero".format(assignment.sourceref))
new_stmt = Assignment(sourceref=assignment.sourceref)
new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref))
new_stmt.nodes.append(0)
new_stmt.nodes.append(0) # XXX literalvalue?
assignment.my_scope().replace_node(assignment, new_stmt)
if assignment.operator in ("+=", "-=") and 0 < assignment.right.value < 256:
howmuch = assignment.right
@ -62,6 +73,22 @@ class Optimizer:
howmuch=howmuch.value, sourceref=assignment.sourceref)
new_stmt.target = assignment.left
assignment.my_scope().replace_node(assignment, new_stmt)
if assignment.operator in ("/=", "//=", "*=") and assignment.right.value == 1:
self.num_warnings += 1
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
assignment.my_scope().remove_node(assignment)
@no_type_check
def _make_new_assignment(self, old_aug_assignment: AugAssignment, constantvalue: int) -> Assignment:
new_assignment = Assignment(sourceref=old_aug_assignment.sourceref)
new_assignment.parent = old_aug_assignment.parent
left = AssignmentTargets(nodes=[old_aug_assignment.left], sourceref=old_aug_assignment.sourceref)
left.parent = new_assignment
new_assignment.nodes.append(left)
value = LiteralValue(value=constantvalue, sourceref=old_aug_assignment.sourceref)
value.parent = new_assignment
new_assignment.nodes.append(value)
return new_assignment
def combine_assignments_into_multi(self) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment

View File

@ -86,7 +86,7 @@ t_BITOR = r"\|"
t_BITXOR = r"\^"
t_BITINVERT = r"~"
t_IS = r"="
t_AUGASSIGN = r"\+=|-=|/=|\*=|<<=|>>=|&=|\|=|\^="
t_AUGASSIGN = r"\+=|-=|/=|//=|\*=|\*\*=|<<=|>>=|&=|\|=|\^="
t_DECR = r"--"
t_INCR = r"\+\+"
t_EQUALS = r"=="

View File

@ -482,6 +482,8 @@ class Expression(AstNode):
estr = "{} {} {}".format(repr(self.left.value), self.operator, repr(self.right.value))
try:
return LiteralValue(value=eval(estr, {}, {}), sourceref=sourceref) # type: ignore # safe because of checks above
except ZeroDivisionError:
raise ParseError("division by zero", sourceref)
except Exception as x:
raise ExpressionEvaluationError("expression error: " + str(x), self.sourceref) from None

View File

@ -38,8 +38,8 @@ start:
v3t++
v3t+=1
v3t+=1 ; @todo? (optimize) join with previous
v3t+=0
v3t+=1
v3t+=0 ; is removed.
v3t+=1 ; @todo? (optimize) join with previous
v3t+=1 ; @todo? (optimize) join with previous
v3t=2.23424 ; @todo store as constant float with generated name, replace value node
v3t=2.23411 ; @todo store as constant float with generated name, replace value node
@ -49,7 +49,13 @@ start:
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
v3t=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
XY*=2
XY*=3
XY=XY/0 ; @todo zerodiv (during expression to code generation)
XY=XY//0 ; @todo zerodiv (during expression to code generation)
XY*=2.23424 ; @todo store as constant float with generated name, replace value node
XY*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
;v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
; A++
; X--
; A+=1