diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index dab8bd641..ff4d7be27 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -186,11 +186,17 @@ interface IAstProcessor { } fun process(functionCall: FunctionCall): IExpression { + val newtarget = functionCall.target.process(this) + if(newtarget is IdentifierReference) + functionCall.target = newtarget functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList() return functionCall } fun process(functionCallStatement: FunctionCallStatement): IStatement { + val newtarget = functionCallStatement.target.process(this) + if(newtarget is IdentifierReference) + functionCallStatement.target = newtarget functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList() return functionCallStatement } @@ -202,6 +208,12 @@ interface IAstProcessor { } fun process(jump: Jump): IStatement { + if(jump.identifier!=null) { + val ident = jump.identifier.process(this) + if(ident is IdentifierReference && ident!==jump.identifier) { + return Jump(null, ident, null, jump.position) + } + } return jump } @@ -230,7 +242,7 @@ interface IAstProcessor { } fun process(literalValue: LiteralValue): LiteralValue { - if(literalValue.arrayvalue!=null && literalValue.heapId==null) { + if(literalValue.arrayvalue!=null) { for(av in literalValue.arrayvalue.withIndex()) { val newvalue = av.value.process(this) literalValue.arrayvalue[av.index] = newvalue @@ -316,7 +328,7 @@ interface IAstProcessor { } fun process(addressOf: AddressOf): IExpression { - process(addressOf.identifier) + addressOf.identifier.process(this) return addressOf } @@ -388,6 +400,12 @@ interface IStatement : Node { } val expensiveToInline: Boolean + + fun definingBlock(): Block { + if(this is Block) + return this + return findParentNode(this)!! + } } @@ -494,7 +512,7 @@ interface INameScope { } } -private object ParentSentinel : Node { +object ParentSentinel : Node { override val position = Position("<>", 0, 0, 0) override var parent: Node = this override fun linkParents(parent: Node) {} @@ -744,6 +762,7 @@ class VarDecl(val type: VarDeclType, val name: String, var value: IExpression?, val isArray: Boolean, + val autoGenerated: Boolean, override val position: Position) : IStatement { override lateinit var parent: Node override val expensiveToInline @@ -787,7 +806,7 @@ class VarDecl(val type: VarDeclType, DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue=0.0, position=position) else -> throw FatalAstException("can only set a default value for a numeric type") } - val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, position) + val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position) if(parent!=null) decl.linkParents(parent) return decl @@ -1665,7 +1684,7 @@ data class IdentifierReference(val nameInSource: List, override val posi ?: throw UndefinedSymbolError(this) val vardecl = node as? VarDecl if(vardecl==null) { - throw ExpressionError("name must be a constant, instead of: ${node::class.simpleName}", position) + return null } else if(vardecl.type!=VarDeclType.CONST) { return null } @@ -2115,6 +2134,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { it.identifier().text, null, it.ARRAYSIG()!=null || it.arrayindex()!=null, + false, it.toPosition()) } @@ -2127,6 +2147,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { vd.identifier().text, it.expression().toAst(), vd.ARRAYSIG()!=null || vd.arrayindex()!=null, + false, it.toPosition()) } @@ -2140,6 +2161,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { vd.identifier().text, cvarinit.expression().toAst(), vd.ARRAYSIG()!=null || vd.arrayindex()!=null, + false, cvarinit.toPosition()) } @@ -2153,6 +2175,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { vd.identifier().text, mvarinit.expression().toAst(), vd.ARRAYSIG()!=null || vd.arrayindex()!=null, + false, mvarinit.toPosition()) } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 559819b64..293739e10 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -90,6 +90,7 @@ private class AstChecker(private val program: Program, is InlineAssembly -> true is INameScope -> true is VariableInitializationAssignment -> true + is NopStatement -> true else -> false } if (!ok) { diff --git a/compiler/src/prog8/ast/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/AstIdentifiersChecker.kt index 6b1bad34b..1c6e6a228 100644 --- a/compiler/src/prog8/ast/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/AstIdentifiersChecker.kt @@ -138,7 +138,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro subroutine.parameters .filter { it.name !in namesInSub } .forEach { - val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, false, subroutine.position) + val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, + isArray = false, autoGenerated = true, position = subroutine.position) vardecl.linkParents(subroutine) subroutine.statements.add(0, vardecl) } @@ -176,7 +177,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) if(existing==null) { // create the local scoped for loop variable itself - val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, false, forLoop.loopVar.position) + val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, + isArray = false, autoGenerated = true, position = forLoop.loopVar.position) vardecl.linkParents(forLoop.body) forLoop.body.statements.add(0, vardecl) forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' @@ -188,7 +190,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) if(existing==null) { // create loop iteration counter variable (without value, to avoid an assignment) - val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, false, forLoop.loopVar.position) + val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, + isArray = false, autoGenerated = true, position = forLoop.loopVar.position) vardecl.linkParents(forLoop.body) forLoop.body.statements.add(0, vardecl) forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' @@ -236,7 +239,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro if(literalValue.heapId!=null && literalValue.parent !is VarDecl) { // a literal value that's not declared as a variable, which refers to something on the heap. // we need to introduce an auto-generated variable for this to be able to refer to the value! - val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, false, literalValue.position) + val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, + isArray = false, autoGenerated = false, position = literalValue.position) anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable) } return super.process(literalValue) diff --git a/compiler/src/prog8/ast/StmtReorderer.kt b/compiler/src/prog8/ast/StmtReorderer.kt index 595d0e90f..e5a18d310 100644 --- a/compiler/src/prog8/ast/StmtReorderer.kt +++ b/compiler/src/prog8/ast/StmtReorderer.kt @@ -412,7 +412,8 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope) pointerExpr.linkParents(arglist[argparam.first.index].parent) arglist[argparam.first.index] = pointerExpr // add a vardecl so that the autovar can be resolved in later lookups - val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue, false, strvalue.position) + val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue, + isArray = false, autoGenerated = false, position=strvalue.position) addVarDecl(strvalue.definingScope(), variable) } } diff --git a/compiler/src/prog8/astvm/VariablesCreator.kt b/compiler/src/prog8/astvm/VariablesCreator.kt index 40b7b0975..c01f55202 100644 --- a/compiler/src/prog8/astvm/VariablesCreator.kt +++ b/compiler/src/prog8/astvm/VariablesCreator.kt @@ -13,9 +13,9 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0)) val globalpos = Position("<>", 0, 0, 0) - val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), false, globalpos) - val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), false, globalpos) - val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), false, globalpos) + val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos) + val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = true, position = globalpos) + val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos) vdA.linkParents(program.namespace) vdX.linkParents(program.namespace) vdY.linkParents(program.namespace) diff --git a/compiler/src/prog8/optimizing/CallGraph.kt b/compiler/src/prog8/optimizing/CallGraph.kt index a2dcd6844..556283bb2 100644 --- a/compiler/src/prog8/optimizing/CallGraph.kt +++ b/compiler/src/prog8/optimizing/CallGraph.kt @@ -6,10 +6,11 @@ import prog8.compiler.loadAsmIncludeFile class CallGraph(private val program: Program): IAstProcessor { - private val modulesImporting = mutableMapOf>().withDefault { mutableListOf() } - private val modulesImportedBy = mutableMapOf>().withDefault { mutableListOf() } - private val subroutinesCalling = mutableMapOf>().withDefault { mutableListOf() } - private val subroutinesCalledBy = mutableMapOf>().withDefault { mutableListOf() } + val modulesImporting = mutableMapOf>().withDefault { mutableListOf() } + val modulesImportedBy = mutableMapOf>().withDefault { mutableListOf() } + val subroutinesCalling = mutableMapOf>().withDefault { mutableListOf() } + val subroutinesCalledBy = mutableMapOf>().withDefault { mutableListOf() } + val usedSymbols = mutableSetOf() init { process(program) @@ -51,6 +52,15 @@ class CallGraph(private val program: Program): IAstProcessor { rootmodule.importedBy.add(rootmodule) // don't discard root module } + override fun process(block: Block): IStatement { + if(block.definingModule().isLibraryModule) { + // make sure the block is not removed + addNodeAndParentScopes(block) + } + + return super.process(block) + } + override fun process(directive: Directive): IStatement { val thisModule = directive.definingModule() if(directive.directive=="%import") { @@ -66,6 +76,43 @@ class CallGraph(private val program: Program): IAstProcessor { return super.process(directive) } + override fun process(identifier: IdentifierReference): IExpression { + // track symbol usage + val target = identifier.targetStatement(this.program.namespace) + if(target!=null) { + addNodeAndParentScopes(target) + } + return super.process(identifier) + } + + private fun addNodeAndParentScopes(stmt: IStatement) { + usedSymbols.add(stmt) + var node: Node=stmt + do { + if(node is INameScope && node is IStatement) { + usedSymbols.add(node) + } + node=node.parent + } while (node !is Module && node !is ParentSentinel) + } + + override fun process(subroutine: Subroutine): IStatement { + if((subroutine.name=="start" && subroutine.definingScope().name=="main") + || subroutine.name==initvarsSubName || subroutine.definingModule().isLibraryModule) { + // make sure the entrypoint is mentioned in the used symbols + addNodeAndParentScopes(subroutine) + } + return super.process(subroutine) + } + + override fun process(decl: VarDecl): IStatement { + if(decl.autoGenerated || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) { + // make sure autogenerated vardecls are in the used symbols + addNodeAndParentScopes(decl) + } + return super.process(decl) + } + override fun process(functionCall: FunctionCall): IExpression { val otherSub = functionCall.target.targetSubroutine(program.namespace) if(otherSub!=null) { diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index b053fc0ed..eafc7e222 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -8,7 +8,6 @@ import kotlin.math.floor /* todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers) - todo: implement usage counters for variables (locals and heap), blocks. Remove if count is zero. 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) + print warning about this */ @@ -18,13 +17,13 @@ internal class StatementOptimizer(private val program: Program, private val opti var scopesToFlatten = mutableListOf() private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } + private val callgraph = CallGraph(program) companion object { private var generatedLabelSequenceNumber = 0 } override fun process(program: Program) { - val callgraph = CallGraph(program) removeUnusedCode(callgraph) if(optimizeInlining) { inlineSubroutines(callgraph) @@ -90,8 +89,6 @@ internal class StatementOptimizer(private val program: Program, private val opti } private fun removeUnusedCode(callgraph: CallGraph) { - // TODO remove unused variables (local and global) - // remove all subroutines that aren't called, or are empty val removeSubroutines = mutableSetOf() val entrypoint = program.entrypoint() @@ -109,9 +106,8 @@ internal class StatementOptimizer(private val program: Program, private val opti } val removeBlocks = mutableSetOf() - // TODO remove blocks that have no incoming references program.modules.flatMap { it.statements }.filterIsInstance().forEach { block -> - if (block.containsNoCodeNorVars()) + if (block.containsNoCodeNorVars() && "force_output" !in block.options()) removeBlocks.add(block) } @@ -119,7 +115,7 @@ internal class StatementOptimizer(private val program: Program, private val opti removeBlocks.forEach { it.definingScope().remove(it) } } - // remove modules that are not imported, or are empty + // remove modules that are not imported, or are empty (unless it's a library modules) val removeModules = mutableSetOf() program.modules.forEach { if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars())) @@ -127,24 +123,34 @@ internal class StatementOptimizer(private val program: Program, private val opti } if (removeModules.isNotEmpty()) { - println("[debug] removing ${removeModules.size} empty/unused modules") program.modules.removeAll(removeModules) } } override fun process(block: Block): IStatement { - if(block.containsNoCodeNorVars()) { - optimizationsDone++ - return NopStatement(block.position) + if("force_output" !in block.options()) { + if (block.containsNoCodeNorVars()) { + optimizationsDone++ + printWarning("removing empty block '${block.name}'", block.position) + return NopStatement(block.position) + } + + if (block !in callgraph.usedSymbols) { + optimizationsDone++ + printWarning("removing unused block '${block.name}'", block.position) + return NopStatement(block.position) // remove unused block + } } + return super.process(block) } override fun process(subroutine: Subroutine): IStatement { super.process(subroutine) - - if(subroutine.asmAddress==null) { + val forceOutput = "force_output" in subroutine.definingBlock().options() + if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.containsNoCodeNorVars()) { + printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position) optimizationsDone++ return NopStatement(subroutine.position) } @@ -167,9 +173,27 @@ internal class StatementOptimizer(private val program: Program, private val opti } + if(subroutine !in callgraph.usedSymbols && !forceOutput) { + printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position) + optimizationsDone++ + return NopStatement(subroutine.position) // remove unused subroutine + } + return subroutine } + override fun process(decl: VarDecl): IStatement { + val forceOutput = "force_output" in decl.definingBlock().options() + if(decl !in callgraph.usedSymbols && !forceOutput) { + if(decl.type!=VarDeclType.CONST) + printWarning("removing unused variable '${decl.name}'", decl.position) + optimizationsDone++ + return NopStatement(decl.position) // remove unused variable + } + + return super.process(decl) + } + private fun deduplicateAssignments(statements: List): MutableList { // removes 'duplicate' assignments that assign the isSameAs target val linesToRemove = mutableListOf() diff --git a/examples/test.p8 b/examples/test.p8 index 805fe1b21..63e8897c4 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,21 +6,19 @@ sub start() { - foo(1) - } + ubyte x = 99 - sub foo(ubyte param1) { + return - sub subsub() { + startqqq: - } - - sub param1() { + sub startzzz() { + if_cc goto startqqq + c64.EXTCOL++ } } - ; for ubyte y in 0 to 3 { ; for ubyte x in 0 to 10 { ; ubyte product = x*y