for loops no longer execute when from var already reached beyond the end

This commit is contained in:
Irmen de Jong 2023-05-02 23:21:12 +02:00
parent 3d69a95c49
commit 21bc505d85
4 changed files with 154 additions and 84 deletions

View File

@ -54,19 +54,28 @@ internal class ForLoopsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
if(stepsize<0) {
println("signed byte check to<=from") // TODO
} else {
println("signed byte check from<=to") // TODO
}
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
else
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0) {
println("unsigned byte check to<=from") // TODO
} else {
println("unsigned byte check from<=to") // TODO
}
if(stepsize<0)
asmgen.out(" cmp $varname | bcs $endLabel")
else
asmgen.out(" cmp $varname | bcc $endLabel | beq $endLabel")
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(" sta $modifiedLabel+1")
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
@ -84,20 +93,30 @@ $modifiedLabel cmp #0 ; modified
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
if(stepsize<0) {
println("signed byte check to<=from") // TODO
} else {
println("signed byte check from<=to") // TODO
}
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
else
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0) {
println("unsigned byte check to<=from") // TODO
} else {
println("unsigned byte check from<=to") // TODO
}
if(stepsize<0)
asmgen.out(" cmp $varname | bcs $endLabel")
else
asmgen.out(" cmp $varname | bcc $endLabel | beq $endLabel")
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
@ -129,22 +148,9 @@ $modifiedLabel cmp #0 ; modified
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_W) {
if(stepsize<0) {
println("signed word check to<=from") // TODO
} else {
println("signed word check from<=to") // TODO
}
} else {
if(stepsize<0) {
println("unsigned word check to<=from") // TODO
} else {
println("unsigned word check from<=to") // TODO
}
}
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@ -177,14 +183,9 @@ $modifiedLabel2 cmp #0 ; modified
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_W) {
println("signed word check from<=to") // TODO
} else {
println("unsigned word check from<=to") // TODO
}
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@ -231,14 +232,9 @@ $endLabel""")
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_W) {
println("signed word check to<=from") // TODO
} else {
println("unsigned word check to<=from") // TODO
}
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@ -289,6 +285,53 @@ $endLabel""")
asmgen.loopEndLabels.pop()
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
// pre-check for end already reached.
// 'to' is in AY, do NOT clobber this!
if(iterableDt==DataType.ARRAY_W) {
if(stepsize<0)
asmgen.out("""
sta P8ZP_SCRATCH_REG
cmp $fromVar
tya
sbc $fromVar+1
bvc +
eor #${'$'}80
+ bpl $endLabel
lda P8ZP_SCRATCH_REG""")
else
asmgen.out("""
sta P8ZP_SCRATCH_REG
cmp $fromVar
tya
sbc $fromVar+1
bvc +
eor #${'$'}80
+ bmi $endLabel
lda P8ZP_SCRATCH_REG""")
} else {
if(stepsize<0)
asmgen.out("""
cpy $fromVar+1
beq +
bcc ++
bcs $endLabel
+ cmp $fromVar
bcc +
beq +
bne $endLabel
+""")
else
asmgen.out("""
cpy $fromVar+1
bcc $endLabel
bne +
cmp $fromVar
bcc $endLabel
+""")
}
}
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
@ -645,7 +688,7 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.variable),

View File

