From c03f6604af5d16a4a882332b2b8062f1900be010 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 30 May 2021 00:25:47 +0200 Subject: [PATCH] added free words counting method to zeropage --- compiler/src/prog8/compiler/Zeropage.kt | 24 +++- .../compiler/target/cpu6502/codegen/AsmGen.kt | 33 +++-- .../target/cpu6502/codegen/ForLoopsAsmGen.kt | 4 +- compiler/test/UnitTests.kt | 96 ++++++++++---- examples/test.p8 | 118 ++++++++++++------ 5 files changed, 199 insertions(+), 76 deletions(-) diff --git a/compiler/src/prog8/compiler/Zeropage.kt b/compiler/src/prog8/compiler/Zeropage.kt index c92cbea7c..ce300bddc 100644 --- a/compiler/src/prog8/compiler/Zeropage.kt +++ b/compiler/src/prog8/compiler/Zeropage.kt @@ -19,7 +19,29 @@ abstract class Zeropage(protected val options: CompilationOptions) { val allowedDatatypes = NumericDatatypes - fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size + fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size + fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty() + fun availableWords(): Int { + if(options.zeropage==ZeropageType.DONTUSE) + return 0 + + val words = free.windowed(2).filter { it[0] == it[1]-1 } + var nonOverlappingWordsCount = 0 + var prevMsbLoc = -1 + for(w in words) { + if(w[0]!=prevMsbLoc) { + nonOverlappingWordsCount++ + prevMsbLoc = w[1] + } + } + return nonOverlappingWordsCount + } + fun hasWordAvailable(): Boolean { + if(options.zeropage==ZeropageType.DONTUSE) + return false + + return free.windowed(2).any { it[0] == it[1] - 1 } + } fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int { assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 841a287b5..7a8fb85e6 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -1070,14 +1070,31 @@ $repeatLabel lda $counterVar private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): Pair { // TODO share counter variables between subroutines or even between repeat loops as long as they're not nested val counterVar = makeLabel("repeatcounter") - val zpAvail = if(dt==DataType.UWORD) 2 else 1 - return if(constIterations!=null && constIterations>=16 && zeropage.available() >= zpAvail) { - // allocate count var on ZP - val zpAddr = zeropage.allocate(counterVar, dt, stmt.position, errors) - out("$counterVar = $zpAddr ; auto zp $dt") - Pair(counterVar, false) - } else { - Pair(counterVar, true) + +// println("REPEATCOUNTERVAR $counterVar SIZE $dt CTX ${stmt.definingSubroutine()}") + + when(dt) { + DataType.UBYTE -> { + return if(constIterations!=null && constIterations>=16 && zeropage.hasByteAvailable()) { + // allocate count var on ZP + val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors) + out("$counterVar = $zpAddr ; auto zp byte") + Pair(counterVar, false) + } else { + Pair(counterVar, true) + } + } + DataType.UWORD -> { + return if(constIterations!=null && constIterations>=16 && zeropage.hasWordAvailable()) { + // allocate count var on ZP + val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors) + out("$counterVar = $zpAddr ; auto zp word") + Pair(counterVar, false) + } else { + Pair(counterVar, true) + } + } + else -> throw AssemblyError("invalidt dt") } } diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/ForLoopsAsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/ForLoopsAsmGen.kt index 99727fb27..24e5229ec 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/ForLoopsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/ForLoopsAsmGen.kt @@ -288,7 +288,7 @@ $loopLabel sty $indexVar bne $loopLabel beq $endLabel""") } - if(length>=16 && asmgen.zeropage.available() > 0) { + if(length>=16 && asmgen.zeropage.hasByteAvailable()) { // allocate index var on ZP val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") @@ -327,7 +327,7 @@ $loopLabel sty $indexVar bne $loopLabel beq $endLabel""") } - if(length>=16 && asmgen.zeropage.available() > 0) { + if(length>=16 && asmgen.zeropage.hasByteAvailable()) { // allocate index var on ZP val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index ee56170ba..e6e7654f9 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -179,28 +179,48 @@ class TestC64Zeropage { fun testZpDontuse() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target)) println(zp.free) - assertEquals(0, zp.available()) + assertEquals(0, zp.availableBytes()) assertFailsWith { zp.allocate("", DataType.BYTE, null, errors) } } @Test - fun testFreeSpaces() { + fun testFreeSpacesBytes() { val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) - assertEquals(18, zp1.available()) + assertEquals(18, zp1.availableBytes()) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) - assertEquals(85, zp2.available()) + assertEquals(85, zp2.availableBytes()) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) - assertEquals(125, zp3.available()) + assertEquals(125, zp3.availableBytes()) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) - assertEquals(238, zp4.available()) + assertEquals(238, zp4.availableBytes()) + zp4.allocate("test", DataType.UBYTE, null, errors) + assertEquals(237, zp4.availableBytes()) + zp4.allocate("test2", DataType.UBYTE, null, errors) + assertEquals(236, zp4.availableBytes()) + } + + @Test + fun testFreeSpacesWords() { + val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) + assertEquals(6, zp1.availableWords()) + val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) + assertEquals(38, zp2.availableWords()) + val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) + assertEquals(57, zp3.availableWords()) + val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) + assertEquals(116, zp4.availableWords()) + zp4.allocate("test", DataType.UWORD, null, errors) + assertEquals(115, zp4.availableWords()) + zp4.allocate("test2", DataType.UWORD, null, errors) + assertEquals(114, zp4.availableWords()) } @Test fun testReservedSpace() { val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) - assertEquals(238, zp1.available()) + assertEquals(238, zp1.availableBytes()) assertTrue(50 in zp1.free) assertTrue(100 in zp1.free) assertTrue(49 in zp1.free) @@ -209,7 +229,7 @@ class TestC64Zeropage { assertTrue(255 in zp1.free) assertTrue(199 in zp1.free) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target)) - assertEquals(139, zp2.available()) + assertEquals(139, zp2.availableBytes()) assertFalse(50 in zp2.free) assertFalse(100 in zp2.free) assertTrue(49 in zp2.free) @@ -222,18 +242,22 @@ class TestC64Zeropage { @Test fun testBasicsafeAllocation() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) - assertEquals(18, zp.available()) + assertEquals(18, zp.availableBytes()) + assertTrue(zp.hasByteAvailable()) + assertTrue(zp.hasWordAvailable()) assertFailsWith { // in regular zp there aren't 5 sequential bytes free zp.allocate("", DataType.FLOAT, null, errors) } - for (i in 0 until zp.available()) { + for (i in 0 until zp.availableBytes()) { val loc = zp.allocate("", DataType.UBYTE, null, errors) assertTrue(loc > 0) } - assertEquals(0, zp.available()) + assertEquals(0, zp.availableBytes()) + assertFalse(zp.hasByteAvailable()) + assertFalse(zp.hasWordAvailable()) assertFailsWith { zp.allocate("", DataType.UBYTE, null, errors) } @@ -245,16 +269,18 @@ class TestC64Zeropage { @Test fun testFullAllocation() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) - assertEquals(238, zp.available()) + assertEquals(238, zp.availableBytes()) + assertTrue(zp.hasByteAvailable()) + assertTrue(zp.hasWordAvailable()) val loc = zp.allocate("", DataType.UWORD, null, errors) assertTrue(loc > 3) assertFalse(loc in zp.free) - val num = zp.available() / 2 + val num = zp.availableBytes() / 2 for(i in 0..num-4) { zp.allocate("", DataType.UWORD, null, errors) } - assertEquals(6,zp.available()) + assertEquals(6,zp.availableBytes()) assertFailsWith { // can't allocate because no more sequential bytes, only fragmented @@ -265,7 +291,9 @@ class TestC64Zeropage { zp.allocate("", DataType.UBYTE, null, errors) } - assertEquals(0, zp.available()) + assertEquals(0, zp.availableBytes()) + assertFalse(zp.hasByteAvailable()) + assertFalse(zp.hasWordAvailable()) assertFailsWith { // no more space zp.allocate("", DataType.UBYTE, null, errors) @@ -275,7 +303,7 @@ class TestC64Zeropage { @Test fun testEfficientAllocation() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) - assertEquals(18, zp.available()) + assertEquals(18, zp.availableBytes()) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors)) @@ -288,7 +316,7 @@ class TestC64Zeropage { assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors)) - assertEquals(0, zp.available()) + assertEquals(0, zp.availableBytes()) } @Test @@ -301,6 +329,8 @@ class TestC64Zeropage { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TestCx16Zeropage { + private val errors = ErrorReporter() + @Test fun testReservedLocations() { val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target)) @@ -308,19 +338,37 @@ class TestCx16Zeropage { } @Test - fun testFreeSpaces() { + fun testFreeSpacesBytes() { val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target)) - assertEquals(88, zp1.available()) - val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target)) - assertEquals(175, zp3.available()) - val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) - assertEquals(216, zp4.available()) + assertEquals(88, zp1.availableBytes()) + val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target)) + assertEquals(175, zp2.availableBytes()) + val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) + assertEquals(216, zp3.availableBytes()) + zp3.allocate("test", DataType.UBYTE, null, errors) + assertEquals(215, zp3.availableBytes()) + zp3.allocate("test2", DataType.UBYTE, null, errors) + assertEquals(214, zp3.availableBytes()) + } + + @Test + fun testFreeSpacesWords() { + val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) + assertEquals(108, zp1.availableWords()) + val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target)) + assertEquals(87, zp2.availableWords()) + val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target)) + assertEquals(44, zp3.availableWords()) + zp3.allocate("test", DataType.UWORD, null, errors) + assertEquals(43, zp3.availableWords()) + zp3.allocate("test2", DataType.UWORD, null, errors) + assertEquals(42, zp3.availableWords()) } @Test fun testReservedSpace() { val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) - assertEquals(216, zp1.available()) + assertEquals(216, zp1.availableBytes()) assertTrue(0x22 in zp1.free) assertTrue(0x80 in zp1.free) assertTrue(0xff in zp1.free) diff --git a/examples/test.p8 b/examples/test.p8 index 6b4e2b0d2..01ffd7746 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,11 +1,16 @@ %import textio ; txt.* -%zeropage kernalsafe +%zeropage basicsafe main { + ; test program for the optimization of repeat var allocation (asmgen.createRepeatCounterVar) + ; output must be: 60 6164 6224 12328 + + uword xx + sub start() { - ubyte xx + xx=0 repeat 10 { xx++ @@ -19,55 +24,86 @@ main { repeat 10 { xx++ } + repeat 5 { + repeat 4 { + xx++ + } + } + txt.print_uw(xx) + txt.nl() + + repeat 1000 { + xx++ + } + repeat 1000 { + xx++ + } + repeat 1000 { + xx++ + } + repeat 260 { + repeat 4 { + xx++ + } + } + repeat 260 { + repeat 260 { + xx++ + } + } + txt.print_uw(xx) + txt.nl() + + sub2() + + if xx!=12328 + txt.print("\n!fail!\n") + else + txt.print("\nok\n") + } + + sub sub2() { + repeat 10 { xx++ } - repeat 1000 { + repeat 10 { xx++ } - repeat 1000 { + repeat 10 { xx++ } - repeat 1000 { + repeat 10 { xx++ } - repeat 1000 { - xx++ - } - repeat 1000 { - xx++ + repeat 5 { + repeat 4 { + xx++ + } } + txt.print_uw(xx) + txt.nl() - str string1 = "stringvalue\n" - str string2 = "stringvalue\n" - str string3 = "stringvalue\n" - str string4 = "a" - str string5 = "bb" - - txt.print(string1) - txt.print(string2) - txt.print(string3) - string1[1]='?' - string2[2] = '?' - string3[3] = '?' - txt.print(string1) - txt.print(string2) - txt.print(string3) - - txt.print("a") - txt.print("a") - txt.print(string4) - txt.print("bb") - txt.print("bb") - txt.print(string5) - txt.print("\n") - txt.print("\n\n") - txt.print("hello\n") - txt.print("hello\n") - txt.print("hello\n") - txt.print("bye\n") - txt.print("bye\n") - txt.print("bye\n") - + repeat 1000 { + xx++ + } + repeat 1000 { + xx++ + } + repeat 1000 { + xx++ + } + repeat 260 { + repeat 4 { + xx++ + } + } + repeat 260 { + repeat 260 { + xx++ + } + } + txt.print_uw(xx) + txt.nl() } }