optimize calls/jumps and loops with always known conditions.

This commit is contained in:
Irmen de Jong 2018-10-07 23:08:34 +02:00
parent 7b51597fe9
commit 3ac2385d4b
7 changed files with 138 additions and 43 deletions

View File

@ -30,21 +30,21 @@
float[len(zcoor)] rotatedz
sub start() {
if irq.time_changed {
irq.time_changed = 0
_vm_gfx_clearscr(0)
_vm_gfx_text(8, 6, 1, "Spin")
_vm_gfx_text(29, 11, 1, "to Win !")
while(1) {
if irq.time_changed {
irq.time_changed = 0
_vm_gfx_clearscr(0)
_vm_gfx_text(8, 6, 1, "Spin")
_vm_gfx_text(29, 11, 1, "to Win !")
for byte i in 0 to width//10 {
_vm_gfx_line(i*2+width//2-width//10, 130, i*10.w, 199, 6)
for byte i in 0 to width//10 {
_vm_gfx_line(i*2+width//2-width//10, 130, i*10.w, 199, 6)
}
rotate_vertices(flt(irq.global_time) / 30.0)
draw_edges()
}
rotate_vertices(flt(irq.global_time) / 30.0)
draw_edges()
}
goto start
}
sub rotate_vertices(t: float) {

View File

@ -4,12 +4,17 @@
~ main {
byte[10,5] barray = 0
sub start() {
X=barray[2,3]
barray[3,3]=X
repeat {
_vm_write_str("333\n")
} until(1)
repeat {
_vm_write_str("444\n")
} until (0)
return
}

View File

@ -307,12 +307,12 @@ interface INameScope {
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(!stmt.elsepart.isEmpty())
if(stmt.elsepart.isNotEmpty())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(!stmt.elsepart.isEmpty())
if(stmt.elsepart.isNotEmpty())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
}
@ -368,6 +368,13 @@ interface INameScope {
}
fun isEmpty() = statements.isEmpty()
fun isNotEmpty() = statements.isNotEmpty()
fun remove(stmt: IStatement) {
val removed = statements.remove(stmt)
if(!removed)
throw FatalAstException("stmt to remove wasn't found in scope")
}
}
@ -1153,7 +1160,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
class Jump(val address: Int?,
val identifier: IdentifierReference?,
val generatedLabel: String?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : IStatement {
override lateinit var parent: Node

View File

@ -83,7 +83,13 @@ class AstChecker(private val namespace: INameScope,
override fun process(returnStmt: Return): IStatement {
val expectedReturnValues = (returnStmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList()
if(expectedReturnValues.size != returnStmt.values.size) {
checkResult.add(SyntaxError("number of return values doesn't match subroutine return spec", returnStmt.position))
// if the return value is a function call, check the result of that call instead
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
val dt = (returnStmt.values[0] as FunctionCall).resultingDatatype(namespace, heap)
if(dt!=null && expectedReturnValues.isEmpty())
checkResult.add(SyntaxError("number of return values doesn't match subroutine return spec", returnStmt.position))
} else
checkResult.add(SyntaxError("number of return values doesn't match subroutine return spec", returnStmt.position))
}
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
@ -176,8 +182,8 @@ class AstChecker(private val namespace: INameScope,
override fun process(label: Label): IStatement {
// scope check
if(label.parent !is Block && label.parent !is Subroutine) {
checkResult.add(SyntaxError("Labels can only be defined in the scope of a block or within another subroutine", label.position))
if(label.parent !is Block && label.parent !is Subroutine && label.parent !is AnonymousScope) {
checkResult.add(SyntaxError("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position))
}
return super.process(label)
}

View File

@ -135,9 +135,16 @@ class StackVmProgram(val name: String, val heap: HeapValues) {
fun optimize() {
println("\nOptimizing stackVM code...")
optimizeDataConversionAndUselessDiscards()
// todo optimize stackvm code more
// remove nops (that are not a label)
this.instructions.removeIf { it.opcode==Opcode.NOP && it !is LabelInstr }
}
private fun optimizeDataConversionAndUselessDiscards() {
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
// - push somthing followed by a discard -> remove both
// - push something followed by a discard -> remove both
val typeConversionOpcodes = setOf(
Opcode.LSB,
@ -242,11 +249,6 @@ class StackVmProgram(val name: String, val heap: HeapValues) {
for(rins in instructionsToReplace) {
instructions[rins.key] = rins.value
}
// remove nops (that are not a label)
this.instructions.removeIf { it.opcode==Opcode.NOP && it !is LabelInstr }
// todo optimize stackvm code more
}
fun blockvar(scopedname: String, decl: VarDecl) {
@ -329,7 +331,7 @@ class Compiler(private val options: CompilationOptions) {
val translator = StatementTranslator(intermediate, namespace, heap)
translator.process(module)
println(" ${translator.stmtUniqueSequenceNr} source statements, ${intermediate.numInstructions} resulting instructions")
println(" ${intermediate.numInstructions} vm instructions")
return intermediate
}
@ -350,7 +352,7 @@ class Compiler(private val options: CompilationOptions) {
private class StatementTranslator(private val stackvmProg: StackVmProgram,
private val namespace: INameScope,
private val heap: HeapValues): IAstProcessor {
var stmtUniqueSequenceNr = 0
var generatedLabelSequenceNumber = 0
private set
val breakStmtLabelStack : Stack<String> = Stack()
@ -387,7 +389,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
private fun translate(statements: List<IStatement>) {
for (stmt: IStatement in statements) {
stmtUniqueSequenceNr++
generatedLabelSequenceNumber++
when (stmt) {
is Label -> translate(stmt)
is Return -> translate(stmt)
@ -592,7 +594,10 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
stackvmProg.instr(Opcode.NOP)
}
private fun makeLabel(postfix: String): String = "_prog8stmt_${stmtUniqueSequenceNr}_$postfix"
private fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8stmt_${generatedLabelSequenceNumber}_$postfix"
}
private fun translate(stmt: IfStatement) {
/*

View File

@ -33,9 +33,14 @@ fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
val optimizer = StatementOptimizer(globalNamespace, heap)
this.process(optimizer)
for(stmt in optimizer.statementsToRemove) {
val scope=stmt.definingScope()
scope.remove(stmt)
}
this.linkParents() // re-link in final configuration
if(optimizer.optimizationsDone > 0)
println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed")
this.linkParents() // re-link in final configuration
return optimizer.optimizationsDone
}

View File

@ -24,11 +24,11 @@ import prog8.functions.BuiltinFunctions
todo inline subroutines that are "sufficiently small"
*/
class StatementOptimizer(private val globalNamespace: INameScope, private val heap: HeapValues) : IAstProcessor {
class StatementOptimizer(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
var optimizationsDone: Int = 0
private set
private var statementsToRemove = mutableListOf<IStatement>()
var statementsToRemove = mutableListOf<IStatement>()
private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun process(functionCall: FunctionCallStatement): IStatement {
@ -40,20 +40,59 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he
}
}
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCall.arglist, functionCall.position)
}
}
return super.process(functionCall)
}
override fun process(functionCall: FunctionCall): IExpression {
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
}
}
return super.process(functionCall)
}
override fun process(returnStmt: Return): IStatement {
// if the return value is a subroutine call, replace this with a jump to the subroutine
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
val call = returnStmt.values[0] as FunctionCall
if(call.target.targetStatement(namespace) is Subroutine) {
optimizationsDone++
return Jump(null, call.target, null, call.position)
}
}
return super.process(returnStmt)
}
override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement)
val constvalue = ifStatement.condition.constValue(globalNamespace, heap)
val constvalue = ifStatement.condition.constValue(namespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
printWarning("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
printWarning("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
}
@ -69,6 +108,7 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he
// loopvar/reg = range value , follow by block
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment)
optimizationsDone++
return forLoop.body
}
}
@ -77,15 +117,22 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he
override fun process(whileLoop: WhileLoop): IStatement {
super.process(whileLoop)
val constvalue = whileLoop.condition.constValue(globalNamespace, heap)
val constvalue = whileLoop.condition.constValue(namespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true
// always true -> print a warning, and optimize into body + jump
printWarning("condition is always true", whileLoop.position)
whileLoop
val label = Label("__back", whileLoop.condition.position)
whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), whileLoop.condition.position),
null, whileLoop.condition.position))
optimizationsDone++
return whileLoop.body
} else {
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position)
optimizationsDone++
AnonymousScope(mutableListOf(), whileLoop.position)
}
}
@ -94,18 +141,38 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he
override fun process(repeatLoop: RepeatLoop): IStatement {
super.process(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace, heap)
val constvalue = repeatLoop.untilCondition.constValue(namespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block
printWarning("condition is always true", repeatLoop.position)
optimizationsDone++
repeatLoop.body
} else {
// always false
// always false -> print a warning, and optimize into body + jump
printWarning("condition is always false", repeatLoop.position)
repeatLoop
val label = Label("__back", repeatLoop.untilCondition.position)
repeatLoop.body.statements.add(0, label)
repeatLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), repeatLoop.untilCondition.position),
null, repeatLoop.untilCondition.position))
optimizationsDone++
return repeatLoop.body
}
}
return repeatLoop
}
override fun process(jump: Jump): IStatement {
val subroutine = jump.identifier?.targetStatement(namespace) as? Subroutine
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump) {
optimizationsDone++
return first
}
}
return jump
}
}