Skip to content

Commit

Permalink
remove unused variables, subroutines, blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
irmen committed Jul 2, 2019
1 parent ff12942 commit d83f49d
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 38 deletions.
33 changes: 28 additions & 5 deletions compiler/src/prog8/ast/AST.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -316,7 +328,7 @@ interface IAstProcessor {
}

fun process(addressOf: AddressOf): IExpression {
process(addressOf.identifier)
addressOf.identifier.process(this)
return addressOf
}

Expand Down Expand Up @@ -388,6 +400,12 @@ interface IStatement : Node {
}

val expensiveToInline: Boolean

fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}


Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -2115,6 +2134,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
it.identifier().text,
null,
it.ARRAYSIG()!=null || it.arrayindex()!=null,
false,
it.toPosition())
}

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

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

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

Expand Down
1 change: 1 addition & 0 deletions compiler/src/prog8/ast/AstChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/prog8/ast/AstIdentifiersChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/prog8/ast/StmtReorderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/prog8/astvm/VariablesCreator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 51 additions & 4 deletions compiler/src/prog8/optimizing/CallGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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") {
Expand All @@ -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) {
Expand Down
50 changes: 37 additions & 13 deletions compiler/src/prog8/optimizing/StatementOptimizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -109,42 +106,51 @@ 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)
}

if (removeBlocks.isNotEmpty()) {
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()))
removeModules.add(it)
}

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)
}
Expand All @@ -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>()
Expand Down
Loading

0 comments on commit d83f49d

Please sign in to comment.