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 previous_stmt = None
for node in module.all_nodes(): for node in module.all_nodes():
if isinstance(node, Scope): if isinstance(node, Scope):
previous_stmt = None
if node.nodes and isinstance(node.parent, (Block, Subroutine)): if node.nodes and isinstance(node.parent, (Block, Subroutine)):
self._check_last_statement_is_return(node.nodes[-1]) self._check_last_statement_is_return(node.nodes[-1])
elif isinstance(node, SubCall): elif isinstance(node, SubCall):
if isinstance(node.target, SymbolName): if isinstance(node.target, SymbolName):
subdef = node.my_scope().lookup(node.target.name) 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): elif isinstance(node, Subroutine):
# the previous statement (if any) must be a Goto or Return # 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) raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", node.sourceref)
elif isinstance(node, IncrDecr): elif isinstance(node, IncrDecr):
if isinstance(node.target, SymbolName): 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) lvalue_types = set(datatype_of(lv, node.my_scope()) for lv in node.left.nodes)
if len(lvalue_types) == 1: if len(lvalue_types) == 1:
_, newright = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref) _, 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 node.right = newright
else: else:
raise TypeError("invalid coerced constant type", newright) raise TypeError("invalid coerced constant type", newright)
@ -187,14 +187,14 @@ class PlyParser:
def reduce_right(assign: Assignment) -> Assignment: def reduce_right(assign: Assignment) -> Assignment:
if isinstance(assign.right, Assignment): if isinstance(assign.right, Assignment):
right = reduce_right(assign.right) right = reduce_right(assign.right)
assign.left.extend(right.left) assign.left.nodes.extend(right.left.nodes)
assign.right = right.right assign.right = right.right
return assign return assign
for node in module.all_nodes(Assignment): for node in module.all_nodes(Assignment):
if isinstance(node.right, Assignment): if isinstance(node.right, Assignment):
multi = reduce_right(node) 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 @no_type_check
def apply_directive_options(self, module: Module) -> None: def apply_directive_options(self, module: Module) -> None:
@ -324,9 +324,8 @@ class PlyParser:
@no_type_check @no_type_check
def _get_subroutine_usages_from_goto(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_goto(self, usages: Dict[Tuple[str, str], Set[str]],
goto: Goto, parent_scope: Scope) -> None: goto: Goto, parent_scope: Scope) -> None:
target = goto.target.target if isinstance(goto.target, SymbolName):
if isinstance(target, SymbolName): usages[(parent_scope.name, goto.target.name)].add(str(goto.sourceref))
usages[(parent_scope.name, target.name)].add(str(goto.sourceref))
self._get_subroutine_usages_from_expression(usages, goto.condition, parent_scope) 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]], 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 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,\ 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 from .plylex import print_warning, print_bold
@ -18,14 +19,13 @@ class Optimizer:
def optimize(self) -> None: def optimize(self) -> None:
self.num_warnings = 0 self.num_warnings = 0
self.optimize_assignments() self.optimize_assignments()
return # XXX fix all methods below self.combine_assignments_into_multi()
#self.combine_assignments_into_multi() self.optimize_multiassigns()
#self.optimize_multiassigns() self.remove_unused_subroutines()
#self.remove_unused_subroutines() self.optimize_goto_compare_with_zero()
#self.optimize_compare_with_zero()
# @todo join multiple incr/decr of same var into one (if value stays < 256) # @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) # @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: def optimize_assignments(self) -> None:
# remove assignment statements that do nothing (A=A) # remove assignment statements that do nothing (A=A)
@ -64,10 +64,10 @@ class Optimizer:
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 block, parent in self.module.all_scopes(): for scope in self.module.all_nodes(Scope):
rvalue = None rvalue = None
assignments = [] assignments = [] # type: List[Assignment]
for stmt in list(block.nodes): for stmt in list(scope.nodes):
if isinstance(stmt, Assignment): if isinstance(stmt, Assignment):
if assignments: if assignments:
if stmt.right == rvalue: if stmt.right == rvalue:
@ -77,8 +77,8 @@ class Optimizer:
# replace the first assignment by a multi-assign with all the others # replace the first assignment by a multi-assign with all the others
for assignment in assignments[1:]: for assignment in assignments[1:]:
print("{}: joined with previous assignment".format(assignment.sourceref)) print("{}: joined with previous assignment".format(assignment.sourceref))
assignments[0].left.extend(assignment.left) assignments[0].left.nodes.extend(assignment.left.nodes)
block.scope.remove_node(assignment) scope.remove_node(assignment)
rvalue = None rvalue = None
assignments.clear() assignments.clear()
else: else:
@ -88,18 +88,19 @@ class Optimizer:
rvalue = None rvalue = None
assignments.clear() assignments.clear()
@no_type_check
def optimize_multiassigns(self) -> None: def optimize_multiassigns(self) -> None:
# optimize multi-assign statements (remove duplicate targets, optimize order) # optimize multi-assign statements (remove duplicate targets, optimize order)
for block, parent in self.module.all_scopes(): for assignment in self.module.all_nodes(Assignment):
for assignment in block.nodes: if len(assignment.left.nodes) > 1:
if isinstance(assignment, Assignment) and len(assignment.left) > 1: # remove duplicates
# remove duplicates lvalues = set(assignment.left.nodes)
lvalues = set(assignment.left) if len(lvalues) != len(assignment.left.nodes):
if len(lvalues) != len(assignment.left): print("{}: removed duplicate assignment targets".format(assignment.sourceref))
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)
# @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any) assignment.left.nodes = list(lvalues)
assignment.left = list(lvalues)
@no_type_check
def remove_unused_subroutines(self) -> None: def remove_unused_subroutines(self) -> None:
# some symbols are used by the emitted assembly code from the code generator, # some symbols are used by the emitted assembly code from the code generator,
# and should never be removed or the assembler will fail # 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.GIVUAYF", "c64flt.copy_mflt", "c64flt.float_add_one", "c64flt.float_sub_one",
"c64flt.float_add_SW1_to_XY", "c64flt.float_sub_SW1_from_XY"} "c64flt.float_add_SW1_to_XY", "c64flt.float_sub_SW1_from_XY"}
num_discarded = 0 num_discarded = 0
for sub, parent in self.module.all_scopes(): for sub in self.module.all_nodes(Subroutine):
if isinstance(sub, Subroutine): usages = self.module.subroutine_usage[(sub.parent.name, sub.name)]
usages = self.module.subroutine_usage[(parent.name, sub.name)] if not usages and sub.parent.name + '.' + sub.name not in never_remove:
if not usages and parent.name + '.' + sub.name not in never_remove: sub.parent.remove_node(sub)
parent.scope.remove_node(sub) num_discarded += 1
num_discarded += 1
if num_discarded: if num_discarded:
print("discarded {:d} unused subroutines".format(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 # 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 # the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
for block, parent in self.module.all_scopes(): for goto in self.module.all_nodes(Goto):
if block.scope: if isinstance(goto.condition, Expression):
for goto in block.all_nodes(Goto): print("NOT IMPLEMENTED YET: optimize goto conditionals", goto.condition) # @todo
if isinstance(goto.condition, Expression): # if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0:
print("NOT IMPLEMENTED YET: optimize goto conditionals", goto.condition) # @todo # simplified = False
# if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0: # if cond.ifstatus in ("true", "ne"):
# simplified = False # if cond.comparison_op == "==":
# if cond.ifstatus in ("true", "ne"): # # if_true something == 0 -> if_not something
# if cond.comparison_op == "==": # cond.ifstatus = "not"
# # if_true something == 0 -> if_not something # cond.comparison_op, cond.rvalue = "", None
# cond.ifstatus = "not" # simplified = True
# cond.comparison_op, cond.rvalue = "", None # elif cond.comparison_op == "!=":
# simplified = True # # if_true something != 0 -> if_true something
# elif cond.comparison_op == "!=": # cond.comparison_op, cond.rvalue = "", None
# # if_true something != 0 -> if_true something # simplified = True
# cond.comparison_op, cond.rvalue = "", None # elif cond.ifstatus in ("not", "eq"):
# simplified = True # if cond.comparison_op == "==":
# elif cond.ifstatus in ("not", "eq"): # # if_not something == 0 -> if_true something
# if cond.comparison_op == "==": # cond.ifstatus = "true"
# # if_not something == 0 -> if_true something # cond.comparison_op, cond.rvalue = "", None
# cond.ifstatus = "true" # simplified = True
# cond.comparison_op, cond.rvalue = "", None # elif cond.comparison_op == "!=":
# simplified = True # # if_not something != 0 -> if_not something
# elif cond.comparison_op == "!=": # cond.comparison_op, cond.rvalue = "", None
# # if_not something != 0 -> if_not something # simplified = True
# cond.comparison_op, cond.rvalue = "", None # if simplified:
# simplified = True # print("{}: simplified comparison with zero".format(stmt.sourceref))
# if simplified:
# print("{}: simplified comparison with zero".format(stmt.sourceref))
def remove_empty_blocks(self) -> None: def remove_empty_blocks(self) -> None:
# remove blocks without name and without address, or that are empty # 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") 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) @attr.s(cmp=True, slots=True)
class LiteralValue(AstNode): class LiteralValue(AstNode):
# no subnodes. # no subnodes.
@ -499,6 +493,20 @@ class Expression(AstNode):
print(tree(self, 0)) 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) @attr.s(cmp=False, slots=True)
class CallArgument(AstNode): class CallArgument(AstNode):
# one subnode: the value (Expression) # one subnode: the value (Expression)
@ -614,19 +622,20 @@ class AssignmentTargets(AstNode):
@attr.s(cmp=False, slots=True, repr=False) @attr.s(cmp=False, slots=True, repr=False)
class Assignment(AstNode): class Assignment(AstNode):
# can be single- or multi-assignment # 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 @property
def left(self) -> AssignmentTargets: def left(self) -> AssignmentTargets:
return self.nodes[0] # type: ignore return self.nodes[0] # type: ignore
@property @property
def right(self) -> Union[LiteralValue, Expression]: def right(self) -> Union[Register, LiteralValue, Expression]:
return self.nodes[1] # type: ignore return self.nodes[1] # type: ignore
@right.setter @right.setter
def right(self, rvalue: Union[LiteralValue, Expression]) -> None: def right(self, rvalue: Union[Register, LiteralValue, Expression]) -> None:
assert isinstance(rvalue, (LiteralValue, Expression)) assert isinstance(rvalue, (Register, LiteralValue, Expression))
self.nodes[1] = rvalue self.nodes[1] = rvalue