@ -473,6 +473,7 @@ Loops
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
The loop variable must be declared separately as byte or word earlier, so that you can reuse it for multiple occasions.
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
If the from value is already outside of the loop range, the whole for loop is skipped.
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
The *do--until* loop is used to repeat a piece of code until a certain condition is true.
@ -492,16 +493,6 @@ Only simple statements are allowed to be inside an unroll loop (assignments, fun
on it to be the last value in the range for instance! The value of the variable should only be used inside the for loop body.
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
.. warning::
For efficiency reasons, it is assumed that the ending value of the for loop is actually >= the starting value
(or <= if the step is negative). This means that for loops in prog8 behave differently than in other
languages if this is *not* the case! A for loop from ubyte 10 to ubyte 2, for example, will iterate through
all values 10, 11, 12, 13, .... 254, 255, 0 (wrapped), 1, 2. In other languages the entire loop will
be skipped in such cases. But prog8 omits the overhead of an extra loop range check and/or branch for every for loop
because the most common case is that it is not needed.
You should add an explicit range check yourself if the ending value can be less than the start value and
a full wrap-around loop is not what you want!
Conditional Execution
---------------------

View File

@ -10,12 +10,8 @@ For 9.0 major changes
- DONE: divmod() now supports multiple datatypes. divmodw() has been removed.
- DONE: cx16diskio module merged into diskio (which got specialized for commander x16 target). load() and load_raw() with extra ram bank parameter are gone.
- DONE: drivenumber parameter removed from all routines in diskio module. The drive to work on is now simply stored as a diskio.drivenumber variable, which defaults to 8.
- DONE: for loops now skip the whole loop if from value already outside the loop range (this is what all other programming languages also do)
- VM codegen: fix for loop pre-check for negative stepsizes.
- 6502 codegen: see if we can let for loops skip the loop if startvar>endvar, without adding a lot of code size/duplicating the loop condition.
It is documented behavior to now loop 'around' $00 but it's too easy to forget about!
Lot of work because of many special cases in ForLoopsAsmgen.translateForOverNonconstRange()
(vm codegen already behaves like this partly! Except for negative step sizes right now)
- once 9.0 is stable, upgrade other programs (assem, shell, etc) to it. + add migration guide to the manual.
- [much work:] add special (u)word array type (or modifier such as @fast? ) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
this is an enormous amout of work, if this type is to be treated equally as existing (u)word , because all expression / lookup / assignment routines need to know about the distinction....

View File

@ -5,67 +5,107 @@
main {
sub start() {
ubyte from = 20
ubyte target = 10
ubyte from = 250
ubyte xx
for xx in from to target {
txt.print_ub(xx)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
for xx in target downto from {
txt.print_ub(xx)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
byte starget = -120
byte sfrom = 120
byte sfrom = -10
byte starget = -20
byte sxx
for sxx in sfrom to starget {
txt.print_b(sxx)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
for sxx in starget downto sfrom {
txt.print_b(sxx)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
uword wtarget = 10
uword wfrom = 65530
uword wfrom = 1020
uword wtarget = 1010
uword ww
for ww in wfrom to wtarget {
txt.print_uw(ww)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
for ww in wtarget downto wfrom {
txt.print_uw(ww)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
word swtarget = -32760
word swfrom = 32760
word swfrom = -1010
word swtarget = -1020
word sww
for sww in swfrom to swtarget {
txt.print_w(sww)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
for sww in swtarget downto swfrom {
txt.print_w(sww)
txt.spc()
}
txt.print("done\n")
txt.print("done\n\n")
; all of the above with stepsize 2 / -2
for xx in from to target step 2{
txt.print_ub(xx)
txt.spc()
}
txt.print("done\n\n")
for xx in target downto from step -2 {
txt.print_ub(xx)
txt.spc()
}
txt.print("done\n\n")
for sxx in sfrom to starget step 2 {
txt.print_b(sxx)
txt.spc()
}
txt.print("done\n\n")
for sxx in starget downto sfrom step -2 {
txt.print_b(sxx)
txt.spc()
}
txt.print("done\n\n")
for ww in wfrom to wtarget step 2 {
txt.print_uw(ww)
txt.spc()
}
txt.print("done\n\n")
for ww in wtarget downto wfrom step -2 {
txt.print_uw(ww)
txt.spc()
}
txt.print("done\n\n")
for sww in swfrom to swtarget step 2 {
txt.print_w(sww)
txt.spc()
}
txt.print("done\n\n")
for sww in swtarget downto swfrom step -2 {
txt.print_w(sww)
txt.spc()
}
txt.print("done\n\n")
; TODO all of the above with stepsize 2 / -2
}
}