diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 4d2225b3c..efed132da 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -496,6 +496,8 @@ class Module(override val name: String, val source: Path) : Node, INameScope { override lateinit var parent: Node lateinit var program: Program + val imports = mutableSetOf() + val importedBy = mutableSetOf() override fun linkParents(parent: Node) { this.parent=parent @@ -1625,7 +1627,7 @@ class FunctionCallStatement(override var target: IdentifierReference, override fun process(processor: IAstProcessor) = processor.process(this) override fun toString(): String { - return "FunctionCall(target=$target, pos=$position)" + return "FunctionCallStatement(target=$target, pos=$position)" } } @@ -1690,6 +1692,9 @@ class Subroutine(override val name: String, override var statements: MutableList, override val position: Position) : IStatement, INameScope { override lateinit var parent: Node + val calledBy = mutableSetOf() + val calls = mutableSetOf() + val scopedname: String by lazy { makeScopedName(name) } override fun linkParents(parent: Node) { diff --git a/compiler/src/prog8/optimizing/CallGraphBuilder.kt b/compiler/src/prog8/optimizing/CallGraphBuilder.kt new file mode 100644 index 000000000..07d09bae4 --- /dev/null +++ b/compiler/src/prog8/optimizing/CallGraphBuilder.kt @@ -0,0 +1,90 @@ +package prog8.optimizing + +import prog8.ast.* + + +class CallGraphBuilder(private val program: Program): IAstProcessor { + + private val modulesImporting = mutableMapOf>().withDefault { mutableSetOf() } + private val modulesImportedBy = mutableMapOf>().withDefault { mutableSetOf() } + private val subroutinesCalling = mutableMapOf>().withDefault { mutableSetOf() } + private val subroutinesCalledBy = mutableMapOf>().withDefault { mutableSetOf() } + + private 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 process(program: Program) { + super.process(program) + + program.modules.forEach { + it.importedBy.clear() + it.imports.clear() + + it.importedBy.addAll(modulesImportedBy.getValue(it)) + it.imports.addAll(modulesImporting.getValue(it)) + + if(it.isLibraryModule && it.importedBy.isEmpty()) + it.importedBy.add(it) // don't discard auto-imported library module + + forAllSubroutines(it) { sub -> + sub.calledBy.clear() + sub.calls.clear() + + sub.calledBy.addAll(subroutinesCalledBy.getValue(sub)) + sub.calls.addAll(subroutinesCalling.getValue(sub)) + } + } + + val rootmodule = program.modules.first() + rootmodule.importedBy.add(rootmodule) // don't discard root module + } + + override fun process(directive: Directive): IStatement { + if(directive.directive=="%import") { + val importedModule: Module = program.modules.single { it.name==directive.args[0].name } + val thisModule = directive.definingModule() + modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule) + modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule) + } + return super.process(directive) + } + + override fun process(functionCall: FunctionCall): IExpression { + val otherSub = functionCall.target.targetSubroutine(program.namespace) + if(otherSub!=null) { + val thisSub = functionCall.definingScope() as Subroutine + subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) + subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) + } + return super.process(functionCall) + } + + override fun process(functionCallStatement: FunctionCallStatement): IStatement { + val otherSub = functionCallStatement.target.targetSubroutine(program.namespace) + if(otherSub!=null) { + val thisSub = functionCallStatement.definingScope() as Subroutine + subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) + subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) + } + return super.process(functionCallStatement) + } + + override fun process(jump: Jump): IStatement { + val otherSub = jump.identifier?.targetSubroutine(program.namespace) + if(otherSub!=null) { + val thisSub = jump.definingScope() as Subroutine + subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) + subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) + } + return super.process(jump) + } +} diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 519032a67..9e272316c 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -11,7 +11,7 @@ import kotlin.math.floor todo: implement usage counters for blocks, variables, subroutines, heap variables. Then: - todo remove unused: subroutines, blocks, modules (in this order) + todo remove unused: subroutines, blocks (in this order) todo remove unused: variable declarations todo remove unused strings and arrays from the heap todo inline subroutines that are called exactly once (regardless of their size) @@ -28,6 +28,24 @@ class StatementOptimizer(private val program: Program) : IAstProcessor { private set private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } + + override fun process(program: Program) { + val callgraph = CallGraphBuilder(program) + callgraph.process(program) + + // remove modules that are not imported, or are empty + val removeModules = mutableSetOf() + program.modules.forEach { + if(it.importedBy.isEmpty() || it.isEmpty()) { + printWarning("discarding empty or unused module: ${it.name}") + removeModules.add(it) + } + } + program.modules.removeAll(removeModules) + + super.process(program) + } + override fun process(block: Block): IStatement { if(block.statements.isEmpty()) { // remove empty block @@ -38,6 +56,7 @@ class StatementOptimizer(private val program: Program) : IAstProcessor { } override fun process(subroutine: Subroutine): IStatement { + println("STMT OPTIMIZE $subroutine ${subroutine.calledBy} ${subroutine.calls}") super.process(subroutine) if(subroutine.asmAddress==null) {