callgraph fixed scanning asm subroutines, and deletion of unused subs and modules

This commit is contained in:
Irmen de Jong 2019-06-21 01:47:07 +02:00
parent b374af3526
commit 7de7d5234f
9 changed files with 179 additions and 86 deletions

View File

@ -10,6 +10,7 @@ import prog8.optimizing.simplifyExpressions
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule import prog8.parser.importLibraryModule
import prog8.parser.importModule import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.lang.Exception import java.lang.Exception
@ -73,7 +74,7 @@ private fun compileMain(args: Array<String>) {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs // import main module and everything it needs
println("Parsing...") println("Parsing...")
val programAst = Program(filepath.fileName.toString(), mutableListOf()) val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath) importModule(programAst, filepath)
val compilerOptions = determineCompilationOptions(programAst) val compilerOptions = determineCompilationOptions(programAst)
@ -187,17 +188,17 @@ private fun compileMain(args: Array<String>) {
fun determineCompilationOptions(program: Program): CompilationOptions { fun determineCompilationOptions(program: Program): CompilationOptions {
val moduleAst = program.modules.first() val mainModule = program.modules.first()
val options = moduleAst.statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet() val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%launcher" } val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
moduleAst.loadAddress = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%address" } mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0 as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
val floatsEnabled = options.any { it.name == "enable_floats" } val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType = val zpType: ZeropageType =
if (zpoption == null) if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
@ -208,7 +209,7 @@ fun determineCompilationOptions(program: Program): CompilationOptions {
ZeropageType.KERNALSAFE ZeropageType.KERNALSAFE
// error will be printed by the astchecker // error will be printed by the astchecker
} }
val zpReserved = moduleAst.statements val zpReserved = mainModule.statements
.asSequence() .asSequence()
.filter { it is Directive && it.directive == "%zpreserved" } .filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args } .map { (it as Directive).args }

View File

@ -298,6 +298,10 @@ interface IAstProcessor {
process(addressOf.identifier) process(addressOf.identifier)
return addressOf return addressOf
} }
fun process(inlineAssembly: InlineAssembly): IStatement {
return inlineAssembly
}
} }
@ -312,6 +316,8 @@ interface Node {
return findParentNode<Module>(this)!! return findParentNode<Module>(this)!!
} }
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
fun definingScope(): INameScope { fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this) val scope = findParentNode<INameScope>(this)
if(scope!=null) { if(scope!=null) {
@ -384,12 +390,12 @@ interface INameScope {
is WhileLoop -> subscopes[stmt.body.name] = stmt.body is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> { is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.isNotEmpty()) if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart subscopes[stmt.elsepart.name] = stmt.elsepart
} }
is IfStatement -> { is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.isNotEmpty()) if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart subscopes[stmt.elsepart.name] = stmt.elsepart
} }
} }
@ -445,8 +451,8 @@ interface INameScope {
} }
} }
fun isEmpty() = statements.isEmpty() fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun isNotEmpty() = statements.isNotEmpty() fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: IStatement) { fun remove(stmt: IStatement) {
val removed = statements.remove(stmt) val removed = statements.remove(stmt)
@ -486,6 +492,17 @@ class Program(val name: String, val modules: MutableList<Module>) {
val loadAddress: Int val loadAddress: Int
get() = modules.first().loadAddress get() = modules.first().loadAddress
fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
}
}
} }
@ -617,19 +634,6 @@ open class Return(var values: List<IExpression>, override val position: Position
override fun toString(): String { override fun toString(): String {
return "Return(values: $values, pos=$position)" return "Return(values: $values, pos=$position)"
} }
fun definingSubroutine(): Subroutine? {
var scope = definingScope()
while(scope !is GlobalNamespace) {
if(scope is Subroutine)
return scope
val parent = scope.parent
if(parent is Subroutine)
return parent
scope = parent.definingScope()
}
return null
}
} }
@ -1639,7 +1643,7 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
this.parent = parent this.parent = parent
} }
override fun process(processor: IAstProcessor) = this override fun process(processor: IAstProcessor) = processor.process(this)
} }
@ -1692,7 +1696,7 @@ class Subroutine(override val name: String,
override var statements: MutableList<IStatement>, override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope { override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node override lateinit var parent: Node
val calledBy = mutableSetOf<Subroutine>() val calledBy = mutableSetOf<INameScope>()
val calls = mutableSetOf<Subroutine>() val calls = mutableSetOf<Subroutine>()
val scopedname: String by lazy { makeScopedName(name) } val scopedname: String by lazy { makeScopedName(name) }

View File

@ -148,7 +148,7 @@ private class AstChecker(private val program: Program,
} }
override fun process(forLoop: ForLoop): IStatement { override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty()) if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position) printWarning("for loop body is empty", forLoop.position)
if(!forLoop.iterable.isIterable(program)) { if(!forLoop.iterable.isIterable(program)) {

View File

@ -170,7 +170,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
} else if(forLoop.loopVar!=null) { } else if(forLoop.loopVar!=null) {
val varName = forLoop.loopVar.nameInSource.last() val varName = forLoop.loopVar.nameInSource.last()
if(forLoop.decltype!=null) { if(forLoop.decltype!=null) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create the local scoped for loop variable itself // create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, false, varName, null, forLoop.loopVar.position) val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, false, varName, null, forLoop.loopVar.position)
@ -182,7 +182,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
} }
if(forLoop.iterable !is RangeExpr) { if(forLoop.iterable !is RangeExpr) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment) // create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, false, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position) val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, false, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position)

View File

@ -191,7 +191,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
return r return r
} else { } else {
// asmsub // asmsub
if(subroutine.isNotEmpty()) if(subroutine.containsCodeOrVars())
throw CompilerException("kernel subroutines (with memory address) can't have a body: $subroutine") throw CompilerException("kernel subroutines (with memory address) can't have a body: $subroutine")
prog.memoryPointer(subroutine.scopedname, subroutine.asmAddress, DataType.UBYTE) // the datatype is a bit of a dummy in this case prog.memoryPointer(subroutine.scopedname, subroutine.asmAddress, DataType.UBYTE) // the datatype is a bit of a dummy in this case
@ -426,7 +426,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
* if the branch statement just contains jumps, more efficient code is generated. * if the branch statement just contains jumps, more efficient code is generated.
* (just the appropriate branching instruction is outputted!) * (just the appropriate branching instruction is outputted!)
*/ */
if(branch.elsepart.isEmpty() && branch.truepart.isEmpty()) if(branch.elsepart.containsNoCodeNorVars() && branch.truepart.containsNoCodeNorVars())
return return
fun branchOpcode(branch: BranchStatement, complement: Boolean) = fun branchOpcode(branch: BranchStatement, complement: Boolean) =
@ -470,7 +470,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
val labelElse = makeLabel(branch, "else") val labelElse = makeLabel(branch, "else")
val labelEnd = makeLabel(branch, "end") val labelEnd = makeLabel(branch, "end")
val opcode = branchOpcode(branch, true) val opcode = branchOpcode(branch, true)
if (branch.elsepart.isEmpty()) { if (branch.elsepart.containsNoCodeNorVars()) {
prog.instr(opcode, callLabel = labelEnd) prog.instr(opcode, callLabel = labelEnd)
translate(branch.truepart) translate(branch.truepart)
prog.label(labelEnd) prog.label(labelEnd)
@ -535,7 +535,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt") else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
} }
val labelEnd = makeLabel(stmt, "end") val labelEnd = makeLabel(stmt, "end")
if(stmt.elsepart.isEmpty()) { if(stmt.elsepart.containsNoCodeNorVars()) {
prog.instr(conditionJumpOpcode, callLabel = labelEnd) prog.instr(conditionJumpOpcode, callLabel = labelEnd)
translate(stmt.truepart) translate(stmt.truepart)
prog.label(labelEnd) prog.label(labelEnd)
@ -1620,7 +1620,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
} }
private fun translate(loop: ForLoop) { private fun translate(loop: ForLoop) {
if(loop.body.isEmpty()) return if(loop.body.containsNoCodeNorVars()) return
prog.line(loop.position) prog.line(loop.position)
val loopVarName: String val loopVarName: String
val loopVarDt: DataType val loopVarDt: DataType
@ -2154,18 +2154,7 @@ internal class Compiler(private val program: Program): IAstProcessor {
val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else "" val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else ""
val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else "" val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else ""
val filename=args[0].str!! val filename=args[0].str!!
val sourcecode = val sourcecode = loadAsmIncludeFile(filename, source)
if(filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8)) ?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
} else {
// first try in the same folder as where the containing file was imported from
val sib = source.resolveSibling(filename)
if(sib.toFile().isFile)
sib.toFile().readText()
else
File(filename).readText()
}
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd) prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd)
} }
@ -2179,3 +2168,19 @@ internal class Compiler(private val program: Program): IAstProcessor {
} }
} }
fun loadAsmIncludeFile(filename: String, source: Path): String {
return if (filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8))
?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
} else {
// first try in the same folder as where the containing file was imported from
val sib = source.resolveSibling(filename)
if (sib.toFile().isFile)
sib.toFile().readText()
else
File(filename).readText()
}
}

