optimizer

This commit is contained in:
Irmen de Jong 2018-01-21 13:34:07 +01:00
parent eb58119b97
commit ab71a15007
3 changed files with 83 additions and 76 deletions

View File

@ -78,16 +78,16 @@ class PlyParser:
previous_stmt = None
for node in module.all_nodes():
if isinstance(node, Scope):
previous_stmt = None
if node.nodes and isinstance(node.parent, (Block, Subroutine)):
self._check_last_statement_is_return(node.nodes[-1])
elif isinstance(node, SubCall):
if isinstance(node.target, SymbolName):
subdef = node.my_scope().lookup(node.target.name)
self.check_subroutine_arguments(node, subdef) # type: ignore
if isinstance(subdef, Subroutine):
self.check_subroutine_arguments(node, subdef)
elif isinstance(node, Subroutine):
# the previous statement (if any) must be a Goto or Return
if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)):
if not isinstance(previous_stmt, (Scope, Goto, Return, VarDef, Subroutine)):
raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", node.sourceref)
elif isinstance(node, IncrDecr):
if isinstance(node.target, SymbolName):
@ -172,7 +172,7 @@ class PlyParser:
lvalue_types = set(datatype_of(lv, node.my_scope()) for lv in node.left.nodes)
if len(lvalue_types) == 1:
_, newright = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref)
if isinstance(newright, (LiteralValue, Expression)):
if isinstance(newright, (Register, LiteralValue, Expression)):
node.right = newright
else:
raise TypeError("invalid coerced constant type", newright)
@ -187,14 +187,14 @@ class PlyParser:
def reduce_right(assign: Assignment) -> Assignment:
if isinstance(assign.right, Assignment):
right = reduce_right(assign.right)
assign.left.extend(right.left)
assign.left.nodes.extend(right.left.nodes)
assign.right = right.right
return assign
for node in module.all_nodes(Assignment):
if isinstance(node.right, Assignment):
multi = reduce_right(node)
assert multi is node and len(multi.left) > 1 and not isinstance(multi.right, Assignment)
assert multi is node and len(multi.left.nodes) > 1 and not isinstance(multi.right, Assignment)
@no_type_check
def apply_directive_options(self, module: Module) -> None:
@ -324,9 +324,8 @@ class PlyParser:
@no_type_check
def _get_subroutine_usages_from_goto(self, usages: Dict[Tuple[str, str], Set[str]],
goto: Goto, parent_scope: Scope) -> None:
target = goto.target.target
if isinstance(target, SymbolName):
usages[(parent_scope.name, target.name)].add(str(goto.sourceref))
if isinstance(goto.target, SymbolName):
usages[(parent_scope.name, goto.target.name)].add(str(goto.sourceref))
self._get_subroutine_usages_from_expression(usages, goto.condition, parent_scope)
def _get_subroutine_usages_from_return(self, usages: Dict[Tuple[str, str], Set[str]],

View File

@ -5,8 +5,9 @@ This is the optimizer that applies various optimizations to the parse tree.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from typing import List, no_type_check
from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue
datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope
from .plylex import print_warning, print_bold
@ -18,14 +19,13 @@ class Optimizer:
def optimize(self) -> None:
self.num_warnings = 0
self.optimize_assignments()
return # XXX fix all methods below
#self.combine_assignments_into_multi()
#self.optimize_multiassigns()
#self.remove_unused_subroutines()
#self.optimize_compare_with_zero()
self.combine_assignments_into_multi()
self.optimize_multiassigns()
self.remove_unused_subroutines()
self.optimize_goto_compare_with_zero()
# @todo join multiple incr/decr of same var into one (if value stays < 256)
# @todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
#self.remove_empty_blocks()
self.remove_empty_blocks()
def optimize_assignments(self) -> None:
# remove assignment statements that do nothing (A=A)
@ -64,10 +64,10 @@ class Optimizer:
def combine_assignments_into_multi(self) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
for block, parent in self.module.all_scopes():
for scope in self.module.all_nodes(Scope):
rvalue = None
assignments = []
for stmt in list(block.nodes):
assignments = [] # type: List[Assignment]
for stmt in list(scope.nodes):
if isinstance(stmt, Assignment):
if assignments:
if stmt.right == rvalue:
@ -77,8 +77,8 @@ class Optimizer:
# replace the first assignment by a multi-assign with all the others
for assignment in assignments[1:]:
print("{}: joined with previous assignment".format(assignment.sourceref))
assignments[0].left.extend(assignment.left)
block.scope.remove_node(assignment)
assignments[0].left.nodes.extend(assignment.left.nodes)
scope.remove_node(assignment)
rvalue = None
assignments.clear()
else:
@ -88,18 +88,19 @@ class Optimizer:
rvalue = None
assignments.clear()
@no_type_check
def optimize_multiassigns(self) -> None:
# optimize multi-assign statements (remove duplicate targets, optimize order)
for block, parent in self.module.all_scopes():
for assignment in block.nodes:
if isinstance(assignment, Assignment) and len(assignment.left) > 1:
# remove duplicates
lvalues = set(assignment.left)
if len(lvalues) != len(assignment.left):
print("{}: removed duplicate assignment targets".format(assignment.sourceref))
# @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
assignment.left = list(lvalues)
for assignment in self.module.all_nodes(Assignment):
if len(assignment.left.nodes) > 1:
# remove duplicates
lvalues = set(assignment.left.nodes)
if len(lvalues) != len(assignment.left.nodes):
print("{}: removed duplicate assignment targets".format(assignment.sourceref))
# @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
assignment.left.nodes = list(lvalues)
@no_type_check
def remove_unused_subroutines(self) -> None:
# some symbols are used by the emitted assembly code from the code generator,
# and should never be removed or the assembler will fail
@ -107,47 +108,45 @@ class Optimizer:
"c64flt.GIVUAYF", "c64flt.copy_mflt", "c64flt.float_add_one", "c64flt.float_sub_one",
"c64flt.float_add_SW1_to_XY", "c64flt.float_sub_SW1_from_XY"}
num_discarded = 0
for sub, parent in self.module.all_scopes():
if isinstance(sub, Subroutine):
usages = self.module.subroutine_usage[(parent.name, sub.name)]
if not usages and parent.name + '.' + sub.name not in never_remove:
parent.scope.remove_node(sub)
num_discarded += 1
for sub in self.module.all_nodes(Subroutine):
usages = self.module.subroutine_usage[(sub.parent.name, sub.name)]
if not usages and sub.parent.name + '.' + sub.name not in never_remove:
sub.parent.remove_node(sub)
num_discarded += 1
if num_discarded:
print("discarded {:d} unused subroutines".format(num_discarded))
def optimize_compare_with_zero(self) -> None:
@no_type_check
def optimize_goto_compare_with_zero(self) -> None:
# a conditional goto that compares a value with zero will be simplified
# the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
for block, parent in self.module.all_scopes():
if block.scope:
for goto in block.all_nodes(Goto):
if isinstance(goto.condition, Expression):
print("NOT IMPLEMENTED YET: optimize goto conditionals", goto.condition) # @todo
# if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0:
# simplified = False
# if cond.ifstatus in ("true", "ne"):
# if cond.comparison_op == "==":
# # if_true something == 0 -> if_not something
# cond.ifstatus = "not"
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.comparison_op == "!=":
# # if_true something != 0 -> if_true something
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.ifstatus in ("not", "eq"):
# if cond.comparison_op == "==":
# # if_not something == 0 -> if_true something
# cond.ifstatus = "true"
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.comparison_op == "!=":
# # if_not something != 0 -> if_not something
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# if simplified:
# print("{}: simplified comparison with zero".format(stmt.sourceref))
for goto in self.module.all_nodes(Goto):
if isinstance(goto.condition, Expression):
print("NOT IMPLEMENTED YET: optimize goto conditionals", goto.condition) # @todo
# if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0:
# simplified = False
# if cond.ifstatus in ("true", "ne"):
# if cond.comparison_op == "==":
# # if_true something == 0 -> if_not something
# cond.ifstatus = "not"
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.comparison_op == "!=":
# # if_true something != 0 -> if_true something
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.ifstatus in ("not", "eq"):
# if cond.comparison_op == "==":
# # if_not something == 0 -> if_true something
# cond.ifstatus = "true"
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# elif cond.comparison_op == "!=":
# # if_not something != 0 -> if_not something
# cond.comparison_op, cond.rvalue = "", None
# simplified = True
# if simplified:
# print("{}: simplified comparison with zero".format(stmt.sourceref))
def remove_empty_blocks(self) -> None:
# remove blocks without name and without address, or that are empty

