added unroll loop construct

This commit is contained in:
Irmen de Jong 2023-03-14 23:37:49 +01:00
parent c07cd72e85
commit fd25e85d59
11 changed files with 139 additions and 0 deletions

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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)

View File

@ -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"
}
})

View File

@ -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())

View File

@ -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() {

View File

@ -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)

View File

@ -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)

View File

@ -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!

View File

@ -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
-------------------------------

View File

@ -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 ) ;