diff --git a/il65/codegen.py b/il65/codegen.py index a4ce113ac..c41d3789a 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -207,18 +207,17 @@ class CodeGenerator: self.generate_block_vars(zpblock) self.p("\t.pend\n") # make sure the main.start routine clears the decimal and carry flags as first steps - for block in self.parsed.blocks: - if block.name == "main": - statements = list(block.statements) - for index, stmt in enumerate(statements): - if isinstance(stmt, ParseResult.Label) and stmt.name == "start": - asmlines = [ - "\t\tcld\t\t\t; clear decimal flag", - "\t\tclc\t\t\t; clear carry flag" - ] - statements.insert(index+1, ParseResult.InlineAsm(0, asmlines)) - break - block.statements = statements + block = self.parsed.find_block("main") + statements = list(block.statements) + for index, stmt in enumerate(statements): + if isinstance(stmt, ParseResult.Label) and stmt.name == "start": + asmlines = [ + "\t\tcld\t\t\t; clear decimal flag", + "\t\tclc\t\t\t; clear carry flag" + ] + statements.insert(index+1, ParseResult.InlineAsm(0, asmlines)) + break + block.statements = statements # generate for block in sorted(self.parsed.blocks, key=lambda b: b.address): if block.name in ("ZP", "
"): diff --git a/il65/main.py b/il65/main.py index acd2ab9a7..080b714db 100644 --- a/il65/main.py +++ b/il65/main.py @@ -33,11 +33,11 @@ def main() -> None: print("\n" + description) start = time.perf_counter() - pp = PreprocessingParser(args.sourcefile) + pp = PreprocessingParser(args.sourcefile, ) sourcelines, symbols = pp.preprocess() symbols.print_table(True) - p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols) + p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage) parsed = p.parse() if parsed: if args.noopt: diff --git a/il65/parse.py b/il65/parse.py index e15b9ab16..13058d614 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -11,7 +11,8 @@ import re import os import shutil import enum -from typing import Set, List, Tuple, Optional, Any, Dict, Union +from collections import defaultdict +from typing import Set, List, Tuple, Optional, Any, Dict, Union, Set from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\ parse_expr_as_string from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \ @@ -34,6 +35,7 @@ class ParseResult: self.restorezp = False self.start_address = 0 self.blocks = [] # type: List['ParseResult.Block'] + self.subroutine_usage = defaultdict(set) # type: Dict[Tuple[str, str], Set[str]] class Block: _unnamed_block_labels = {} # type: Dict[ParseResult.Block, str] @@ -445,11 +447,23 @@ class ParseResult: if block.name != "
": self.blocks.append(block) + def find_block(self, name: str) -> Block: + for block in self.blocks: + if block.name == name: + return block + raise KeyError("block not found: " + name) + + def sub_used_by(self, sub: SubroutineDef, sourceref: SourceRef) -> None: + self.subroutine_usage[(sub.blockname, sub.name)].add(str(sourceref)) + class Parser: - def __init__(self, filename: str, outputdir: str, sourcelines: List[Tuple[int, str]]=None, - parsing_import: bool=False, ppsymbols: SymbolTable=None) -> None: + def __init__(self, filename: str, outputdir: str, sourcelines: List[Tuple[int, str]] = None, parsing_import: bool = False, + ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None: self.result = ParseResult(filename) + if sub_usage is not None: + # re-use the (global) subroutine usage tracking + self.result.subroutine_usage = sub_usage self.sourceref = SourceRef(filename, -1, 0) if sourcelines: self.lines = sourcelines @@ -763,7 +777,7 @@ class Parser: raise self.PError("imported file not found") def create_import_parser(self, filename: str, outputdir: str) -> 'Parser': - return Parser(filename, outputdir, parsing_import=True) + return Parser(filename, outputdir, parsing_import=True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage) def parse_block(self) -> ParseResult.Block: # first line contains block header "~ [name] [addr]" followed by a '{' @@ -844,7 +858,7 @@ class Parser: elif line.startswith(("sub ", "sub\t")): if is_zp_block: raise self.PError("ZP block cannot contain subroutines") - self.parse_subx_def(line) + self.parse_subroutine_def(line) elif line.startswith(("asminclude ", "asminclude\t", "asmbinary ", "asmbinary\t")): if is_zp_block: raise self.PError("ZP block cannot contain assembler directives") @@ -903,7 +917,7 @@ class Parser: except (ValueError, SymbolError) as x: raise self.PError(str(x)) from x - def parse_subx_def(self, line: str) -> None: + def parse_subroutine_def(self, line: str) -> None: match = re.match(r"^sub\s+(?P\w+)\s+" r"\((?P[\w\s:,]*)\)" r"\s*->\s*" @@ -927,7 +941,7 @@ class Parser: if code_decl: address = None # parse the subroutine code lines (until the closing '}') - subroutine_block = ParseResult.Block(name, self.sourceref, self.cur_block.symbols) + subroutine_block = ParseResult.Block(self.cur_block.name + "." + name, self.sourceref, self.cur_block.symbols) current_block = self.cur_block self.cur_block = subroutine_block while True: @@ -1073,6 +1087,7 @@ class Parser: argname = preg args_with_pnames.append((argname, value)) arguments = args_with_pnames + self.result.sub_used_by(symbol, self.sourceref) else: if arguments: raise self.PError("call cannot take any arguments here, use a subroutine for that") @@ -1146,6 +1161,23 @@ class Parser: line = self.next_line() if line.strip() == "}": return ParseResult.InlineAsm(lineno, asmlines) + # asm can refer to other symbols as well, track subroutine usage + if line.startswith((" ", "\t")): + splits = line.split(maxsplit=2) + if len(splits) == 2: + for match in re.finditer(r"(?P[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]): + name = match.group("symbol") + if name[0] == '$': + continue + try: + if '.' not in name: + name = self.cur_block.symbols.parent.name + '.' + name + _, symbol = self.lookup(name) + except ParseError: + pass + else: + if symbol: + self.result.sub_used_by(symbol, self.sourceref) asmlines.append(line) def parse_asminclude(self, line: str) -> ParseResult.InlineAsm: @@ -1263,6 +1295,7 @@ class Parser: name = sym.name if symblock is self.cur_block else sym.blockname + '.' + sym.name return ParseResult.MemMappedValue(None, DataType.WORD, 1, name, True) elif isinstance(sym, SubroutineDef): + self.result.sub_used_by(sym, self.sourceref) name = sym.name if symblock is self.cur_block else sym.blockname + '.' + sym.name return ParseResult.MemMappedValue(sym.address, DataType.WORD, 1, name, True) else: @@ -1314,9 +1347,10 @@ class Parser: if '.' not in dottedname: dottedname = self.cur_block.name + '.' + dottedname try: - symtable, sym = self.ppsymbols.lookup(dottedname) - assert dottedname.startswith(symtable.name) - symblock = None # the block might not have been parsed yet, so just return this instead + if self.ppsymbols: + symtable, sym = self.ppsymbols.lookup(dottedname) + assert dottedname.startswith(symtable.name) + symblock = None # the block might not have been parsed yet, so just return this instead except (LookupError, SymbolError) as x: raise self.PError(str(x)) return symblock, sym @@ -1390,6 +1424,7 @@ class Optimizer: self.remove_identity_assigns(block) self.combine_assignments_into_multi(block) self.optimize_multiassigns(block) + self.remove_unused_subroutines(block) for sub in block.symbols.iter_subroutines(True): self.remove_identity_assigns(sub.sub_block) self.combine_assignments_into_multi(sub.sub_block) @@ -1442,6 +1477,20 @@ class Optimizer: # remove the Nones block.statements = [s for s in block.statements if s is not None] + def remove_unused_subroutines(self, block: ParseResult.Block) -> 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 = {"c64util.GIVUAYF", "c64.FREADUY", "c64.FTOMEMXY"} + discarded = [] + for sub in list(block.symbols.iter_subroutines()): + usages = self.parsed.subroutine_usage[(sub.blockname, sub.name)] + if not usages and sub.blockname + '.' + sub.name not in never_remove: + block.symbols.discard_sub(sub.name) + discarded.append(sub.name) + if discarded: + print("{}: discarded unused subroutines from block '{:s}': ".format(block.sourceref, block.name), end="") + print(", ".join(discarded)) + def _value_sortkey(value: ParseResult.Value) -> int: if isinstance(value, ParseResult.RegisterValue): diff --git a/il65/preprocess.py b/il65/preprocess.py index fad782ee6..e6e75b043 100644 --- a/il65/preprocess.py +++ b/il65/preprocess.py @@ -58,8 +58,8 @@ class PreprocessingParser(Parser): def parse_label(self, line: str) -> None: super().parse_label(line) - def parse_subx_def(self, line: str) -> None: - super().parse_subx_def(line) + def parse_subroutine_def(self, line: str) -> None: + super().parse_subroutine_def(line) def create_import_parser(self, filename: str, outputdir: str) -> 'Parser': return PreprocessingParser(filename) diff --git a/il65/symbols.py b/il65/symbols.py index 116d3bac5..0d927d3d9 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -171,7 +171,7 @@ class SubroutineDef(SymbolDefinition): address: Optional[int]=None, sub_block: Any=None) -> None: super().__init__(blockname, name, sourceref, False) self.address = address - self.sub_block = sub_block + self.sub_block = sub_block # this is a ParseResult.Block self.parameters = parameters self.input_registers = set() # type: Set[str] self.return_registers = set() # type: Set[str] @@ -277,6 +277,8 @@ class SymbolTable: raise SymbolError("undefined block '{:s}'".format(namepart)) from None if isinstance(scope, SymbolTable): return scope.lookup(nameparts[-1]) + elif isinstance(scope, SubroutineDef): + return scope.sub_block.symbols.lookup(nameparts[-1]) else: raise SymbolError("invalid block name '{:s}' in dotted name".format(namepart)) @@ -383,6 +385,13 @@ class SymbolTable: self.check_identifier_valid(name, sourceref) self.symbols[name] = SubroutineDef(self.name, name, sourceref, parameters, returnvalues, address, sub_block) + def discard_sub(self, name: str) -> None: + sub = self.symbols[name] + if isinstance(sub, SubroutineDef): + del self.symbols[name] + else: + raise TypeError("not a subroutine") + def define_label(self, name: str, sourceref: SourceRef) -> None: self.check_identifier_valid(name, sourceref) self.symbols[name] = LabelDef(self.name, name, sourceref, False) diff --git a/testsource/calls.ill b/testsource/calls.ill index d5e7baa54..39daa68df 100644 --- a/testsource/calls.ill +++ b/testsource/calls.ill @@ -93,4 +93,12 @@ bar start foo.bar() return + +sub unused_sub ()->() { + A=X + X=Y + Y=A + return +} + } diff --git a/testsource/dtypes.ill b/testsource/dtypes.ill index 3fc1df899..1e8495e00 100644 --- a/testsource/dtypes.ill +++ b/testsource/dtypes.ill @@ -3,6 +3,7 @@ output raw clobberzp +import "c64lib" ~ ZP { ; ZeroPage block definition: