mirror of
https://github.com/irmen/prog8.git
synced 2025-02-23 07:29:12 +00:00
compiler
This commit is contained in:
parent
3ea0723c3e
commit
eb58119b97
@ -133,7 +133,7 @@ class PlyParser:
|
||||
if zpnode.name != "ZP":
|
||||
return
|
||||
zeropage = Zeropage(module.zp_options)
|
||||
for vardef in zpnode.scope.filter_nodes(VarDef):
|
||||
for vardef in zpnode.all_nodes(VarDef):
|
||||
if vardef.datatype.isstring():
|
||||
raise ParseError("cannot put strings in the zeropage", vardef.sourceref)
|
||||
try:
|
||||
@ -158,7 +158,10 @@ class PlyParser:
|
||||
encountered_blocks.add(blockname)
|
||||
elif isinstance(node, Expression):
|
||||
try:
|
||||
process_expression(node, node.my_scope(), node.sourceref)
|
||||
evaluated = process_expression(node, node.my_scope(), node.sourceref)
|
||||
if evaluated is not node:
|
||||
# replace the node with the newly evaluated result
|
||||
node.parent.replace_node(node, evaluated)
|
||||
except ParseError:
|
||||
raise
|
||||
except Exception as x:
|
||||
@ -408,11 +411,11 @@ class PlyParser:
|
||||
self.parse_errors += import_parse_errors
|
||||
else:
|
||||
raise FileNotFoundError("missing il65lib")
|
||||
# XXX append the imported module's contents (blocks) at the end of the current module
|
||||
# for block in (node for imported_module in imported
|
||||
# for node in imported_module.scope.nodes
|
||||
# if isinstance(node, Block)):
|
||||
# module.scope.add_node(block)
|
||||
# append the imported module's contents (blocks) at the end of the current module
|
||||
for block in (node for imported_module in imported
|
||||
for node in imported_module.scope.nodes
|
||||
if isinstance(node, Block)):
|
||||
module.scope.add_node(block)
|
||||
|
||||
def import_file(self, filename: str) -> Tuple[Module, int]:
|
||||
sub_parser = PlyParser(imported_module=True)
|
||||
|
@ -52,19 +52,13 @@ class AssemblyGenerator:
|
||||
out("\t.end")
|
||||
|
||||
def sanitycheck(self) -> None:
|
||||
start_found = False
|
||||
for block, parent in self.module.all_scopes():
|
||||
assert isinstance(block, (Module, Block, Subroutine))
|
||||
for label in block.nodes:
|
||||
if isinstance(label, Label) and label.name == "start" and block.name == "main":
|
||||
start_found = True
|
||||
break
|
||||
if start_found:
|
||||
for label in self.module.all_nodes(Label):
|
||||
if label.name == "start" and label.my_scope().name == "main":
|
||||
break
|
||||
if not start_found:
|
||||
else:
|
||||
print_bold("ERROR: program entry point is missing ('start' label in 'main' block)\n")
|
||||
raise SystemExit(1)
|
||||
all_blocknames = [b.name for b in self.module.scope.filter_nodes(Block)]
|
||||
all_blocknames = [b.name for b in self.module.all_nodes(Block)]
|
||||
unique_blocknames = set(all_blocknames)
|
||||
if len(all_blocknames) != len(unique_blocknames):
|
||||
for name in unique_blocknames:
|
||||
@ -137,7 +131,7 @@ class AssemblyGenerator:
|
||||
generate_block_vars(out, zpblock, True)
|
||||
# there's no code in the zero page block.
|
||||
out("\v.pend\n")
|
||||
for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0):
|
||||
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
|
||||
if block.name == "ZP":
|
||||
continue # already processed
|
||||
self.cur_block = block
|
||||
@ -149,7 +143,7 @@ class AssemblyGenerator:
|
||||
out("{:s}\t.proc\n".format(block.label))
|
||||
generate_block_init(out, block)
|
||||
generate_block_vars(out, block)
|
||||
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is not None)
|
||||
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is not None)
|
||||
if subroutines:
|
||||
# these are (external) subroutines that are defined by address instead of a scope/code
|
||||
out("; external subroutines")
|
||||
@ -164,7 +158,7 @@ class AssemblyGenerator:
|
||||
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
|
||||
# make sure the main.start routine clears the decimal and carry flags as first steps
|
||||
out("\vcld\n\vclc\n\vclv")
|
||||
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is None)
|
||||
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is None)
|
||||
if subroutines:
|
||||
# these are subroutines that are defined by a scope/code
|
||||
out("; -- block subroutines")
|
||||
|
@ -7,7 +7,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 ..plyparse import Block, VarType, VarDef
|
||||
from ..plyparse import Block, VarType, VarDef, LiteralValue
|
||||
from ..datatypes import DataType, STRING_DATATYPES
|
||||
from . import to_hex, to_mflpt5, CodeError
|
||||
|
||||
@ -56,18 +56,18 @@ def generate_block_init(out: Callable, block: Block) -> None:
|
||||
float_inits = {}
|
||||
prev_value_a, prev_value_x = None, None
|
||||
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
|
||||
for vardef in block.scope.filter_nodes(VarDef):
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
if vardef.vartype == VarType.VAR:
|
||||
vars_by_datatype[vardef.datatype].append(vardef)
|
||||
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
|
||||
assert type(bytevar.value) is int
|
||||
if bytevar.value != prev_value_a:
|
||||
out("\vlda #${:02x}".format(bytevar.value))
|
||||
prev_value_a = bytevar.value
|
||||
assert isinstance(bytevar.value, LiteralValue) and type(bytevar.value.value) is int
|
||||
if bytevar.value.value != prev_value_a:
|
||||
out("\vlda #${:02x}".format(bytevar.value.value))
|
||||
prev_value_a = bytevar.value.value
|
||||
out("\vsta {:s}".format(bytevar.name))
|
||||
for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value):
|
||||
assert type(wordvar.value) is int
|
||||
v_hi, v_lo = divmod(wordvar.value, 256)
|
||||
assert isinstance(wordvar.value, LiteralValue) and type(wordvar.value.value) is int
|
||||
v_hi, v_lo = divmod(wordvar.value.value, 256)
|
||||
if v_hi != prev_value_a:
|
||||
out("\vlda #${:02x}".format(v_hi))
|
||||
prev_value_a = v_hi
|
||||
@ -77,18 +77,18 @@ def generate_block_init(out: Callable, block: Block) -> None:
|
||||
out("\vsta {:s}".format(wordvar.name))
|
||||
out("\vstx {:s}+1".format(wordvar.name))
|
||||
for floatvar in vars_by_datatype[DataType.FLOAT]:
|
||||
assert isinstance(floatvar.value, (int, float))
|
||||
fpbytes = to_mflpt5(floatvar.value) # type: ignore
|
||||
assert isinstance(floatvar.value, LiteralValue) and type(floatvar.value.value) in (int, float)
|
||||
fpbytes = to_mflpt5(floatvar.value.value) # type: ignore
|
||||
float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value)
|
||||
for arrayvar in vars_by_datatype[DataType.BYTEARRAY]:
|
||||
assert type(arrayvar.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0])
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
|
||||
for arrayvar in vars_by_datatype[DataType.WORDARRAY]:
|
||||
assert type(arrayvar.value) is int
|
||||
_memsetw(arrayvar.name, arrayvar.value, arrayvar.size[0])
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memsetw(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
|
||||
for arrayvar in vars_by_datatype[DataType.MATRIX]:
|
||||
assert type(arrayvar.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0] * arrayvar.size[1])
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0] * arrayvar.size[1])
|
||||
if float_inits:
|
||||
out("\vldx #4")
|
||||
out("-")
|
||||
@ -114,7 +114,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
|
||||
# The memory bytes of the allocated variables is set to zero (so it compresses very well),
|
||||
# their actual starting values are set by the block init code.
|
||||
vars_by_vartype = defaultdict(list) # type: Dict[VarType, List[VarDef]]
|
||||
for vardef in block.scope.filter_nodes(VarDef):
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
vars_by_vartype[vardef.vartype].append(vardef)
|
||||
out("; constants")
|
||||
for vardef in vars_by_vartype.get(VarType.CONST, []):
|
||||
@ -132,13 +132,13 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
|
||||
# create a definition for variables at a specific place in memory (memory-mapped)
|
||||
if vardef.datatype.isnumeric():
|
||||
assert vardef.size == [1]
|
||||
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower()))
|
||||
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower()))
|
||||
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), vardef.size[0]))
|
||||
out("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value.value), vardef.size[0]))
|
||||
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), vardef.size[0]))
|
||||
out("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value.value), vardef.size[0]))
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) in (2, 3)
|
||||
if len(vardef.size) == 2:
|
||||
@ -147,7 +147,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
|
||||
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), comment))
|
||||
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment))
|
||||
else:
|
||||
raise CodeError("invalid var type")
|
||||
out("; normal variables - initial values will be set by init code")
|
||||
|
@ -81,7 +81,6 @@ def main() -> None:
|
||||
print("\nParsing program source code.")
|
||||
parser = PlyParser()
|
||||
parsed_module = parser.parse_file(args.sourcefile)
|
||||
raise SystemExit("First fix the parser to iterate all nodes in the new way.") # XXX
|
||||
if parsed_module:
|
||||
if args.nooptimize:
|
||||
print_bold("not optimizing the parse tree!")
|
||||
|
@ -6,7 +6,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
|
||||
datatype_of, coerce_constant_value, AssignmentTargets
|
||||
datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue
|
||||
from .plylex import print_warning, print_bold
|
||||
|
||||
|
||||
@ -18,49 +18,51 @@ class Optimizer:
|
||||
def optimize(self) -> None:
|
||||
self.num_warnings = 0
|
||||
self.optimize_assignments()
|
||||
self.combine_assignments_into_multi()
|
||||
self.optimize_multiassigns()
|
||||
self.remove_unused_subroutines()
|
||||
self.optimize_compare_with_zero()
|
||||
return # XXX fix all methods below
|
||||
#self.combine_assignments_into_multi()
|
||||
#self.optimize_multiassigns()
|
||||
#self.remove_unused_subroutines()
|
||||
#self.optimize_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):
|
||||
def optimize_assignments(self) -> None:
|
||||
# remove assignment statements that do nothing (A=A)
|
||||
# and augmented assignments that have no effect (A+=0)
|
||||
# convert augmented assignments to simple incr/decr if possible (A+=10 => A++ by 10)
|
||||
# @todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?)
|
||||
for block, parent in self.module.all_scopes():
|
||||
for assignment in list(block.nodes):
|
||||
if isinstance(assignment, Assignment):
|
||||
assignment.left = [lv for lv in assignment.left if lv != assignment.right]
|
||||
if not assignment.left:
|
||||
block.scope.remove_node(assignment)
|
||||
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:
|
||||
assignment.my_scope().remove_node(assignment)
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
|
||||
if isinstance(assignment, AugAssignment):
|
||||
if isinstance(assignment.right, LiteralValue) and isinstance(assignment.right.value, (int, float)):
|
||||
if assignment.right.value == 0 and assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
|
||||
if isinstance(assignment, AugAssignment):
|
||||
if isinstance(assignment.right, (int, float)):
|
||||
if assignment.right == 0 and assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
|
||||
block.scope.remove_node(assignment)
|
||||
if assignment.right >= 8 and assignment.operator in ("<<=", ">>="):
|
||||
print("{}: shifting result is always zero".format(assignment.sourceref))
|
||||
new_stmt = Assignment(sourceref=assignment.sourceref)
|
||||
new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref))
|
||||
new_stmt.nodes.append(0)
|
||||
block.scope.replace_node(assignment, new_stmt)
|
||||
if assignment.operator in ("+=", "-=") and 0 < assignment.right < 256:
|
||||
howmuch = assignment.right
|
||||
if howmuch not in (0, 1):
|
||||
_, howmuch = coerce_constant_value(datatype_of(assignment.left, block.scope), howmuch, assignment.sourceref)
|
||||
new_stmt = IncrDecr(operator="++" if assignment.operator == "+=" else "--",
|
||||
howmuch=howmuch, sourceref=assignment.sourceref)
|
||||
new_stmt.target = assignment.left
|
||||
block.scope.replace_node(assignment, new_stmt)
|
||||
assignment.my_scope().remove_node(assignment)
|
||||
if assignment.right.value >= 8 and assignment.operator in ("<<=", ">>="):
|
||||
print("{}: shifting result is always zero".format(assignment.sourceref))
|
||||
new_stmt = Assignment(sourceref=assignment.sourceref)
|
||||
new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref))
|
||||
new_stmt.nodes.append(0)
|
||||
assignment.my_scope().replace_node(assignment, new_stmt)
|
||||
if assignment.operator in ("+=", "-=") and 0 < assignment.right.value < 256:
|
||||
howmuch = assignment.right
|
||||
if howmuch.value not in (0, 1):
|
||||
_, howmuch = coerce_constant_value(datatype_of(assignment.left, assignment.my_scope()),
|
||||
howmuch, assignment.sourceref)
|
||||
new_stmt = IncrDecr(operator="++" if assignment.operator == "+=" else "--",
|
||||
howmuch=howmuch.value, sourceref=assignment.sourceref)
|
||||
new_stmt.target = assignment.left
|
||||
assignment.my_scope().replace_node(assignment, new_stmt)
|
||||
|
||||
def combine_assignments_into_multi(self):
|
||||
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():
|
||||
rvalue = None
|
||||
@ -86,7 +88,7 @@ class Optimizer:
|
||||
rvalue = None
|
||||
assignments.clear()
|
||||
|
||||
def optimize_multiassigns(self):
|
||||
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:
|
||||
@ -98,7 +100,7 @@ class Optimizer:
|
||||
# @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
|
||||
assignment.left = list(lvalues)
|
||||
|
||||
def remove_unused_subroutines(self):
|
||||
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
|
||||
never_remove = {"c64.FREADUY", "c64.FTOMEMXY", "c64.FADD", "c64.FSUB",
|
||||
@ -114,14 +116,14 @@ class Optimizer:
|
||||
if num_discarded:
|
||||
print("discarded {:d} unused subroutines".format(num_discarded))
|
||||
|
||||
def optimize_compare_with_zero(self):
|
||||
def optimize_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 stmt in block.scope.filter_nodes(Goto):
|
||||
if isinstance(stmt.condition, Expression):
|
||||
print("NOT IMPLEMENTED YET: optimize goto conditionals", stmt.condition) # @todo
|
||||
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"):
|
||||
@ -149,7 +151,7 @@ class Optimizer:
|
||||
|
||||
def remove_empty_blocks(self) -> None:
|
||||
# remove blocks without name and without address, or that are empty
|
||||
for node, parent in self.module.all_scopes():
|
||||
for node in self.module.all_nodes():
|
||||
if isinstance(node, (Subroutine, Block)):
|
||||
if not node.scope:
|
||||
continue
|
||||
@ -160,14 +162,14 @@ class Optimizer:
|
||||
if empty:
|
||||
self.num_warnings += 1
|
||||
print_warning("ignoring empty block or subroutine", node.sourceref)
|
||||
assert isinstance(parent, (Block, Module))
|
||||
parent.scope.nodes.remove(node)
|
||||
assert isinstance(node.parent, (Block, Module))
|
||||
node.my_scope().nodes.remove(node)
|
||||
if isinstance(node, Block):
|
||||
if not node.name and node.address is None:
|
||||
self.num_warnings += 1
|
||||
print_warning("ignoring block without name and address", node.sourceref)
|
||||
assert isinstance(parent, Module)
|
||||
parent.scope.nodes.remove(node)
|
||||
assert isinstance(node.parent, Module)
|
||||
node.my_scope().nodes.remove(node)
|
||||
|
||||
|
||||
def optimize(mod: Module) -> None:
|
||||
|
@ -270,7 +270,7 @@ class Module(AstNode):
|
||||
@no_type_check
|
||||
def zeropage(self) -> Optional[Block]:
|
||||
# return the zeropage block (if defined)
|
||||
first_block = next(self.scope.filter_nodes(Block))
|
||||
first_block = next(self.scope.all_nodes(Block))
|
||||
if first_block.name == "ZP":
|
||||
return first_block
|
||||
return None
|
||||
@ -278,7 +278,7 @@ class Module(AstNode):
|
||||
@no_type_check
|
||||
def main(self) -> Optional[Block]:
|
||||
# return the 'main' block (if defined)
|
||||
for block in self.scope.filter_nodes(Block):
|
||||
for block in self.scope.all_nodes(Block):
|
||||
if block.name == "main":
|
||||
return block
|
||||
return None
|
||||
|
Loading…
x
Reference in New Issue
Block a user