callgraph

This commit is contained in:
Irmen de Jong 2021-04-05 17:57:07 +02:00
parent 0032235933
commit 619fa9b65e
4 changed files with 17 additions and 89 deletions

View File

@ -265,7 +265,7 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
val remover = UnusedCodeRemover(programAst, errors, compTarget)
remover.visit(programAst)
remover.applyModifications()
@ -286,7 +286,7 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
if(errors.noErrors()) {
inliner.applyModifications()
inliner.fixCallsToInlinedSubroutines()
val remover2 = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
remover2.visit(programAst)
remover2.applyModifications()
}
@ -300,7 +300,7 @@ private fun postprocessAst(programAst: Program, errors: IErrorReporter, compiler
programAst.variousCleanups(errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()

View File

@ -9,50 +9,25 @@ import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
val imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() }
val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() }
init {
visit(program)
}
override fun visit(program: Program) {
super.visit(program)
program.modules.forEach {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(importedBy.getValue(it))
it.imports.addAll(imports.getValue(it))
}
val rootmodule = program.modules.first()
rootmodule.importedBy.add(rootmodule) // don't discard root module
}
override fun visit(directive: Directive) {
val thisModule = directive.definingModule()
if (directive.directive == "%import") {
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
}
super.visit(directive)
@ -102,53 +77,6 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
super.visit(jump)
}
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr, bra)
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
val jumptarget = matches.groups[2]?.value
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target = matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
}
}
}
}
}
fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
@ -216,6 +144,10 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
return false // TODO implement unused check for struct decls, also check inline asm
}
fun unused(module: Module): Boolean {
return false // TODO
}
inline fun unused(label: Label) = false // just always output labels
fun unused(stmt: ISymbolStatement): Boolean {

View File

@ -14,18 +14,16 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
private val compTarget: ICompilationTarget): AstWalker() {
private val callgraph = CallGraph(program, asmFileLoader)
private val callgraph = CallGraph(program)
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibraryModule && (module.importedBy.isEmpty() || module.containsNoCodeNorVars()))
return if (!module.isLibraryModule && (module.containsNoCodeNorVars() || callgraph.unused(module)))
listOf(IAstModification.Remove(module, module.definingScope()))
else
noModifications

View File

@ -274,9 +274,9 @@ class Program(val name: String,
init {
// insert a container module for all interned strings later
if(modules.firstOrNull()?.name != internedStringsModuleName) {
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, false, Path.of(""))
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, true, Path.of(""))
modules.add(0, internedStringsModule)
val block = Block(internedStringsModuleName, null, mutableListOf(), false, Position.DUMMY)
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
internedStringsModule.linkParents(this)
internedStringsModule.program = this
@ -339,8 +339,6 @@ class Module(override val name: String,
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0