diff --git a/compiler/examples/cube3d.p8 b/compiler/examples/cube3d.p8 index f79d5a72a..843535a0b 100644 --- a/compiler/examples/cube3d.p8 +++ b/compiler/examples/cube3d.p8 @@ -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) { diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 2d36818c7..6d8c6a80e 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -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 } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 537ec4424..facfa9437 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -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 diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index f82b83635..bb468fdf6 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -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) } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 0ded59a61..18e21402d 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -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 = Stack() @@ -387,7 +389,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, private fun translate(statements: List) { 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) { /* diff --git a/compiler/src/prog8/optimizing/Extensions.kt b/compiler/src/prog8/optimizing/Extensions.kt index e8e216d98..a1742ec66 100644 --- a/compiler/src/prog8/optimizing/Extensions.kt +++ b/compiler/src/prog8/optimizing/Extensions.kt @@ -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 } diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 3af8474d8..b754ea90a 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -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() + var statementsToRemove = mutableListOf() + 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 + } } \ No newline at end of file