mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
optimizer
This commit is contained in:
parent
eb58119b97
commit
ab71a15007
@ -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]],
|
||||||
|
113
il65/optimize.py
113
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
|
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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user