mirror of
https://github.com/irmen/prog8.git
synced 2025-02-10 14:32:20 +00:00
callgraph
This commit is contained in:
parent
0032235933
commit
619fa9b65e
@ -265,7 +265,7 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
|
|||||||
// optimize the parse tree
|
// optimize the parse tree
|
||||||
println("Optimizing...")
|
println("Optimizing...")
|
||||||
|
|
||||||
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
|
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
||||||
remover.visit(programAst)
|
remover.visit(programAst)
|
||||||
remover.applyModifications()
|
remover.applyModifications()
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
|
|||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
inliner.applyModifications()
|
inliner.applyModifications()
|
||||||
inliner.fixCallsToInlinedSubroutines()
|
inliner.fixCallsToInlinedSubroutines()
|
||||||
val remover2 = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
|
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
|
||||||
remover2.visit(programAst)
|
remover2.visit(programAst)
|
||||||
remover2.applyModifications()
|
remover2.applyModifications()
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ private fun postprocessAst(programAst: Program, errors: IErrorReporter, compiler
|
|||||||
programAst.variousCleanups(errors)
|
programAst.variousCleanups(errors)
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
|
val callGraph = CallGraph(programAst)
|
||||||
callGraph.checkRecursiveCalls(errors)
|
callGraph.checkRecursiveCalls(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.verifyFunctionArgTypes()
|
programAst.verifyFunctionArgTypes()
|
||||||
|
@ -9,50 +9,25 @@ import prog8.ast.expressions.FunctionCall
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
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 imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() }
|
||||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
visit(program)
|
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) {
|
override fun visit(directive: Directive) {
|
||||||
val thisModule = directive.definingModule()
|
val thisModule = directive.definingModule()
|
||||||
if (directive.directive == "%import") {
|
if (directive.directive == "%import") {
|
||||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
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)
|
super.visit(directive)
|
||||||
@ -102,53 +77,6 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
|
|||||||
super.visit(jump)
|
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) {
|
fun checkRecursiveCalls(errors: IErrorReporter) {
|
||||||
val cycles = recursionCycles()
|
val cycles = recursionCycles()
|
||||||
if(cycles.any()) {
|
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
|
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
|
inline fun unused(label: Label) = false // just always output labels
|
||||||
|
|
||||||
fun unused(stmt: ISymbolStatement): Boolean {
|
fun unused(stmt: ISymbolStatement): Boolean {
|
||||||
|
@ -14,18 +14,16 @@ import prog8.ast.walk.AstWalker
|
|||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
internal class UnusedCodeRemover(private val program: Program,
|
internal class UnusedCodeRemover(private val program: Program,
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
private val compTarget: ICompilationTarget,
|
private val compTarget: ICompilationTarget): AstWalker() {
|
||||||
asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
|
|
||||||
|
|
||||||
private val callgraph = CallGraph(program, asmFileLoader)
|
private val callgraph = CallGraph(program)
|
||||||
|
|
||||||
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
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()))
|
listOf(IAstModification.Remove(module, module.definingScope()))
|
||||||
else
|
else
|
||||||
noModifications
|
noModifications
|
||||||
|
@ -274,9 +274,9 @@ class Program(val name: String,
|
|||||||
init {
|
init {
|
||||||
// insert a container module for all interned strings later
|
// insert a container module for all interned strings later
|
||||||
if(modules.firstOrNull()?.name != internedStringsModuleName) {
|
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)
|
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.statements.add(block)
|
||||||
internedStringsModule.linkParents(this)
|
internedStringsModule.linkParents(this)
|
||||||
internedStringsModule.program = this
|
internedStringsModule.program = this
|
||||||
@ -339,8 +339,6 @@ class Module(override val name: String,
|
|||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
lateinit var program: Program
|
lateinit var program: Program
|
||||||
val importedBy = mutableListOf<Module>()
|
|
||||||
val imports = mutableSetOf<Module>()
|
|
||||||
|
|
||||||
val loadAddress: Int by lazy {
|
val loadAddress: Int by lazy {
|
||||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user