mirror of
https://github.com/irmen/prog8.git
synced 2024-11-20 03:32:05 +00:00
added unroll
loop construct
This commit is contained in:
parent
c07cd72e85
commit
fd25e85d59
@ -374,4 +374,11 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
|
||||
return if(unrollLoop.iterations<1)
|
||||
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,19 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(unrollLoop: UnrollLoop) {
|
||||
if(unrollLoop.iterations<0 || unrollLoop.iterations>65535)
|
||||
errors.err("invalid number of unrolls", unrollLoop.position)
|
||||
unrollLoop.body.statements.forEach {
|
||||
if(it !is InlineAssembly && it !is Assignment && it !is BuiltinFunctionCallStatement && it !is FunctionCallStatement && it !is PostIncrDecr)
|
||||
errors.err("invalid statement in unroll loop", it.position)
|
||||
}
|
||||
if(unrollLoop.iterations * unrollLoop.body.statements.size > 256) {
|
||||
errors.warn("large number of unrolls, potential code size issue", unrollLoop.position)
|
||||
}
|
||||
super.visit(unrollLoop)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
|
@ -55,6 +55,7 @@ class IntermediateAstMaker(private val program: Program, private val options: Co
|
||||
is Label -> transform(statement)
|
||||
is PostIncrDecr -> transform(statement)
|
||||
is RepeatLoop -> transform(statement)
|
||||
is UnrollLoop -> transform(statement)
|
||||
is Return -> transform(statement)
|
||||
is Subroutine -> {
|
||||
if(statement.isAsmSubroutine)
|
||||
@ -325,6 +326,16 @@ class IntermediateAstMaker(private val program: Program, private val options: Co
|
||||
return repeat
|
||||
}
|
||||
|
||||
private fun transform(srcUnroll: UnrollLoop): PtNodeGroup {
|
||||
val result = PtNodeGroup()
|
||||
repeat(srcUnroll.iterations) {
|
||||
srcUnroll.body.statements.forEach {
|
||||
result.add(transformStatement(it))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun transform(srcNode: Return): PtReturn {
|
||||
val ret = PtReturn(srcNode.position)
|
||||
if(srcNode.value!=null)
|
||||
|
@ -3,6 +3,7 @@ package prog8tests.ast
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.*
|
||||
@ -12,6 +13,7 @@ import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestVariousCompilerAst: FunSpec({
|
||||
@ -214,5 +216,46 @@ main {
|
||||
"""
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("unroll good") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
unroll 200 {
|
||||
cx16.r0++
|
||||
poke(2000,2)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false, errors=errors) shouldNotBe null
|
||||
errors.warnings.size shouldBe 1
|
||||
errors.warnings[0] shouldContain "large number of unrolls"
|
||||
}
|
||||
|
||||
test("unroll bad") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
repeat {
|
||||
unroll 80 {
|
||||
cx16.r0++
|
||||
when cx16.r0 {
|
||||
1 -> cx16.r0++
|
||||
else -> cx16.r0++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "invalid statement in unroll loop"
|
||||
errors.errors[1] shouldContain "invalid statement in unroll loop"
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -150,6 +150,9 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
val unrollstmt = unrollloop()?.toAst()
|
||||
if(unrollstmt!=null) return unrollstmt
|
||||
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
@ -573,6 +576,14 @@ private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
|
||||
val iterations = integerliteral().toAst().number.toInt()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return UnrollLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
|
@ -880,6 +880,26 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class UnrollLoop(val iterations: Int, var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a RepeatLoop")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if (node===body) body = replacement as AnonymousScope
|
||||
else throw FatalAstException("invalid replace")
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class UntilLoop(var body: AnonymousScope,
|
||||
var condition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
|
@ -106,6 +106,7 @@ abstract class AstWalker {
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@ -148,6 +149,7 @@ abstract class AstWalker {
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@ -394,6 +396,12 @@ abstract class AstWalker {
|
||||
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(unrollLoop: UnrollLoop, parent: Node) {
|
||||
track(before(unrollLoop, parent), unrollLoop, parent)
|
||||
unrollLoop.body.accept(this, unrollLoop)
|
||||
track(after(unrollLoop, parent), unrollLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.condition.accept(this, untilLoop)
|
||||
|
@ -131,6 +131,10 @@ interface IAstVisitor {
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(unrollLoop: UnrollLoop) {
|
||||
unrollLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.condition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
|
@ -485,6 +485,10 @@ You can also create loops by using the ``goto`` statement, but this should usual
|
||||
|
||||
Breaking out of a loop prematurely is possible with the ``break`` statement.
|
||||
|
||||
The *unroll* loop is not really a loop, but looks like one. It actually duplicates the statements in its block on the spot by
|
||||
the given number of times. It's meant to "unroll loops" - trade memory for speed by avoiding the actual repeat loop counting code.
|
||||
Only simple statements are allowed to be inside an unroll loop (assignments, function calls etc.).
|
||||
|
||||
.. attention::
|
||||
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
|
||||
after the loop without first assigning a new value to it!
|
||||
|
@ -767,6 +767,21 @@ If you omit the iteration count, it simply loops forever.
|
||||
You can still ``break`` out of such a loop if you want though.
|
||||
|
||||
|
||||
unroll loop
|
||||
^^^^^^^^^^^
|
||||
|
||||
Like a repeat loop, but trades memory for speed by not generating the code
|
||||
for the counter. Instead it duplicates the code inside the loop on the spot for
|
||||
the given number of iterations. This means that only a constant number of iterations can be specified.
|
||||
Also, only simple statements such as assignments and function calls can be inside the loop::
|
||||
|
||||
unroll 80 {
|
||||
cx16.VERA_DATA0 = 255
|
||||
}
|
||||
|
||||
A `break` statement cannot occur in an unroll loop, as there is not really a loop to break out of.
|
||||
|
||||
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
|
||||
|
@ -92,6 +92,7 @@ statement :
|
||||
| whileloop
|
||||
| untilloop
|
||||
| repeatloop
|
||||
| unrollloop
|
||||
| whenstmt
|
||||
| breakstmt
|
||||
| labeldef
|
||||
@ -289,6 +290,8 @@ untilloop: 'do' (statement | statement_block) EOL? 'until' expression ;
|
||||
|
||||
repeatloop: 'repeat' expression? EOL? (statement | statement_block) ;
|
||||
|
||||
unrollloop: 'unroll' integerliteral? EOL? (statement | statement_block) ;
|
||||
|
||||
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
|
||||
|
||||
when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;
|
||||
|
Loading…
Reference in New Issue
Block a user