mirror of
https://github.com/irmen/prog8.git
synced 2025-03-13 05:31:01 +00:00
callgraph fixed scanning asm subroutines, and deletion of unused subs and modules
This commit is contained in:
parent
b374af3526
commit
7de7d5234f
@ -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 }
|
||||||
|
@ -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) }
|
||||||
|
@ -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)) {
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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++
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user