diff --git a/il65/compile.py b/il65/compile.py index 9d205d4d0..5a5e23396 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -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]], diff --git a/il65/optimize.py b/il65/optimize.py index 731f95361..4eae1270a 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -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 diff --git a/il65/plyparse.py b/il65/plyparse.py index b6386f802..0332c9682 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -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