From 374e2b311d58e70caa28b22391b39f84b7c159f6 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 4 Apr 2021 15:52:10 +0200 Subject: [PATCH] refactoring unused code removal and noModification --- .../compiler/BeforeAsmGenerationAstChanger.kt | 2 - compiler/src/prog8/compiler/Compiler.kt | 13 +- .../astprocessing/AstVariousTransforms.kt | 1 - .../astprocessing/LiteralsToAutoVars.kt | 1 - .../astprocessing/StatementReorderer.kt | 1 - .../compiler/astprocessing/TypecastsAdder.kt | 2 - .../compiler/astprocessing/VariousCleanups.kt | 1 - .../src/prog8/optimizer/BinExprSplitter.kt | 1 - compiler/src/prog8/optimizer/CallGraph.kt | 85 ++-------- .../optimizer/ConstantFoldingOptimizer.kt | 1 - .../optimizer/ConstantIdentifierReplacer.kt | 2 - .../prog8/optimizer/ExpressionSimplifier.kt | 1 - compiler/src/prog8/optimizer/Extensions.kt | 6 +- .../src/prog8/optimizer/StatementOptimizer.kt | 54 +----- .../src/prog8/optimizer/SubroutineInliner.kt | 9 +- .../src/prog8/optimizer/UnusedCodeRemover.kt | 81 +++++---- .../src/prog8/ast/statements/AstStatements.kt | 22 ++- compilerAst/src/prog8/ast/walk/AstWalker.kt | 160 +++++++++--------- docs/source/syntaxreference.rst | 2 +- docs/source/todo.rst | 4 + examples/test.p8 | 23 ++- 21 files changed, 197 insertions(+), 275 deletions(-) diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index 3696b832d..736889cf3 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -14,8 +14,6 @@ import prog8.compiler.target.ICompilationTarget internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { - private val noModifications = emptyList() - override fun after(decl: VarDecl, parent: Node): Iterable { subroutineVariables.add(decl.name to decl) if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 759c067d6..199bf8a67 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -264,11 +264,16 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) { // optimize the parse tree println("Optimizing...") + + val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile) + remover.visit(programAst) + remover.applyModifications() + while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = programAst.simplifyExpressions() val optsDone2 = programAst.splitBinaryExpressions(compTarget) - val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile) + val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget) programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away errors.report() if (optsDone1 + optsDone2 + optsDone3 == 0) @@ -281,9 +286,9 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: if(errors.noErrors()) { inliner.applyModifications() inliner.fixCallsToInlinedSubroutines() - val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile) - remover.visit(programAst) - remover.applyModifications() + val remover2 = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile) + remover2.visit(programAst) + remover2.applyModifications() } errors.report() diff --git a/compiler/src/prog8/compiler/astprocessing/AstVariousTransforms.kt b/compiler/src/prog8/compiler/astprocessing/AstVariousTransforms.kt index f50e52874..7682eb814 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstVariousTransforms.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstVariousTransforms.kt @@ -14,7 +14,6 @@ import prog8.ast.walk.IAstModification internal class AstVariousTransforms(private val program: Program) : AstWalker() { - private val noModifications = emptyList() override fun before(decl: VarDecl, parent: Node): Iterable { // is it a struct variable? then define all its struct members as mangled names, diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index e698610d8..4dbf449d6 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -13,7 +13,6 @@ import prog8.ast.walk.IAstModification internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { - private val noModifications = emptyList() override fun after(string: StringLiteralValue, parent: Node): Iterable { if(string.parent !is VarDecl && string.parent !is WhenChoice) { diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index ff87ba8eb..bcc84d662 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -26,7 +26,6 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport // - sorts the choices in when statement. // - insert AddressOf (&) expression where required (string params to a UWORD function param etc). - private val noModifications = emptyList() private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") override fun after(module: Module, parent: Node): Iterable { diff --git a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt index b1c581c36..477515ee6 100644 --- a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt +++ b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt @@ -18,8 +18,6 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk * (this includes function call arguments) */ - private val noModifications = emptyList() - override fun after(decl: VarDecl, parent: Node): Iterable { val declValue = decl.value if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) { diff --git a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt index 1bcf1da09..ab1d66fb4 100644 --- a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt +++ b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt @@ -13,7 +13,6 @@ import prog8.compiler.IErrorReporter internal class VariousCleanups(val errors: IErrorReporter): AstWalker() { - private val noModifications = emptyList() override fun before(nopStatement: NopStatement, parent: Node): Iterable { return listOf(IAstModification.Remove(nopStatement, parent as INameScope)) diff --git a/compiler/src/prog8/optimizer/BinExprSplitter.kt b/compiler/src/prog8/optimizer/BinExprSplitter.kt index f873a0f0c..98fe9e149 100644 --- a/compiler/src/prog8/optimizer/BinExprSplitter.kt +++ b/compiler/src/prog8/optimizer/BinExprSplitter.kt @@ -12,7 +12,6 @@ import prog8.compiler.target.ICompilationTarget internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { - private val noModifications = emptyList() // override fun after(decl: VarDecl, parent: Node): Iterable { // TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: diff --git a/compiler/src/prog8/optimizer/CallGraph.kt b/compiler/src/prog8/optimizer/CallGraph.kt index 464a646db..c58895c8c 100644 --- a/compiler/src/prog8/optimizer/CallGraph.kt +++ b/compiler/src/prog8/optimizer/CallGraph.kt @@ -1,24 +1,16 @@ package prog8.optimizer -import prog8.ast.INameScope import prog8.ast.Module import prog8.ast.Node import prog8.ast.Program -import prog8.ast.base.DataType -import prog8.ast.base.ParentSentinel import prog8.ast.base.Position import prog8.ast.expressions.AddressOf import prog8.ast.expressions.FunctionCall -import prog8.ast.expressions.IdentifierReference import prog8.ast.statements.* import prog8.ast.walk.IAstVisitor import prog8.compiler.IErrorReporter import java.nio.file.Path -private val alwaysKeepSubroutines = setOf( - Pair("main", "start") -) - private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) @@ -30,25 +22,10 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena val calls = mutableMapOf>().withDefault { mutableListOf() } val calledBy = mutableMapOf>().withDefault { mutableListOf() } - // TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars - val usedSymbols = mutableSetOf() - init { visit(program) } - fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) { - fun findSubs(scope: INameScope) { - scope.statements.forEach { - if (it is Subroutine) - sub(it) - if (it is INameScope) - findSubs(it) - } - } - findSubs(scope) - } - override fun visit(program: Program) { super.visit(program) @@ -64,15 +41,6 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena rootmodule.importedBy.add(rootmodule) // don't discard root module } - override fun visit(block: Block) { - if (block.definingModule().isLibraryModule) { - // make sure the block is not removed - addNodeAndParentScopes(block) - } - - super.visit(block) - } - override fun visit(directive: Directive) { val thisModule = directive.definingModule() if (directive.directive == "%import") { @@ -90,44 +58,6 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena super.visit(directive) } - override fun visit(identifier: IdentifierReference) { - // track symbol usage - val target = identifier.targetStatement(program) - if (target != null) { - addNodeAndParentScopes(target) - } - super.visit(identifier) - } - - private fun addNodeAndParentScopes(stmt: Statement) { - usedSymbols.add(stmt) - var node: Node = stmt - do { - if (node is INameScope && node is Statement) { - usedSymbols.add(node) - } - node = node.parent - } while (node !is Module && node !is ParentSentinel) - } - - override fun visit(subroutine: Subroutine) { - if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines - || subroutine.definingModule().isLibraryModule) { - // make sure the entrypoint is mentioned in the used symbols - addNodeAndParentScopes(subroutine) - } - super.visit(subroutine) - } - - override fun visit(decl: VarDecl) { - if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT) - addNodeAndParentScopes(decl) - else if(decl.parent is Block && decl.definingModule().isLibraryModule) - addNodeAndParentScopes(decl) - - super.visit(decl) - } - override fun visit(functionCall: FunctionCall) { val otherSub = functionCall.target.targetSubroutine(program) if (otherSub != null) { @@ -172,11 +102,6 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena super.visit(jump) } - override fun visit(structDecl: StructDecl) { - usedSymbols.add(structDecl) - usedSymbols.addAll(structDecl.statements) - } - override fun visit(inlineAssembly: InlineAssembly) { // parse inline asm for subroutine calls (jmp, jsr, bra) val scope = inlineAssembly.definingSubroutine() @@ -274,4 +199,14 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena recStack[sub] = false return false } + + fun unused(stmt: ISymbolStatement): Boolean { + if(stmt is Subroutine) + return calledBy[stmt].isNullOrEmpty() // TODO also check inline assembly if it uses the subroutine + + // TODO implement algorithm to check usages of other things: + // stmt can be: Block, Label, VarDecl (including ParameterVarDecl), Subroutine, StructDecl. + // NOTE also check inline assembly blocks that may reference a name. + return false + } } diff --git a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt index ed8dad11f..965b9db1b 100644 --- a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -14,7 +14,6 @@ import kotlin.math.pow internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { - private val noModifications = emptyList() override fun before(memread: DirectMemoryRead, parent: Node): Iterable { // @( &thing ) --> thing diff --git a/compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt index a9c640d01..816beb124 100644 --- a/compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -15,7 +15,6 @@ import prog8.compiler.target.ICompilationTarget // Fix up the literal value's type to match that of the vardecl internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() { - private val noModifications = emptyList() override fun after(decl: VarDecl, parent: Node): Iterable { try { @@ -40,7 +39,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat // and the array var initializer values and sizes. // This is needed because further constant optimizations depend on those. internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { - private val noModifications = emptyList() override fun after(identifier: IdentifierReference, parent: Node): Iterable { // replace identifiers that refer to const value, with the value itself diff --git a/compiler/src/prog8/optimizer/ExpressionSimplifier.kt b/compiler/src/prog8/optimizer/ExpressionSimplifier.kt index 84876bd15..a13abe973 100644 --- a/compiler/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/compiler/src/prog8/optimizer/ExpressionSimplifier.kt @@ -25,7 +25,6 @@ import kotlin.math.pow internal class ExpressionSimplifier(private val program: Program) : AstWalker() { private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() - private val noModifications = emptyList() override fun after(typecast: TypecastExpression, parent: Node): Iterable { val mods = mutableListOf() diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index c48575fdd..89bd8910f 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -4,7 +4,6 @@ import prog8.ast.IBuiltinFunctions import prog8.ast.Program import prog8.compiler.IErrorReporter import prog8.compiler.target.ICompilationTarget -import java.nio.file.Path internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) { @@ -43,9 +42,8 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati internal fun Program.optimizeStatements(errors: IErrorReporter, functions: IBuiltinFunctions, - compTarget: ICompilationTarget, - asmFileLoader: (filename: String, source: Path)->String): Int { - val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader) + compTarget: ICompilationTarget): Int { + val optimizer = StatementOptimizer(this, errors, functions, compTarget) optimizer.visit(this) val optimizationCount = optimizer.applyModifications() diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index 51110704c..94424064a 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -12,7 +12,6 @@ import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstVisitor import prog8.compiler.IErrorReporter import prog8.compiler.target.ICompilationTarget -import java.nio.file.Path import kotlin.math.floor internal const val retvarName = "prog8_retval" @@ -21,29 +20,10 @@ internal const val retvarName = "prog8_retval" internal class StatementOptimizer(private val program: Program, private val errors: IErrorReporter, private val functions: IBuiltinFunctions, - private val compTarget: ICompilationTarget, - asmFileLoader: (filename: String, source: Path)->String -) : AstWalker() { + private val compTarget: ICompilationTarget) : AstWalker() { - private val noModifications = emptyList() - private val callgraph = CallGraph(program, asmFileLoader) private val subsThatNeedReturnVariable = mutableSetOf>() - override fun after(block: Block, parent: Node): Iterable { - if("force_output" !in block.options()) { - if (block.containsNoCodeNorVars()) { - if(block.name != program.internedStringsModuleName) - errors.warn("removing empty block '${block.name}'", block.position) - return listOf(IAstModification.Remove(block, parent as INameScope)) - } - - if (block !in callgraph.usedSymbols) { - errors.warn("removing unused block '${block.name}'", block.position) - return listOf(IAstModification.Remove(block, parent as INameScope)) - } - } - return noModifications - } override fun after(subroutine: Subroutine, parent: Node): Iterable { for(returnvar in subsThatNeedReturnVariable) { @@ -51,38 +31,6 @@ internal class StatementOptimizer(private val program: Program, returnvar.first.statements.add(0, decl) } subsThatNeedReturnVariable.clear() - - val forceOutput = "force_output" in subroutine.definingBlock().options() - if(subroutine.asmAddress==null && !forceOutput) { - if(subroutine.containsNoCodeNorVars() && !subroutine.inline) { - errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) - val removals = callgraph.calledBy.getValue(subroutine).map { - IAstModification.Remove(it, it.definingScope()) - }.toMutableList() - removals += IAstModification.Remove(subroutine, subroutine.definingScope()) - return removals - } - } - - if(subroutine !in callgraph.usedSymbols && !forceOutput) { - if(!subroutine.isAsmSubroutine) { - errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) - return listOf(IAstModification.Remove(subroutine, subroutine.definingScope())) - } - } - - return noModifications - } - - override fun after(decl: VarDecl, parent: Node): Iterable { - val forceOutput = "force_output" in decl.definingBlock().options() - if(decl !in callgraph.usedSymbols && !forceOutput) { - if(decl.type == VarDeclType.VAR) - errors.warn("removing unused variable '${decl.name}'", decl.position) - - return listOf(IAstModification.Remove(decl, decl.definingScope())) - } - return noModifications } diff --git a/compiler/src/prog8/optimizer/SubroutineInliner.kt b/compiler/src/prog8/optimizer/SubroutineInliner.kt index 2dd4aab2b..2e975cf3f 100644 --- a/compiler/src/prog8/optimizer/SubroutineInliner.kt +++ b/compiler/src/prog8/optimizer/SubroutineInliner.kt @@ -4,8 +4,12 @@ import prog8.ast.IFunctionCall import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.Position -import prog8.ast.expressions.* -import prog8.ast.statements.* +import prog8.ast.expressions.FunctionCall +import prog8.ast.expressions.IdentifierReference +import prog8.ast.statements.FunctionCallStatement +import prog8.ast.statements.Return +import prog8.ast.statements.Subroutine +import prog8.ast.statements.VarDecl import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.compiler.CompilationOptions @@ -13,7 +17,6 @@ import prog8.compiler.IErrorReporter internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() { - private val noModifications = emptyList() private var callsToInlinedSubroutines = mutableListOf>() fun fixCallsToInlinedSubroutines() { diff --git a/compiler/src/prog8/optimizer/UnusedCodeRemover.kt b/compiler/src/prog8/optimizer/UnusedCodeRemover.kt index cd539391a..24b81a561 100644 --- a/compiler/src/prog8/optimizer/UnusedCodeRemover.kt +++ b/compiler/src/prog8/optimizer/UnusedCodeRemover.kt @@ -1,8 +1,10 @@ package prog8.optimizer import prog8.ast.INameScope +import prog8.ast.Module import prog8.ast.Node import prog8.ast.Program +import prog8.ast.base.VarDeclType import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.PrefixExpression @@ -18,39 +20,17 @@ import java.nio.file.Path internal class UnusedCodeRemover(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget, - private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() { + asmFileLoader: (filename: String, source: Path)->String): AstWalker() { - override fun before(program: Program, parent: Node): Iterable { - val callgraph = CallGraph(program, asmFileLoader) - val removals = mutableListOf() + private val callgraph = CallGraph(program, asmFileLoader) - // remove all subroutines that aren't called, or are empty - // NOTE: part of this is also done already in the StatementOptimizer - val entrypoint = program.entrypoint() - program.modules.forEach { - callgraph.forAllSubroutines(it) { sub -> - val forceOutput = "force_output" in sub.definingBlock().options() - if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) { - removals.add(IAstModification.Remove(sub, sub.definingScope())) - } - } - } - - program.modules.flatMap { it.statements }.filterIsInstance().forEach { block -> - if (block.containsNoCodeNorVars() && "force_output" !in block.options()) - removals.add(IAstModification.Remove(block, block.definingScope())) - } - - // remove modules that are not imported, or are empty (unless it's a library modules) - program.modules.forEach { - if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars())) - removals.add(IAstModification.Remove(it, it.definingScope())) - } - - return removals + override fun before(module: Module, parent: Node): Iterable { + return if (!module.isLibraryModule && (module.importedBy.isEmpty() || module.containsNoCodeNorVars())) + listOf(IAstModification.Remove(module, module.definingScope())) + else + noModifications } - override fun before(breakStmt: Break, parent: Node): Iterable { reportUnreachable(breakStmt, parent as INameScope) return emptyList() @@ -85,15 +65,58 @@ internal class UnusedCodeRemover(private val program: Program, } override fun after(block: Block, parent: Node): Iterable { + if("force_output" !in block.options()) { + if (block.containsNoCodeNorVars()) { + if(block.name != program.internedStringsModuleName) + errors.warn("removing unused block '${block.name}'", block.position) + return listOf(IAstModification.Remove(block, parent as INameScope)) + } + if(callgraph.unused(block)) { + errors.warn("removing unused block '${block.name}'", block.position) + return listOf(IAstModification.Remove(block, parent as INameScope)) + } + } + val removeDoubleAssignments = deduplicateAssignments(block.statements) return removeDoubleAssignments.map { IAstModification.Remove(it, block) } } override fun after(subroutine: Subroutine, parent: Node): Iterable { + val forceOutput = "force_output" in subroutine.definingBlock().options() + if (subroutine !== program.entrypoint() && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) { + if(callgraph.unused(subroutine)) { + if(!subroutine.definingModule().isLibraryModule) + errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) + return listOf(IAstModification.Remove(subroutine, subroutine.definingScope())) + } + if(subroutine.containsNoCodeNorVars()) { + if(!subroutine.definingModule().isLibraryModule) + errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) + val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope())) + callgraph.calledBy[subroutine]?.let { + for(node in it) + removals.add(IAstModification.Remove(node, node.definingScope())) + } + return removals + } + } + val removeDoubleAssignments = deduplicateAssignments(subroutine.statements) return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) } } + override fun after(decl: VarDecl, parent: Node): Iterable { + val forceOutput = "force_output" in decl.definingBlock().options() + if(!forceOutput && callgraph.unused(decl)) { + if(decl.type == VarDeclType.VAR) + errors.warn("removing unused variable '${decl.name}'", decl.position) + + return listOf(IAstModification.Remove(decl, decl.definingScope())) + } + + return noModifications + } + private fun deduplicateAssignments(statements: List): List { // removes 'duplicate' assignments that assign the same target directly after another val linesToRemove = mutableListOf() diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index c9fdd9cba..0b95722c5 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -7,6 +7,10 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstVisitor +interface ISymbolStatement { + val name: String +} + sealed class Statement : Node { abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: AstWalker, parent: Node) @@ -48,7 +52,7 @@ class Block(override val name: String, val address: Int?, override var statements: MutableList, val isInLibrary: Boolean, - override val position: Position) : Statement(), INameScope { + override val position: Position) : Statement(), INameScope, ISymbolStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { @@ -95,7 +99,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") } -data class Label(val name: String, override val position: Position) : Statement() { +data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { @@ -153,17 +157,16 @@ enum class ZeropageWish { NOT_IN_ZEROPAGE } - open class VarDecl(val type: VarDeclType, private val declaredDatatype: DataType, val zeropage: ZeropageWish, var arraysize: ArrayIndex?, - val name: String, + override val name: String, private val structName: String?, var value: Expression?, val isArray: Boolean, val autogeneratedDontRemove: Boolean, - override val position: Position) : Statement() { + override val position: Position) : Statement(), ISymbolStatement { override lateinit var parent: Node var struct: StructDecl? = null // set later (because at parse time, we only know the name) private set @@ -281,7 +284,6 @@ open class VarDecl(val type: VarDeclType, class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position) : VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position) - class ArrayIndex(var indexExpr: Expression, override val position: Position) : Node { override lateinit var parent: Node @@ -484,7 +486,6 @@ data class AssignTarget(var identifier: IdentifierReference?, fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position) } - class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() { override lateinit var parent: Node @@ -611,7 +612,6 @@ class NopStatement(override val position: Position): Statement() { override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) } - class AsmGenInfo { // This class contains various attributes that influence the assembly code generator. // Conceptually it should be part of any INameScope. @@ -637,7 +637,7 @@ class Subroutine(override val name: String, val isAsmSubroutine: Boolean, val inline: Boolean, override var statements: MutableList, - override val position: Position) : Statement(), INameScope { + override val position: Position) : Statement(), INameScope, ISymbolStatement { constructor(name: String, parameters: List, returntypes: List, statements: MutableList, inline: Boolean, position: Position) : this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position) @@ -701,7 +701,6 @@ class Subroutine(override val name: String, .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it} } - open class SubroutineParameter(val name: String, val type: DataType, override val position: Position) : Node { @@ -948,10 +947,9 @@ class WhenChoice(var values: MutableList?, // if null, th fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) } - class StructDecl(override val name: String, override var statements: MutableList, // actually, only vardecls here - override val position: Position): Statement(), INameScope { + override val position: Position): Statement(), INameScope, ISymbolStatement { override lateinit var parent: Node diff --git a/compilerAst/src/prog8/ast/walk/AstWalker.kt b/compilerAst/src/prog8/ast/walk/AstWalker.kt index c3f050a5a..aa05b7c57 100644 --- a/compilerAst/src/prog8/ast/walk/AstWalker.kt +++ b/compilerAst/src/prog8/ast/walk/AstWalker.kt @@ -76,86 +76,88 @@ interface IAstModification { abstract class AstWalker { - open fun before(addressOf: AddressOf, parent: Node): Iterable = emptyList() - open fun before(array: ArrayLiteralValue, parent: Node): Iterable = emptyList() - open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable = emptyList() - open fun before(assignTarget: AssignTarget, parent: Node): Iterable = emptyList() - open fun before(assignment: Assignment, parent: Node): Iterable = emptyList() - open fun before(block: Block, parent: Node): Iterable = emptyList() - open fun before(branchStatement: BranchStatement, parent: Node): Iterable = emptyList() - open fun before(breakStmt: Break, parent: Node): Iterable = emptyList() - open fun before(decl: VarDecl, parent: Node): Iterable = emptyList() - open fun before(directive: Directive, parent: Node): Iterable = emptyList() - open fun before(expr: BinaryExpression, parent: Node): Iterable = emptyList() - open fun before(expr: PrefixExpression, parent: Node): Iterable = emptyList() - open fun before(forLoop: ForLoop, parent: Node): Iterable = emptyList() - open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable = emptyList() - open fun before(functionCall: FunctionCall, parent: Node): Iterable = emptyList() - open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable = emptyList() - open fun before(identifier: IdentifierReference, parent: Node): Iterable = emptyList() - open fun before(ifStatement: IfStatement, parent: Node): Iterable = emptyList() - open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable = emptyList() - open fun before(jump: Jump, parent: Node): Iterable = emptyList() - open fun before(label: Label, parent: Node): Iterable = emptyList() - open fun before(memread: DirectMemoryRead, parent: Node): Iterable = emptyList() - open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable = emptyList() - open fun before(module: Module, parent: Node): Iterable = emptyList() - open fun before(nopStatement: NopStatement, parent: Node): Iterable = emptyList() - open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable = emptyList() - open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable = emptyList() - open fun before(program: Program, parent: Node): Iterable = emptyList() - open fun before(range: RangeExpr, parent: Node): Iterable = emptyList() - open fun before(untilLoop: UntilLoop, parent: Node): Iterable = emptyList() - open fun before(returnStmt: Return, parent: Node): Iterable = emptyList() - open fun before(scope: AnonymousScope, parent: Node): Iterable = emptyList() - open fun before(string: StringLiteralValue, parent: Node): Iterable = emptyList() - open fun before(structDecl: StructDecl, parent: Node): Iterable = emptyList() - open fun before(subroutine: Subroutine, parent: Node): Iterable = emptyList() - open fun before(typecast: TypecastExpression, parent: Node): Iterable = emptyList() - open fun before(whenChoice: WhenChoice, parent: Node): Iterable = emptyList() - open fun before(whenStatement: WhenStatement, parent: Node): Iterable = emptyList() - open fun before(whileLoop: WhileLoop, parent: Node): Iterable = emptyList() + protected val noModifications = emptyList() - open fun after(addressOf: AddressOf, parent: Node): Iterable = emptyList() - open fun after(array: ArrayLiteralValue, parent: Node): Iterable = emptyList() - open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable = emptyList() - open fun after(assignTarget: AssignTarget, parent: Node): Iterable = emptyList() - open fun after(assignment: Assignment, parent: Node): Iterable = emptyList() - open fun after(block: Block, parent: Node): Iterable = emptyList() - open fun after(branchStatement: BranchStatement, parent: Node): Iterable = emptyList() - open fun after(breakStmt: Break, parent: Node): Iterable = emptyList() - open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable = emptyList() - open fun after(decl: VarDecl, parent: Node): Iterable = emptyList() - open fun after(directive: Directive, parent: Node): Iterable = emptyList() - open fun after(expr: BinaryExpression, parent: Node): Iterable = emptyList() - open fun after(expr: PrefixExpression, parent: Node): Iterable = emptyList() - open fun after(forLoop: ForLoop, parent: Node): Iterable = emptyList() - open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable = emptyList() - open fun after(functionCall: FunctionCall, parent: Node): Iterable = emptyList() - open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable = emptyList() - open fun after(identifier: IdentifierReference, parent: Node): Iterable = emptyList() - open fun after(ifStatement: IfStatement, parent: Node): Iterable = emptyList() - open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable = emptyList() - open fun after(jump: Jump, parent: Node): Iterable = emptyList() - open fun after(label: Label, parent: Node): Iterable = emptyList() - open fun after(memread: DirectMemoryRead, parent: Node): Iterable = emptyList() - open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable = emptyList() - open fun after(module: Module, parent: Node): Iterable = emptyList() - open fun after(nopStatement: NopStatement, parent: Node): Iterable = emptyList() - open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable = emptyList() - open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable = emptyList() - open fun after(program: Program, parent: Node): Iterable = emptyList() - open fun after(range: RangeExpr, parent: Node): Iterable = emptyList() - open fun after(untilLoop: UntilLoop, parent: Node): Iterable = emptyList() - open fun after(returnStmt: Return, parent: Node): Iterable = emptyList() - open fun after(scope: AnonymousScope, parent: Node): Iterable = emptyList() - open fun after(string: StringLiteralValue, parent: Node): Iterable = emptyList() - open fun after(structDecl: StructDecl, parent: Node): Iterable = emptyList() - open fun after(subroutine: Subroutine, parent: Node): Iterable = emptyList() - open fun after(typecast: TypecastExpression, parent: Node): Iterable = emptyList() - open fun after(whenChoice: WhenChoice, parent: Node): Iterable = emptyList() - open fun after(whenStatement: WhenStatement, parent: Node): Iterable = emptyList() - open fun after(whileLoop: WhileLoop, parent: Node): Iterable = emptyList() + open fun before(addressOf: AddressOf, parent: Node): Iterable = noModifications + open fun before(array: ArrayLiteralValue, parent: Node): Iterable = noModifications + open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable = noModifications + open fun before(assignTarget: AssignTarget, parent: Node): Iterable = noModifications + open fun before(assignment: Assignment, parent: Node): Iterable = noModifications + open fun before(block: Block, parent: Node): Iterable = noModifications + open fun before(branchStatement: BranchStatement, parent: Node): Iterable = noModifications + open fun before(breakStmt: Break, parent: Node): Iterable = noModifications + open fun before(decl: VarDecl, parent: Node): Iterable = noModifications + open fun before(directive: Directive, parent: Node): Iterable = noModifications + open fun before(expr: BinaryExpression, parent: Node): Iterable = noModifications + open fun before(expr: PrefixExpression, parent: Node): Iterable = noModifications + open fun before(forLoop: ForLoop, parent: Node): Iterable = noModifications + open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable = noModifications + open fun before(functionCall: FunctionCall, parent: Node): Iterable = noModifications + open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable = noModifications + open fun before(identifier: IdentifierReference, parent: Node): Iterable = noModifications + open fun before(ifStatement: IfStatement, parent: Node): Iterable = noModifications + open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable = noModifications + open fun before(jump: Jump, parent: Node): Iterable = noModifications + open fun before(label: Label, parent: Node): Iterable = noModifications + open fun before(memread: DirectMemoryRead, parent: Node): Iterable = noModifications + open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable = noModifications + open fun before(module: Module, parent: Node): Iterable = noModifications + open fun before(nopStatement: NopStatement, parent: Node): Iterable = noModifications + open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable = noModifications + open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable = noModifications + open fun before(program: Program, parent: Node): Iterable = noModifications + open fun before(range: RangeExpr, parent: Node): Iterable = noModifications + open fun before(untilLoop: UntilLoop, parent: Node): Iterable = noModifications + open fun before(returnStmt: Return, parent: Node): Iterable = noModifications + open fun before(scope: AnonymousScope, parent: Node): Iterable = noModifications + open fun before(string: StringLiteralValue, parent: Node): Iterable = noModifications + open fun before(structDecl: StructDecl, parent: Node): Iterable = noModifications + open fun before(subroutine: Subroutine, parent: Node): Iterable = noModifications + open fun before(typecast: TypecastExpression, parent: Node): Iterable = noModifications + open fun before(whenChoice: WhenChoice, parent: Node): Iterable = noModifications + open fun before(whenStatement: WhenStatement, parent: Node): Iterable = noModifications + open fun before(whileLoop: WhileLoop, parent: Node): Iterable = noModifications + + open fun after(addressOf: AddressOf, parent: Node): Iterable = noModifications + open fun after(array: ArrayLiteralValue, parent: Node): Iterable = noModifications + open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable = noModifications + open fun after(assignTarget: AssignTarget, parent: Node): Iterable = noModifications + open fun after(assignment: Assignment, parent: Node): Iterable = noModifications + open fun after(block: Block, parent: Node): Iterable = noModifications + open fun after(branchStatement: BranchStatement, parent: Node): Iterable = noModifications + open fun after(breakStmt: Break, parent: Node): Iterable = noModifications + open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable = noModifications + open fun after(decl: VarDecl, parent: Node): Iterable = noModifications + open fun after(directive: Directive, parent: Node): Iterable = noModifications + open fun after(expr: BinaryExpression, parent: Node): Iterable = noModifications + open fun after(expr: PrefixExpression, parent: Node): Iterable = noModifications + open fun after(forLoop: ForLoop, parent: Node): Iterable = noModifications + open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable = noModifications + open fun after(functionCall: FunctionCall, parent: Node): Iterable = noModifications + open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable = noModifications + open fun after(identifier: IdentifierReference, parent: Node): Iterable = noModifications + open fun after(ifStatement: IfStatement, parent: Node): Iterable = noModifications + open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable = noModifications + open fun after(jump: Jump, parent: Node): Iterable = noModifications + open fun after(label: Label, parent: Node): Iterable = noModifications + open fun after(memread: DirectMemoryRead, parent: Node): Iterable = noModifications + open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable = noModifications + open fun after(module: Module, parent: Node): Iterable = noModifications + open fun after(nopStatement: NopStatement, parent: Node): Iterable = noModifications + open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable = noModifications + open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable = noModifications + open fun after(program: Program, parent: Node): Iterable = noModifications + open fun after(range: RangeExpr, parent: Node): Iterable = noModifications + open fun after(untilLoop: UntilLoop, parent: Node): Iterable = noModifications + open fun after(returnStmt: Return, parent: Node): Iterable = noModifications + open fun after(scope: AnonymousScope, parent: Node): Iterable = noModifications + open fun after(string: StringLiteralValue, parent: Node): Iterable = noModifications + open fun after(structDecl: StructDecl, parent: Node): Iterable = noModifications + open fun after(subroutine: Subroutine, parent: Node): Iterable = noModifications + open fun after(typecast: TypecastExpression, parent: Node): Iterable = noModifications + open fun after(whenChoice: WhenChoice, parent: Node): Iterable = noModifications + open fun after(whenStatement: WhenStatement, parent: Node): Iterable = noModifications + open fun after(whileLoop: WhileLoop, parent: Node): Iterable = noModifications protected val modifications = mutableListOf>() // private val modificationsReplacedNodes = mutableSetOf>() diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 8e70ec69b..ce603a242 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -129,7 +129,7 @@ Directives take care of that yourself. The program will just start running from whatever state the machine is in when the program was launched. - ``force_output`` (in a block) will force the block to be outputted in the final program. - Can be useful to make sure some data is generated that would otherwise be discarded because it's not referenced (such as sprite data). + Can be useful to make sure some data is generated that would otherwise be discarded because the compiler thinks it's not referenced (such as sprite data) - ``align_word`` (in a block) will make the assembler align the start address of this block on a word boundary in memory (so, an even memory address). - ``align_page`` (in a block) will make the assembler align the start address of this block on a page boundary in memory (so, the LSB of the address is 0). diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 80b226d18..052adbebd 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -2,6 +2,10 @@ TODO ==== +- fix crash parent node mismatch with -noopt (textelite) + +- implement new 'unused' in CallGraph for more node types + - allow inlining of subroutines with params - optimize several inner loops in gfx2 - hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine) diff --git a/examples/test.p8 b/examples/test.p8 index edc11d186..228e21405 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import textio %zeropage basicsafe main { @@ -9,6 +8,26 @@ main { ; Comment here 4,5,6 ] - txt.print_ub(len(array)) + ubyte zz = len(array) + + ubyte foobar + empty2() + + %asm {{ + lda foobar + }} + } + + sub nix() { + } + + sub empty2() { + } +} + +derp { + sub nix2() { + ubyte zz + zz++ } }