View File

@ -1,16 +1,17 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.loadAsmIncludeFile
class CallGraphBuilder(private val program: Program): IAstProcessor { class CallGraphBuilder(private val program: Program): IAstProcessor {
private val modulesImporting = mutableMapOf<Module, Set<Module>>().withDefault { mutableSetOf() } private val modulesImporting = mutableMapOf<Module, Set<Module>>().withDefault { mutableSetOf() }
private val modulesImportedBy = mutableMapOf<Module, Set<Module>>().withDefault { mutableSetOf() } private val modulesImportedBy = mutableMapOf<Module, Set<Module>>().withDefault { mutableSetOf() }
private val subroutinesCalling = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { mutableSetOf() } private val subroutinesCalling = mutableMapOf<INameScope, Set<Subroutine>>().withDefault { mutableSetOf() }
private val subroutinesCalledBy = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { mutableSetOf() } private val subroutinesCalledBy = mutableMapOf<Subroutine, Set<INameScope>>().withDefault { mutableSetOf() }
private fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) { fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) { fun findSubs(scope: INameScope) {
scope.statements.forEach { scope.statements.forEach {
if(it is Subroutine) if(it is Subroutine)
@ -32,9 +33,6 @@ class CallGraphBuilder(private val program: Program): IAstProcessor {
it.importedBy.addAll(modulesImportedBy.getValue(it)) it.importedBy.addAll(modulesImportedBy.getValue(it))
it.imports.addAll(modulesImporting.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 -> forAllSubroutines(it) { sub ->
sub.calledBy.clear() sub.calledBy.clear()
sub.calls.clear() sub.calls.clear()
@ -42,6 +40,7 @@ class CallGraphBuilder(private val program: Program): IAstProcessor {
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub)) sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
sub.calls.addAll(subroutinesCalling.getValue(sub)) sub.calls.addAll(subroutinesCalling.getValue(sub))
} }
} }
val rootmodule = program.modules.first() val rootmodule = program.modules.first()
@ -49,21 +48,27 @@ class CallGraphBuilder(private val program: Program): IAstProcessor {
} }
override fun process(directive: Directive): IStatement { override fun process(directive: Directive): IStatement {
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 }
val thisModule = directive.definingModule()
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule) modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule) modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive=="%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
} }
return super.process(directive) return super.process(directive)
} }
override fun process(functionCall: FunctionCall): IExpression { override fun process(functionCall: FunctionCall): IExpression {
val otherSub = functionCall.target.targetSubroutine(program.namespace) val otherSub = functionCall.target.targetSubroutine(program.namespace)
if(otherSub!=null) { if(otherSub!=null) {
val thisSub = functionCall.definingScope() as Subroutine functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub)
}
} }
return super.process(functionCall) return super.process(functionCall)
} }
@ -71,9 +76,10 @@ class CallGraphBuilder(private val program: Program): IAstProcessor {
override fun process(functionCallStatement: FunctionCallStatement): IStatement { override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace) val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if(otherSub!=null) { if(otherSub!=null) {
val thisSub = functionCallStatement.definingScope() as Subroutine functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub)
}
} }
return super.process(functionCallStatement) return super.process(functionCallStatement)
} }
@ -81,10 +87,55 @@ class CallGraphBuilder(private val program: Program): IAstProcessor {
override fun process(jump: Jump): IStatement { override fun process(jump: Jump): IStatement {
val otherSub = jump.identifier?.targetSubroutine(program.namespace) val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if(otherSub!=null) { if(otherSub!=null) {
val thisSub = jump.definingScope() as Subroutine jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub) subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub) subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(thisSub)
}
} }
return super.process(jump) return super.process(jump)
} }
override fun process(inlineAssembly: InlineAssembly): IStatement {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
return super.process(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Node, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
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) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(scope)
} 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) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(scope)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target= matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(scope)
}
}
}
}
}
}
} }

