mirror of
https://github.com/irmen/prog8.git
synced 2024-11-26 11:49:22 +00:00
big compiler speedup due to optimized scope lookups
This commit is contained in:
parent
a94bc40ab0
commit
f5e6db9d66
@ -31,13 +31,13 @@ which aims to provide many conveniences over raw assembly code (even when using
|
|||||||
|
|
||||||
Rapid edit-compile-run-debug cycle:
|
Rapid edit-compile-run-debug cycle:
|
||||||
|
|
||||||
- use modern PC to work on
|
- use a modern PC to do the work on
|
||||||
- quick compilation times (seconds)
|
- very quick compilation times
|
||||||
- option to automatically run the program in the Vice emulator
|
- can automatically run the program in the Vice emulator after succesful compilation
|
||||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||||
|
|
||||||
It is mainly targeted at the Commodore-64 machine at this time.
|
Prog8 is mainly targeted at the Commodore-64 machine at this time.
|
||||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
||||||
|
|
||||||
Documentation/manual
|
Documentation/manual
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
|
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'org.jetbrains.dokka' version "0.9.18"
|
id 'org.jetbrains.dokka' version "0.9.18"
|
||||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||||
|
@ -53,33 +53,31 @@ interface INameScope {
|
|||||||
|
|
||||||
fun linkParents(parent: Node)
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
fun subScopes(): Map<String, INameScope> {
|
fun subScope(name: String): INameScope? {
|
||||||
// TODO PERFORMANCE: this is called very often and is relatively expensive. Optimize this.
|
|
||||||
val subscopes = mutableMapOf<String, INameScope>()
|
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
when(stmt) {
|
when(stmt) {
|
||||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is BranchStatement -> {
|
is BranchStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is WhenStatement -> {
|
is WhenStatement -> {
|
||||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||||
|
if(scope!=null)
|
||||||
|
return scope.statements
|
||||||
}
|
}
|
||||||
is INameScope -> subscopes[stmt.name] = stmt
|
is INameScope -> if(stmt.name==name) return stmt
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subscopes
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLabelOrVariable(name: String): Statement? {
|
fun getLabelOrVariable(name: String): Statement? {
|
||||||
@ -127,7 +125,7 @@ interface INameScope {
|
|||||||
for(module in localContext.definingModule().program.modules) {
|
for(module in localContext.definingModule().program.modules) {
|
||||||
var scope: INameScope? = module
|
var scope: INameScope? = module
|
||||||
for(name in scopedName.dropLast(1)) {
|
for(name in scopedName.dropLast(1)) {
|
||||||
scope = scope?.subScopes()?.get(name) // TODO PERFORMANCE: EXPENSIVE! (creates new map every call) OPTIMIZE.
|
scope = scope?.subScope(name)
|
||||||
if(scope==null)
|
if(scope==null)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -135,7 +133,7 @@ interface INameScope {
|
|||||||
val result = scope.getLabelOrVariable(scopedName.last())
|
val result = scope.getLabelOrVariable(scopedName.last())
|
||||||
if(result!=null)
|
if(result!=null)
|
||||||
return result
|
return result
|
||||||
return scope.subScopes()[scopedName.last()] as Statement?
|
return scope.subScope(scopedName.last()) as Statement?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -147,7 +145,7 @@ interface INameScope {
|
|||||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||||
if (result != null)
|
if (result != null)
|
||||||
return result
|
return result
|
||||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||||
if (subscope != null)
|
if (subscope != null)
|
||||||
return subscope
|
return subscope
|
||||||
// not found in this scope, look one higher up
|
// not found in this scope, look one higher up
|
||||||
@ -213,7 +211,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
|||||||
return if(mainBlocks.isEmpty()) {
|
return if(mainBlocks.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
mainBlocks[0].subScope("start") as Subroutine?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
||||||
|
|
||||||
for(mainBlock in mainBlocks) {
|
for(mainBlock in mainBlocks) {
|
||||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||||
if (startSub == null) {
|
if (startSub == null) {
|
||||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||||
} else {
|
} else {
|
||||||
@ -58,7 +58,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(irqBlocks.size>1)
|
if(irqBlocks.size>1)
|
||||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||||
for(irqBlock in irqBlocks) {
|
for(irqBlock in irqBlocks) {
|
||||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
val irqSub = irqBlock.subScope("irq") as? Subroutine
|
||||||
if (irqSub != null) {
|
if (irqSub != null) {
|
||||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||||
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.optimizer.UnusedCodeRemover
|
||||||
import prog8.optimizer.constantFold
|
import prog8.optimizer.constantFold
|
||||||
import prog8.optimizer.optimizeStatements
|
import prog8.optimizer.optimizeStatements
|
||||||
import prog8.optimizer.simplifyExpressions
|
import prog8.optimizer.simplifyExpressions
|
||||||
@ -170,6 +171,9 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
|||||||
// because simplified statements and expressions could give rise to more constants that can be folded away:
|
// because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||||
programAst.constantFold(errors)
|
programAst.constantFold(errors)
|
||||||
errors.handle()
|
errors.handle()
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover()
|
||||||
|
remover.visit(programAst)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
@ -24,11 +23,10 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||||
private val callgraph = CallGraph(program) // TODO PERFORMANCE: it is expensive to create this every round
|
private val callgraph = CallGraph(program)
|
||||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
removeUnusedCode(callgraph)
|
|
||||||
super.visit(program)
|
super.visit(program)
|
||||||
|
|
||||||
for(decl in vardeclsToRemove) {
|
for(decl in vardeclsToRemove) {
|
||||||
@ -36,46 +34,6 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
|
||||||
// TODO PERFORMANCE: expensive code (because of callgraph) OPTIMIZE THIS: only run once separately ?
|
|
||||||
// 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.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
|
||||||
removeSubroutines.add(sub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeSubroutines.isNotEmpty()) {
|
|
||||||
removeSubroutines.forEach {
|
|
||||||
it.definingScope().remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val removeBlocks = mutableSetOf<Block>()
|
|
||||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
|
||||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
|
||||||
removeBlocks.add(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeBlocks.isNotEmpty()) {
|
|
||||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
|
||||||
val removeModules = mutableSetOf<Module>()
|
|
||||||
program.modules.forEach {
|
|
||||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
|
||||||
removeModules.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeModules.isNotEmpty()) {
|
|
||||||
program.modules.removeAll(removeModules)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(block: Block): Statement {
|
override fun visit(block: Block): Statement {
|
||||||
if("force_output" !in block.options()) {
|
if("force_output" !in block.options()) {
|
||||||
if (block.containsNoCodeNorVars()) {
|
if (block.containsNoCodeNorVars()) {
|
||||||
|
55
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
55
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
|
import prog8.ast.statements.Block
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
|
||||||
|
|
||||||
|
internal class UnusedCodeRemover: IAstModifyingVisitor {
|
||||||
|
|
||||||
|
|
||||||
|
override fun visit(program: Program) {
|
||||||
|
val callgraph = CallGraph(program)
|
||||||
|
|
||||||
|
// 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.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||||
|
removeSubroutines.add(sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeSubroutines.isNotEmpty()) {
|
||||||
|
removeSubroutines.forEach {
|
||||||
|
it.definingScope().remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val removeBlocks = mutableSetOf<Block>()
|
||||||
|
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||||
|
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||||
|
removeBlocks.add(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeBlocks.isNotEmpty()) {
|
||||||
|
removeBlocks.forEach { it.definingScope().remove(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||||
|
val removeModules = mutableSetOf<Module>()
|
||||||
|
program.modules.forEach {
|
||||||
|
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||||
|
removeModules.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeModules.isNotEmpty()) {
|
||||||
|
program.modules.removeAll(removeModules)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(program)
|
||||||
|
}
|
||||||
|
}
|
@ -31,13 +31,13 @@ Eval stack redesign? (lot of work)
|
|||||||
|
|
||||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||||
Is it easier/faster to just use a single page unsplit stack?
|
Is it easier/faster to just use a single page unsplit stack?
|
||||||
It could then even be moved into the zeropage to greatly reduce code size and slowness.
|
It could then even be moved into the zeropage to reduce code size and slowness.
|
||||||
|
|
||||||
Or just move the LSB portion into a slab of the zeropage.
|
Or just move the LSB portion into a slab of the zeropage.
|
||||||
|
|
||||||
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
||||||
without having to index with X into the eval stack all the time?
|
without having to index with X into the eval stack all the time?
|
||||||
This could GREATLY improvde code size and speed for operatios that work on just a single value.
|
This could GREATLY improve code size and speed for operations that work on just a single value.
|
||||||
|
|
||||||
|
|
||||||
Bug Fixing
|
Bug Fixing
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
; shows next piece
|
; shows next piece
|
||||||
; staged speed increase
|
; staged speed increase
|
||||||
; some simple sound effects
|
; some simple sound effects
|
||||||
;
|
|
||||||
; @todo show ghost?
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user