mirror of
https://github.com/irmen/prog8.git
synced 2024-12-25 23:29:55 +00:00
remove unused variables, subroutines, blocks
This commit is contained in:
parent
ff1294207e
commit
d83f49d84f
@ -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<Block>(this)!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -494,7 +512,7 @@ interface INameScope {
|
||||
}
|
||||
}
|
||||
|
||||
private object ParentSentinel : Node {
|
||||
object ParentSentinel : Node {
|
||||
override val position = Position("<<sentinel>>", 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<String>, 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())
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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("<<global>>", 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)
|
||||
|
@ -6,10 +6,11 @@ import prog8.compiler.loadAsmIncludeFile
|
||||
|
||||
class CallGraph(private val program: Program): IAstProcessor {
|
||||
|
||||
private val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
private val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
private val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
private val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
val usedSymbols = mutableSetOf<IStatement>()
|
||||
|
||||
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) {
|
||||
|
@ -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<INameScope>()
|
||||
|
||||
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<Subroutine>()
|
||||
val entrypoint = program.entrypoint()
|
||||
@ -109,9 +106,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
}
|
||||
|
||||
val removeBlocks = mutableSetOf<Block>()
|
||||
// TODO remove blocks that have no incoming references
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().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<Module>()
|
||||
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<IStatement>): MutableList<Int> {
|
||||
// removes 'duplicate' assignments that assign the isSameAs target
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user