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))
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):

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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

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
"""
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):

View File

@ -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
}