diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt index 80640a8ba..24a6daca6 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt @@ -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), diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 10787c554..51bec907b 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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 --------------------- diff --git a/docs/source/todo.rst b/docs/source/todo.rst index c450bdb35..28ef844dc 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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.... diff --git a/examples/test.p8 b/examples/test.p8 index 0d371aa96..7f4fa2eef 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 } }