View File

@ -7,18 +7,18 @@ import kotlin.math.floor
/* /*
TODO FIX THE OPTIMIZER: RESULTS IN WRONG CODE FOR THE primes.p8 EXAMPLE
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers) 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: implement usage counters for blocks, variables, subroutines, heap variables. Then: todo inline subroutines that are called exactly once (regardless of their size)
todo remove unused: subroutines, blocks (in this order) todo inline subroutines that are only called a few times (max 3?) (if < 20 statements)
todo remove unused: variable declarations todo inline all subroutines that are "very small" (0-3 statements)
todo remove unused strings and arrays from the heap
todo inline subroutines that are called exactly once (regardless of their size)
todo inline subroutines that are only called a few times (max 3?)
todo inline subroutines that are "sufficiently small" (0-3 statements)
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) 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
*/ */
class StatementOptimizer(private val program: Program) : IAstProcessor { class StatementOptimizer(private val program: Program) : IAstProcessor {
@ -33,22 +33,50 @@ class StatementOptimizer(private val program: Program) : IAstProcessor {
val callgraph = CallGraphBuilder(program) val callgraph = CallGraphBuilder(program)
callgraph.process(program) callgraph.process(program)
// 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()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if(sub !== entrypoint && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removeSubroutines.add(sub)
}
}
if(removeSubroutines.isNotEmpty()) {
removeSubroutines.forEach { it.definingScope().statements.remove(it) }
}
val removeBlocks = mutableSetOf<Block>()
// TODO remove blocks that have no incoming references
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars())
removeBlocks.add(block)
}
if(removeBlocks.isNotEmpty()) {
removeBlocks.forEach { it.definingScope().statements.remove(it) }
}
// remove modules that are not imported, or are empty // remove modules that are not imported, or are empty
val removeModules = mutableSetOf<Module>() val removeModules = mutableSetOf<Module>()
program.modules.forEach { program.modules.forEach {
if(it.importedBy.isEmpty() || it.isEmpty()) { if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
printWarning("discarding empty or unused module: ${it.name}")
removeModules.add(it) removeModules.add(it)
}
} }
program.modules.removeAll(removeModules)
if(removeModules.isNotEmpty()) {
println("[debug] removing ${removeModules.size} empty/unused modules")
program.modules.removeAll(removeModules)
}
super.process(program) super.process(program)
} }
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
if(block.statements.isEmpty()) { if(block.containsNoCodeNorVars()) {
// remove empty block
optimizationsDone++ optimizationsDone++
statementsToRemove.add(block) statementsToRemove.add(block)
} }
@ -56,12 +84,10 @@ class StatementOptimizer(private val program: Program) : IAstProcessor {
} }
override fun process(subroutine: Subroutine): IStatement { override fun process(subroutine: Subroutine): IStatement {
println("STMT OPTIMIZE $subroutine ${subroutine.calledBy} ${subroutine.calls}")
super.process(subroutine) super.process(subroutine)
if(subroutine.asmAddress==null) { if(subroutine.asmAddress==null) {
if(subroutine.statements.isEmpty()) { if(subroutine.containsNoCodeNorVars()) {
// remove empty subroutine
optimizationsDone++ optimizationsDone++
statementsToRemove.add(subroutine) statementsToRemove.add(subroutine)
} }
@ -212,13 +238,13 @@ class StatementOptimizer(private val program: Program) : IAstProcessor {
override fun process(ifStatement: IfStatement): IStatement { override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement) super.process(ifStatement)
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
statementsToRemove.add(ifStatement) statementsToRemove.add(ifStatement)
optimizationsDone++ optimizationsDone++
return ifStatement return ifStatement
} }
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
// invert the condition and move else part to true part // invert the condition and move else part to true part
ifStatement.truepart = ifStatement.elsepart ifStatement.truepart = ifStatement.elsepart
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position) ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
@ -246,7 +272,7 @@ class StatementOptimizer(private val program: Program) : IAstProcessor {
override fun process(forLoop: ForLoop): IStatement { override fun process(forLoop: ForLoop): IStatement {
super.process(forLoop) super.process(forLoop)
if(forLoop.body.isEmpty()) { if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop // remove empty for loop
statementsToRemove.add(forLoop) statementsToRemove.add(forLoop)
optimizationsDone++ optimizationsDone++

View File

@ -22,8 +22,11 @@ private class LexerErrorListener: BaseErrorListener() {
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input) internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
fun importModule(program: Program, filePath: Path): Module { fun importModule(program: Program, filePath: Path): Module {
print("importing '${filePath.fileName}'") print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) { if(filePath.parent!=null) {
var importloc = filePath.toString() var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString() val curdir = Paths.get("").toAbsolutePath().toString()
@ -48,7 +51,7 @@ fun importLibraryModule(program: Program, name: String): Module? {
} }
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = modulePath.fileName val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream) val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener() val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors) lexer.addErrorListener(lexerErrors)

View File

@ -3,6 +3,9 @@
~ main { ~ main {
; TODO FIX THE COMPILER OPTIMIZER ; RESULTS IN WRONG CODE FOR THIS PROGRAM
ubyte[256] sieve ubyte[256] sieve
ubyte candidate_prime = 2 ; is increased in the loop ubyte candidate_prime = 2 ; is increased in the loop