diff --git a/compiler/examples/cube3d.p8 b/compiler/examples/cube3d.p8 index f8e2308d2..854484a6c 100644 --- a/compiler/examples/cube3d.p8 +++ b/compiler/examples/cube3d.p8 @@ -88,6 +88,7 @@ } ; draw all edges of the object + ; @todo crashes with array index out of bounds in stackvm for uword edge in edges { ubyte e_from = msb(edge) ubyte e_to = lsb(edge) diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index bf546d107..e233a26a2 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -1,12 +1,49 @@ %import c64utils -%option enable_floats - ~ main { - ;@todo implement the various byte/word division routines. + ubyte[3] ubarray = [11,55,222] + byte[3] barray = [-11,-22,-33] + uword[3] uwarray = [111,2222,55555] + word[3] warray = [-111,-222,-555] + str text = "hello\n" + sub start() { + c64scr.print_ub(X) + c64.CHROUT('\n') + c64scr.print("loop str\n") + for ubyte c in text { + c64scr.print(" c ") + c64scr.print_ub(c) + c64.CHROUT('\n') + } + + c64scr.print("loop ub\n") + for ubyte ub in ubarray{ + c64scr.print(" ub ") + c64scr.print_ub(ub) + c64.CHROUT('\n') + } + +; c64scr.print("loop b\n") ; @todo allow signed loopvars +; for byte b in barray { ; @todo loop doesn't end because of register clobbering?? +; c64scr.print(" b ") +; c64scr.print_b(b) +; c64.CHROUT('\n') +; } + +errorloop: + c64scr.print("loop uw\n") + for uword uw in uwarray { ; @todo loop doesn't end because of register clobbering + c64scr.print(" uw ") + c64scr.print_uw(uw) + c64.CHROUT('\n') + } + +ending: + c64scr.print("\nending\n") + c64scr.print_ub(X) + c64.CHROUT('\n') } - } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index f3a134b06..e3ad82f85 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -1656,6 +1656,10 @@ class ForLoop(val loopRegister: Register?, override fun toString(): String { return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)" } + + companion object { + const val iteratorLoopcounterVarname = "prog8forloopcounter" + } } diff --git a/compiler/src/prog8/ast/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/AstIdentifiersChecker.kt index 94beb062b..8e8fe6349 100644 --- a/compiler/src/prog8/ast/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/AstIdentifiersChecker.kt @@ -151,8 +151,10 @@ class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor { } override fun process(forLoop: ForLoop): IStatement { - // if the for loop has a decltype, it means to declare the loopvar inside the loop body + // If the for loop has a decltype, it means to declare the loopvar inside the loop body // rather than reusing an already declared loopvar from an outer scope. + // For loops that loop over an interable variable (instead of a range of numbers) get an + // additional interation count variable in their scope. if(forLoop.loopRegister!=null) { if(forLoop.decltype!=null) checkResult.add(SyntaxError("register loop variables cannot be explicitly declared with a datatype", forLoop.position)) @@ -164,14 +166,28 @@ class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor { DataType.UBYTE, DataType.UWORD -> { val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) if(existing==null) { + // create the local scoped for loop variable itself val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, null, varName, null, forLoop.loopVar.position) vardecl.linkParents(forLoop.body) forLoop.body.statements.add(0, vardecl) forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' } } + DataType.BYTE, DataType.WORD -> { + checkResult.add(SyntaxError("loop variables can only be unsigned byte or unsigned word", forLoop.position)) // TODO allow signed loopvars + } null -> {} - else -> checkResult.add(SyntaxError("loop variables can only be an unsigned byte or unsigned word", forLoop.position)) // TODO loops over signed values + else -> checkResult.add(SyntaxError("loop variables can only be a byte or word", forLoop.position)) + } + if(forLoop.iterable !is RangeExpr) { + val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) + if(existing==null) { + // create loop iteration counter variable + val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, null, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position) + vardecl.linkParents(forLoop.body) + forLoop.body.statements.add(0, vardecl) + forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' + } } } return super.process(forLoop) diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 55c78dbf5..45d8c43fc 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1682,7 +1682,7 @@ private class StatementTranslator(private val prog: IntermediateProgram, AssignTarget(loop.loopRegister, null, null, loop.position) else AssignTarget(null, loop.loopVar!!.copy(), null, loop.position) - val arrayspec = ArraySpec(RegisterExpr(Register.Y, loop.position), loop.position) + val arrayspec = ArraySpec(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position) val assignLv = Assignment( listOf(assignTarget), null, ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position), @@ -1691,11 +1691,13 @@ private class StatementTranslator(private val prog: IntermediateProgram, translate(assignLv) translate(loop.body) prog.label(continueLabel) - prog.instr(opcodeIncvar(zero.type), callLabel = "Y") + + val loopCounterVar = loop.body.getLabelOrVariable(ForLoop.iteratorLoopcounterVarname) as VarDecl + prog.instr(opcodeIncvar(zero.type), callLabel = loopCounterVar.scopedname) // TODO: optimize edge cases if last value = 255 or 0 (for bytes) etc. to avoid PUSH_BYTE / SUB opcodes and make use of the wrapping around of the value. prog.instr(opcodePush(zero.type), Value(zero.type, numElements)) - prog.instr(opcodePushvar(zero.type), callLabel = "Y") + prog.instr(opcodePushvar(zero.type), callLabel = loopCounterVar.scopedname) prog.instr(opcodeSub(zero.type)) if(zero.type==DataType.UWORD) prog.instr(Opcode.JNZW, callLabel = loopLabel) diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 11d9fc7d7..05b839a15 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -42,7 +42,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap optimizeVariableCopying() optimizeMultipleSequentialLineInstrs() optimizeCallReturnIntoJump() - optimizeRestoreXSaveXIntoRestoreX() + optimizeRestoreXYSaveXYIntoRestoreXY() // todo: optimize stackvm code more optimizeRemoveNops() // must be done as the last step @@ -56,8 +56,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr } } - private fun optimizeRestoreXSaveXIntoRestoreX() { - // replace rrestorex+rsavex combo by only rrestorex + private fun optimizeRestoreXYSaveXYIntoRestoreXY() { + // replace rrestorex/y+rsavex/y combo by only rrestorex/y for(blk in blocks) { val instructionsToReplace = mutableMapOf() @@ -65,6 +65,9 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap if(it[0].value.opcode==Opcode.RRESTOREX && it[1].value.opcode==Opcode.RSAVEX) { instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) } + else if(it[0].value.opcode==Opcode.RRESTOREY && it[1].value.opcode==Opcode.RSAVEY) { + instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) + } } for (rins in instructionsToReplace) { diff --git a/compiler/src/prog8/compiler/intermediate/Opcode.kt b/compiler/src/prog8/compiler/intermediate/Opcode.kt index 4aa070c18..1c889cc8c 100644 --- a/compiler/src/prog8/compiler/intermediate/Opcode.kt +++ b/compiler/src/prog8/compiler/intermediate/Opcode.kt @@ -240,8 +240,10 @@ enum class Opcode { CLI, // clear irq-disable status flag RSAVE, // save all internal registers and status flags RSAVEX, // save just X (the evaluation stack pointer) + RSAVEY, // save just Y (used in for loops for instance) RRESTORE, // restore all internal registers and status flags RRESTOREX, // restore just X (the evaluation stack pointer) + RRESTOREY, // restore just Y (used in for loops for instance) NOP, // do nothing BREAKPOINT, // breakpoint TERMINATE, // end the program diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index dcc3d1e37..b5f558514 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -432,8 +432,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, // restore all registers and cpu status flag " pla | tay | pla | tax | pla | plp" } - Opcode.RSAVEX -> " stx ${C64Zeropage.SCRATCH_REG_X}" + Opcode.RSAVEX -> " stx ${C64Zeropage.SCRATCH_REG_X}" // TODO on stack instead, to allow nested calls? Opcode.RRESTOREX -> " ldx ${C64Zeropage.SCRATCH_REG_X}" + Opcode.RSAVEY -> " tya | pha" + Opcode.RRESTOREY -> " pla | tay" Opcode.DISCARD_BYTE -> " inx" Opcode.DISCARD_WORD -> " inx" Opcode.DISCARD_FLOAT -> " inx | inx | inx" diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index faf845b69..1a3bc8ea3 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -1338,7 +1338,9 @@ class StackVm(private var traceOutputFile: String?) { P_irqd = evalstack.pop().asBooleanValue } Opcode.RSAVEX -> evalstack.push(variables["X"]) + Opcode.RSAVEY -> evalstack.push(variables["Y"]) Opcode.RRESTOREX -> variables["X"] = evalstack.pop() + Opcode.RRESTOREY -> variables["Y"] = evalstack.pop() Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code") Opcode.PUSH_ADDR_HEAPVAR -> { val heapId = variables[ins.callLabel]!!.heapId