diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt index 0c64bf454..f305bedc4 100644 --- a/codeCore/src/prog8/code/ast/AstPrinter.kt +++ b/codeCore/src/prog8/code/ast/AstPrinter.kt @@ -96,13 +96,14 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni str } is PtVariable -> { + val split = if(node.type in SplitWordArrayTypes) "@split" else "" val str = if(node.arraySize!=null) { val eltType = ArrayToElementTypes.getValue(node.type) - "${eltType.name.lowercase()}[${node.arraySize}] ${node.name}" + "${eltType.name.lowercase()}[${node.arraySize}] $split ${node.name}" } else if(node.type in ArrayDatatypes) { val eltType = ArrayToElementTypes.getValue(node.type) - "${eltType.name.lowercase()}[] ${node.name}" + "${eltType.name.lowercase()}[] $split ${node.name}" } else "${node.type.name.lowercase()} ${node.name}" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index 1faf4894b..674309762 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -352,6 +352,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, lda #$numElements jsr floats.func_reverse_f""") } + in SplitWordArrayTypes -> TODO("split word reverse") else -> throw AssemblyError("weird type") } } @@ -398,6 +399,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, jsr prog8_lib.func_sort_ub""") } DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported") + in SplitWordArrayTypes -> TODO("split word sort") else -> throw AssemblyError("weird type") } } else @@ -664,6 +666,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_stack") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_stack") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_stack") + in SplitWordArrayTypes -> TODO("split word any/all") else -> throw AssemblyError("weird type $dt") } } else { @@ -671,6 +674,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0") + in SplitWordArrayTypes -> TODO("split word any/all") else -> throw AssemblyError("weird type $dt") } assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 72ac2497c..6fc6dbd73 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -1469,8 +1469,6 @@ internal class AssignmentAsmGen(private val program: PtProgram, // return // } TargetStorageKind.ARRAY -> { - if(target.array!!.splitWords) - TODO("assign type casted byte into split words ${target.position}") // byte to word, just assign to registers first, then assign into array assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD) assignRegisterpairWord(target, RegisterOrPair.AY) @@ -1868,21 +1866,25 @@ internal class AssignmentAsmGen(private val program: PtProgram, } in WordDatatypes -> { if(target.array!!.splitWords) - TODO("assign word stack value into split words ${target.position}") - asmgen.out(""" - inx - lda P8ESTACK_LO,x - sta ${target.asmVarname}+$scaledIdx - lda P8ESTACK_HI,x - sta ${target.asmVarname}+$scaledIdx+1 - """) + asmgen.out(""" + inx + lda P8ESTACK_LO,x + sta ${target.asmVarname}_lsb+$scaledIdx + lda P8ESTACK_HI,x + sta ${target.asmVarname}_msb+$scaledIdx""") + else + asmgen.out(""" + inx + lda P8ESTACK_LO,x + sta ${target.asmVarname}+$scaledIdx + lda P8ESTACK_HI,x + sta ${target.asmVarname}+$scaledIdx+1""") } DataType.FLOAT -> { asmgen.out(""" lda #<(${target.asmVarname}+$scaledIdx) ldy #>(${target.asmVarname}+$scaledIdx) - jsr floats.pop_float - """) + jsr floats.pop_float""") } else -> throw AssemblyError("weird target variable type ${target.datatype}") } @@ -2933,9 +2935,7 @@ internal class AssignmentAsmGen(private val program: PtProgram, throw AssemblyError("memory is bytes not words") } TargetStorageKind.ARRAY -> { - if(target.array!!.splitWords) - TODO("assign into split words ${target.position}") - asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y) + asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y) if(target.array.splitWords) asmgen.out(""" lda #0 @@ -2992,9 +2992,7 @@ internal class AssignmentAsmGen(private val program: PtProgram, throw AssemblyError("assign word to memory ${target.memory} should have gotten a typecast") } TargetStorageKind.ARRAY -> { - if(target.array!!.splitWords) - TODO("assign into split words ${target.position}") - asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y) + asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y) if(target.array.splitWords) asmgen.out(""" lda #<${word.toHex()} @@ -3392,8 +3390,6 @@ internal class AssignmentAsmGen(private val program: PtProgram, asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") } TargetStorageKind.ARRAY -> { - if(wordtarget.array!!.splitWords) - TODO("assign mem byte into split words ${wordtarget.position}") asmgen.out(" lda ${address.toHex()} | ldy #0") assignRegisterpairWord(wordtarget, RegisterOrPair.AY) } @@ -3423,8 +3419,6 @@ internal class AssignmentAsmGen(private val program: PtProgram, asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") } TargetStorageKind.ARRAY -> { - if(wordtarget.array!!.splitWords) - TODO("assign mem byte into split words ${wordtarget.position}") asmgen.loadByteFromPointerIntoA(identifier) asmgen.out(" ldy #0") assignRegisterpairWord(wordtarget, RegisterOrPair.AY) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt index f9e75a318..ea7a16865 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AugmentableAssignmentAsmGen.kt @@ -193,11 +193,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram, TargetStorageKind.ARRAY -> { val indexNum = target.array!!.index as? PtNumber val indexVar = target.array.index as? PtIdentifier - if(target.array.splitWords) - TODO("in-place assign split words ${target.position}") when { indexNum!=null -> { - val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}" + val targetVarName = if(target.array.splitWords) + "${target.asmVarname} + ${indexNum.number.toInt()}" + else + "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}" when (target.datatype) { in ByteDatatypes -> { when(value.kind) { @@ -284,8 +285,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram, } in WordDatatypes -> { asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y) - asmgen.out(" lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1") - asmgen.out(" iny | lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1+1") + if(target.array.splitWords) { + asmgen.out(" lda ${target.array.variable.name}_lsb,y | sta P8ZP_SCRATCH_W1") + asmgen.out(" lda ${target.array.variable.name}_msb,y | sta P8ZP_SCRATCH_W1+1") + } else { + asmgen.out(" lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1") + asmgen.out(" iny | lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1+1") + } asmgen.saveRegisterLocal(CpuRegister.Y, target.scope!!) when(value.kind) { SourceStorageKind.LITERALNUMBER -> inplaceModification_word_litval_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.number!!.number.toInt()) @@ -305,8 +311,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram, else -> throw AssemblyError("weird source type ${value.kind}") } asmgen.restoreRegisterLocal(CpuRegister.Y) - asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name},y") - asmgen.out(" lda P8ZP_SCRATCH_W1 | dey | sta ${target.array.variable.name},y") + if(target.array.splitWords) { + asmgen.out(" lda P8ZP_SCRATCH_W1 | sta ${target.array.variable.name}_lsb,y") + asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name}_msb,y") + } else { + asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name},y") + asmgen.out(" lda P8ZP_SCRATCH_W1 | dey | sta ${target.array.variable.name},y") + } } DataType.FLOAT -> { asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A) diff --git a/compiler/test/codegeneration/TestArrayInplaceAssign.kt b/compiler/test/codegeneration/TestArrayInplaceAssign.kt index 3b8f09dd4..5a904d100 100644 --- a/compiler/test/codegeneration/TestArrayInplaceAssign.kt +++ b/compiler/test/codegeneration/TestArrayInplaceAssign.kt @@ -1,12 +1,16 @@ package prog8tests.codegeneration import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import prog8.code.ast.printAst import prog8.code.target.C64Target import prog8.code.target.VMTarget +import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.compileText -class TestArrayInplaceAssign: FunSpec({ +class TestArrayThings: FunSpec({ test("assign prefix var to array should compile fine and is not split into inplace array modification") { val text = """ main { @@ -88,5 +92,48 @@ main { compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null } + + test("split only for word arrays") { + val text = """ +main { + ubyte[10] @split sb + uword[10] @split sw + word[10] @split sw2 + float[10] @split sf + + sub start() { + } +}""" + val errors = ErrorReporterForTests() + compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null + errors.errors.size shouldBe 2 + errors.errors.forEach { + it shouldContain "split" + it shouldContain "word arrays" + } + } + + test("split word arrays in asm as lsb/msb") { + val text = """ +main { + uword[10] @split @shared uw + word[10] @split @shared sw + uword[10] @shared normal + + sub start() { + %asm {{ + lda normal + lda uw_lsb + lda uw_msb + lda sw_lsb + lda sw_msb + }} + } +}""" + val result6502 = compileText(C64Target(), false, text, writeAssembly = true)!!.codegenAst!! + val resultVm = compileText(VMTarget(), false, text, writeAssembly = true)!!.codegenAst!! + printAst(result6502, true, ::println) + printAst(resultVm, true, ::println) + } }) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 3083ad1e8..ce2bdbfc4 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -290,10 +290,10 @@ Here are some examples of arrays:: .. note:: Right now, the array should be small enough to be indexable by a single byte index. - This means byte arrays should be <= 256 elements, word arrays <= 128 elements, and float - arrays <= 51 elements. + This means byte arrays should be <= 256 elements, word arrays <= 128 elements (256 if + it's a split array - see below), and float arrays <= 51 elements. -You can split an array initializer list over several lines if you want. +You can write out an array initializer list over several lines if you want to improve readability. Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.) can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte`` @@ -323,6 +323,21 @@ dynamic, location. You can use array indexing on a pointer variable to use it as a dynamic location in memory: currently this is equivalent to directly referencing the bytes in memory at the given index. See also :ref:`pointervars_programming` +**LSB/MSB split word arrays:** +For (u)word arrays, you can make the compiler layout the array in memory as two separate arrays, +one with the LSBs and one with the MSBs of the word values. This is more efficient when storing +and reading words from the array (the index can be used twice). +Add the ``@split`` tag to the variable declaration to do this. +In the assembly code, the array will be generated as two byte arrays namely ``name_lsb`` and ``name_msb``. +Note that the maximum length of a split word array is 256! (regular word arrays are limited to 128 elements). + +.. caution:: + Not all array operations are supported yet on "split word arrays". + The compiler may give an unpleasant error or crash when you hit such a case in your code. + If this happens simply revert to a regular word array and please report the issue, + so that more support can be added in the future where it is needed. + + Strings ^^^^^^^ diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 04fb22a41..974267cb6 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -274,17 +274,22 @@ Variable declarations Variables should be declared with their exact type and size so the compiler can allocate storage for them. You can give them an initial value as well. That value can be a simple literal value, or an expression. If you don't provide an initial value yourself, zero will be used. -You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it +Add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it when selecting variables to be put into zeropage (but no guarantees). If the ZP is full, the variable will be allocated in normal memory elsewhere. -Use the ``@requirezp`` tag to force the variable in zeropage, but if the ZP is full, +Add a ``@requirezp`` tag to force the variable in zeropage, but if the ZP is full, the compilation will fail. -You can add a ``@shared`` shared-tag, to tell the compiler that the variable is shared +Add a ``@shared`` shared-tag, to tell the compiler that the variable is shared with some assembly code and that it should not be optimized away if not used elsewhere. +For (u)word arrays, add ``@split`` to make the array layout in memory as 2 split arrays +one with the LSBs one with the MSBs of the word values. + + The syntax is:: - [ @shared ] [ @zp ] [ @requirezp ] [ = ] + [ @type-tag ] [ = ] +where type-tag is one of the tags mentioned earlier. Various examples:: word thing = 0 diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 0289105bb..a8fdd547b 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -16,14 +16,7 @@ For 9.0 major changes - DONE: (on cx16) added diskio.save_raw() to save without the 2 byte prg header - DONE: added sys.irqsafe_xxx irqd routines - DONE: added gfx2.fill() flood fill routine - -- [much work:] add special (u)word array type (or modifier such as @fast or @split? ) 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.... - So maybe only allow the bare essentials? (store, get, ++/--/+/-, bitwise operations?) - -- TODO: fix the remaining 'simple' split words TODO cases (expression assignments) -- TODO: change more library and examples to use more @split arrays -- TODO: splitarrays unit tests +- DONE: added @split storage class for (u)word arrays to store them as split lsb/msb arrays which is more efficient (but doesn't yet support all array operations) - [much work:] more support for (64tass) SEGMENTS ? - (What, how, isn't current BSS support enough?) diff --git a/examples/bdmusic.p8 b/examples/bdmusic.p8 index 4155e9829..4efb79444 100644 --- a/examples/bdmusic.p8 +++ b/examples/bdmusic.p8 @@ -71,7 +71,7 @@ sub print_notes(ubyte n1, ubyte n2) { ] - uword[] music_freq_table = [ + uword[] @split music_freq_table = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556, 1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304, diff --git a/examples/cube3d-gfx.p8 b/examples/cube3d-gfx.p8 index abf5bfff6..197a601c7 100644 --- a/examples/cube3d-gfx.p8 +++ b/examples/cube3d-gfx.p8 @@ -12,9 +12,9 @@ main { word[] @split zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ] ; storage for rotated coordinates - word[len(xcoor)] rotatedx - word[len(ycoor)] rotatedy - word[len(zcoor)] rotatedz + word[len(xcoor)] @split rotatedx + word[len(ycoor)] @split rotatedy + word[len(zcoor)] @split rotatedz ; edges ubyte[] edgesFrom = [ 0, 2, 6, 4, 1, 3, 7, 5, 0, 2, 6, 4] diff --git a/examples/cube3d-sprites.p8 b/examples/cube3d-sprites.p8 index 772d88f15..9486a4025 100644 --- a/examples/cube3d-sprites.p8 +++ b/examples/cube3d-sprites.p8 @@ -72,9 +72,9 @@ main { word[] @split zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ] ; storage for rotated coordinates - word[len(xcoor)] rotatedx - word[len(ycoor)] rotatedy - word[len(zcoor)] rotatedz + word[len(xcoor)] @split rotatedx + word[len(ycoor)] @split rotatedy + word[len(zcoor)] @split rotatedz sub start() { diff --git a/examples/cube3d.p8 b/examples/cube3d.p8 index 2d680babf..bffd57109 100644 --- a/examples/cube3d.p8 +++ b/examples/cube3d.p8 @@ -12,9 +12,9 @@ main { word[] @split zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ] ; storage for rotated coordinates - word[len(xcoor)] rotatedx - word[len(ycoor)] rotatedy - word[len(zcoor)] rotatedz + word[len(xcoor)] @split rotatedx + word[len(ycoor)] @split rotatedy + word[len(zcoor)] @split rotatedz sub start() { diff --git a/examples/cx16/bdmusic.p8 b/examples/cx16/bdmusic.p8 index c1531cb4e..4fea29576 100644 --- a/examples/cx16/bdmusic.p8 +++ b/examples/cx16/bdmusic.p8 @@ -111,7 +111,7 @@ main { $3532, $322e, $2e29, $2926, $2730, $242c, $2027, $1420 ] - uword[] vera_freqs = [ + uword[] @split vera_freqs = [ 0,0,0,0,0,0,0,0,0,0, ; first 10 notes are not used 120, 127, 135, 143, 152, 160, 170, 180, 191, 203, 215, 227, 240, 255, 270, 287, 304, 320, 341, 360, diff --git a/examples/cx16/cobramk3-gfx.p8 b/examples/cx16/cobramk3-gfx.p8 index 533cd1612..1b4a0d4d5 100644 --- a/examples/cx16/cobramk3-gfx.p8 +++ b/examples/cx16/cobramk3-gfx.p8 @@ -8,9 +8,9 @@ main { ; storage for rotated coordinates - word[shipdata.totalNumberOfPoints] rotatedx - word[shipdata.totalNumberOfPoints] rotatedy - word[shipdata.totalNumberOfPoints] rotatedz + word[shipdata.totalNumberOfPoints] @split rotatedx + word[shipdata.totalNumberOfPoints] @split rotatedy + word[shipdata.totalNumberOfPoints] @split rotatedz sub start() { uword anglex diff --git a/examples/cx16/cube3d.p8 b/examples/cx16/cube3d.p8 index 3de759995..1cf3e04f5 100644 --- a/examples/cx16/cube3d.p8 +++ b/examples/cx16/cube3d.p8 @@ -9,9 +9,9 @@ main { word[] @split zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ] ; storage for rotated coordinates - word[len(xcoor)] rotatedx - word[len(ycoor)] rotatedy - word[len(zcoor)] rotatedz + word[len(xcoor)] @split rotatedx + word[len(ycoor)] @split rotatedy + word[len(zcoor)] @split rotatedz sub start() { diff --git a/examples/cx16/snow.p8 b/examples/cx16/snow.p8 index a6a35fe8e..67d7575bd 100644 --- a/examples/cx16/snow.p8 +++ b/examples/cx16/snow.p8 @@ -5,10 +5,10 @@ main { sub start() { gfx2.screen_mode(4) - uword[128] flakes1_xx - uword[128] flakes1_yy - uword[128] flakes2_xx - uword[128] flakes2_yy + uword[128] @split flakes1_xx + uword[128] @split flakes1_yy + uword[128] @split flakes2_xx + uword[128] @split flakes2_yy ubyte @zp idx for idx in 0 to 127 { diff --git a/examples/test.p8 b/examples/test.p8 index 468a3e7ce..24ee6b1fb 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,18 +1,14 @@ %import floats %import textio %zeropage basicsafe +%option no_sysinit main { sub start() { uword[] @split split_uwords = [12345, 60000, 4096] - ubyte xx=1 - txt.print_ub(lsb(split_uwords[xx])) - txt.spc() - txt.print_ub(msb(split_uwords[xx])) - txt.spc() - split_uwords[1] = mkword($11, xx) ; TODO fix this in codegen - txt.print_uw(split_uwords[1]) + txt.print_ub(len(split_uwords)) + txt.nl() } sub start22() {