View File

@ -382,12 +382,6 @@ class Subroutine(AstNode):
raise ValueError("subroutine must have either a scope or an address, not both")
@attr.s(cmp=False, repr=False)
class Goto(AstNode):
# one or two subnodes: target (SymbolName, int or Dereference) and optionally: condition (Expression)
if_stmt = attr.ib(default=None)
@attr.s(cmp=True, slots=True)
class LiteralValue(AstNode):
# no subnodes.
@ -499,6 +493,20 @@ class Expression(AstNode):
print(tree(self, 0))
@attr.s(cmp=False, repr=False)
class Goto(AstNode):
# one or two subnodes: target (SymbolName, int or Dereference) and optionally: condition (Expression)
if_stmt = attr.ib(default=None)
@property
def target(self) -> Union[SymbolName, int, Dereference]:
return self.nodes[0] # type: ignore
@property
def condition(self) -> Expression:
return self.nodes[1] if len(self.nodes) == 2 else None # type: ignore
@attr.s(cmp=False, slots=True)
class CallArgument(AstNode):
# one subnode: the value (Expression)
@ -614,19 +622,20 @@ class AssignmentTargets(AstNode):
@attr.s(cmp=False, slots=True, repr=False)
class Assignment(AstNode):
# can be single- or multi-assignment
# has two subnodes: left (=AssignmentTargets) and right (=Expression or another Assignment but those will be converted to multi assign)
# has two subnodes: left (=AssignmentTargets) and right (=reg/literal/expr
# or another Assignment but those will be converted to multi assign)
@property
def left(self) -> AssignmentTargets:
return self.nodes[0] # type: ignore
@property
def right(self) -> Union[LiteralValue, Expression]:
def right(self) -> Union[Register, LiteralValue, Expression]:
return self.nodes[1] # type: ignore
@right.setter
def right(self, rvalue: Union[LiteralValue, Expression]) -> None:
assert isinstance(rvalue, (LiteralValue, Expression))
def right(self, rvalue: Union[Register, LiteralValue, Expression]) -> None:
assert isinstance(rvalue, (Register, LiteralValue, Expression))
self.nodes[1] = rvalue