remove unused variables, subroutines, blocks

This commit is contained in:
Irmen de Jong 2019-07-02 04:20:17 +02:00
parent ff1294207e
commit d83f49d84f
8 changed files with 136 additions and 38 deletions

View File

@ -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())
}

View File

@ -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) {

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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) {

View File

@ -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>()

View File

@ -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